☰
SpringBoot基础全解:从自动装配原理到项目实战避坑指南
2026/10/7 10:29:53 网站建设 项目流程

SpringBoot 这话题我从 1.x 时代就开始写了,到现在 3.x 都出来这么久,后台私信里问得最多的依然是"SpringBoot 到底怎么学"、 "项目结构怎么搭合理"这类入门问题。正好借这个机会,把 SpringBoot 基础篇梳理成一套完整的内容,从核心原理到实操细节一次讲透。这篇文章不整虚的,产线怎么用我就怎么写。

1. SpringBoot 到底是什么——从 SSM 时代的痛点说起

很多新手学 SpringBoot 的第一反应是"又一个新框架要学",其实恰恰相反,SpringBoot 的诞生是为了消灭框架。回到 2013 年前后,Java 后端的主流配置是 SSH(Spring + Struts + Hibernate)或者 SSM(Spring + SpringMVC + MyBatis),每次新建项目都要写一堆 XML 配置,数据源配一个、事务管理器配一个、SpringMVC 视图解析器配一个、还有一堆组件扫描、AOP 切面配置。配置文件的篇幅经常超过业务代码,而且每个项目之间复制粘贴改来改去,极容易出错。

SpringBoot 的核心思想用一句话概括:约定大于配置。它把过去需要手动写的大量配置变成"默认行为",你只需要在需要偏离默认规则的时候才写配置。比如过去配置一个内嵌 Tomcat 需要引入依赖、配置端口、配置连接池;现在你引入spring-boot-starter-web,默认端口 8080 就起来了,想改端口就写一行server.port=9090。

这里有个很多人没想明白的关键点:SpringBoot 不是一个新的编程框架,它只是 Spring 框架的一种自动化装配方案。底层用的依然是 Spring 的 IOC 容器、AOP、SpringMVC 这些老伙计,只是把这些东西的组装过程自动化了。所以你在 SpringBoot 里写@Autowired、@Service、@Transactional这些注解时,本质还是在写 Spring 代码。

能干什么、解决什么问题也很明确:快速搭建独立运行的 Spring 应用、零 XML 配置或极简配置、内嵌 Web 容器实现一键启动、通过 starter 机制简化依赖管理。适合谁来看?打算入门 Java 后端的初学者、从传统 SSM 工程迁移的老开发、以及想搞清楚 SpringBoot 自动装配原理的进阶选手。这一篇把底层的逻辑捋清楚,后面你学 SpringCloud、搭微服务才会不慌。

2. 环境准备与第一个 SpringBoot 应用

2.1 开发环境和版本选型

搞 SpringBoot 第一步是选对版本。现在 SpringBoot 官方维护的是 3.x 和 2.7.x 两个大版本线,3.x 要求 JDK 17+,2.7.x 兼容 JDK 8 和 JDK 11。我的建议很直接:新项目优先 3.x,老项目维护看 2.7 的合规基础。

网上经常看到"springboot版本太高"的报错,本质原因是版本与 JDK 或依赖组件不匹配。比如装了 JDK 8 硬上 SpringBoot 3.2.x,启动直接报UnsupportedClassVersionError,这不是框架的问题,是版本组合没选对。版本的配对关系记住这个表:

SpringBoot 版本最低 JDK内嵌 Tomcat适用场景
2.7.xJDK 89.0.x老项目维护、公司强制 JDK 8
3.0.xJDK 1710.1.x新项目首选
3.2.xJDK 1710.1.x新项目,推荐
3.4.xJDK 1710.1.x最新特性,功能激进

选版本另一个坑是 SpringCloud 和 SpringBoot 版本对应关系,如果你打算后续引入微服务治理,先查 SpringCloud 发版公告里支持的 Boot 版本范围,两个大版本错开会导致组件加载失败。基础篇阶段先不用管 SpringCloud,但心里要有这根弦。

2.2 项目结构再拆解——目录不是摆设

新手用 IDEA 的 Spring Initializr 生成项目后,看到一堆目录容易懵。实际产线项目的基础结构一般长这样:

src/main/java ├── com.example.demo │ ├── DemoApplication.java # 启动类,必须在根包 │ ├── controller/ # 接口层 │ ├── service/ # 业务逻辑层 │ ├── mapper/ # 数据访问层(MyBatis) │ ├── entity/ # 数据库实体 │ ├── dto/ # 传输对象 │ ├── config/ # 配置类 │ └── common/ # 通用工具、常量、异常 src/main/resources ├── application.yml # 主配置文件 ├── application-dev.yml # 开发环境 ├── mapper/ # MyBatis XML 文件 ├── static/ # 静态资源 └── templates/ # 模板引擎页面

这里必须强调一个最常见的错误:把启动类放在子包里。SpringBoot 启动时默认扫描启动类所在包及子包下的组件,如果你把启动类放在com.example.admin,而 Controller 放在com.example.web,那 Web 层的@RestController根本不会被扫描注册,接口直接 404。解决方式一:保证启动类位于所有类的根包;解决方式二:手动指定@SpringBootApplication(scanBasePackages = "com.example"),我个人更推荐后者,因为包结构更灵活。

2.3 启动原理——从 main 方法到内嵌 Tomcat

DemoApplication.java里面就一个main方法加上@SpringBootApplication注解,点运行之后发生了什么?

第一步调SpringApplication.run(DemoApplication.class, args),这里会创建 Spring 应用上下文(AnnotationConfigServletWebServerApplicationContext)。第二步根据你的依赖推断应用类型:classpath 里有spring-boot-starter-web就启动 Web 容器,没有就启动普通上下文。第三步执行自动装配逻辑,加载所有META-INF/spring.factories文件里的配置类,创建内嵌 Tomcat、初始化 DispatcherServlet。

内嵌 Tomcat 是 SpringBoot 一个极具代表性的设计。传统 SSM 项目要把打好 war 包丢到外部 Tomcat 的 webapps 目录,SpringBoot 直接把 Tomcat 作为依赖嵌进来,代码里启动 Tomcat、绑定端口、部署应用一条龙完成。这也是为什么java -jar就能跑应用的原因——Tomcat 的 jar 包都在 fat jar 里,内置的JarLauncher负责把它们加载起来。

这里补充一个我实测过的坑:如果你自定义了 Tomcat 的TomcatServletWebServerFactory去改端口、改虚拟线程等参数,注意配置生效时机是在SpringApplication.run执行过程中,不是 run 完之后。如果你想在 run 之前动态改端口(比如从配置中心拉取),得在 main 方法里先设置System.setProperty("server.port", "9090"),或者在 run 之前构造SpringApplication实例后手动设置属性。

3. 核心配置体系——从 application.yml 到多环境管理

3.1 配置文件的优先级和加载顺序

SpringBoot 的配置文件支持.properties和.yml两种格式,application.yml是我最推荐的,层级清晰很多。配置文件的加载顺序有一定优先级设计,你要知道这个顺序,因为排查问题时会发现"明明配置了怎么不生效"的怪事,多半是优先级理解反了。

默认情况下,SpringBoot 按下面的优先级从高到低加载配置:

  1. 命令行参数(java -jar app.jar --server.port=8081)
  2. JVM 系统属性(-Dserver.port=8081)
  3. 操作系统环境变量
  4. application-{profile}.yml(指定环境)
  5. application.yml
  6. classpath 内部的application.yml

所以你遇到"改配置文件没反应"的问题,先检查是不是有环境变量或者启动脚本里硬编码的参数把它覆盖了。我记得有一次排查了一个多小时,发现是 Dockerfile 里ENV SERVER_PORT=8080把配置文件里的 9090 盖掉了,这种问题在云原生环境下很容易踩。

3.2 YAML 配置的常用项和绑定方式

一个典型的application.yml配置长这样:

server: port: 9090 servlet: context-path: /api spring: application: name: demo-service datasource: url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.entity logging: level: com.example.demo.mapper: debug

context-path这个配置很多人容易忽略,它决定了所有接口的前缀。如果你配置了/api,那么@RequestMapping("/user")实际对外暴露的地址是/api/user。前后端联调时最常遇到的 404 问题,一半以上跟这个前缀有关。

配置绑定我推荐用@ConfigurationProperties,它能把配置文件里的结构化数据直接映射成 Java 对象。举个例子,你配置了:

app: upload: path: /data/upload maxSize: 100MB

对应的配置类:

@Component @ConfigurationProperties(prefix = "app.upload") @Data public class UploadProperties { private String path; private DataSize maxSize; }

然后在任何地方@Autowired这个类就能拿到配置值。用@ConfigurationProperties的好处是类型安全,DataSize类型会自动把 "100MB" 解析成字节数,避免了用@Value拿字符串还要手动转换的麻烦。配合 IDEA 的 spring-boot-configuration-processor 依赖还能在写配置时有代码提示。

3.3 多环境配置——dev/prod 切换的正确姿势

产线上的多环境配置绝对不能靠手动改配置文件,正确姿势是 profile 机制。

在resources目录下放多个配置文件:

# application.yml 只放公共配置 spring: profiles: active: dev # application-dev.yml 放开发环境 server: port: 8080 # application-prod.yml 放生产环境 server: port: 8080

启动时用--spring.profiles.active=prod覆盖默认环境,或者用环境变量SPRING_PROFILES_ACTIVE=prod指定。

要提醒的是,数据库密码、密钥这类敏感配置不要直接写进 application-prod.yml 再推到 Git 仓库。产线实践一般用环境变量注入或者配置中心(Nacos、Apollo),配置文件里只留占位符${DB_PASSWORD},在部署平台填实际值。

4. 自动装配原理与 starter 机制

4.1 自动装配到底自动了什么

SpringBoot 最核心的机制就是自动装配。很多教程讲到原理就止步于"SpringBoot 启动时会自动读取 spring.factories",但实际原理远比这个复杂,值得好好拆解。

@SpringBootApplication是一个组合注解,它由三个注解组成:

  • @SpringBootConfiguration:本质上是一个@Configuration,标记这是一个配置类
  • @EnableAutoConfiguration:自动装配的总开关
  • @ComponentScan:组件扫描

关键在@EnableAutoConfiguration,它通过@Import引入了AutoConfigurationImportSelector类。这个类做的事情是:

  1. 读取所有 jar 包META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件(SpringBoot 2.7 之前是META-INF/spring.factories)
  2. 拿到所有自动配置类的全限定名列表
  3. 根据条件注解(@ConditionalOnClass、@ConditionalOnMissingBean等)进行过滤
  4. 把符合条件的配置类导入 IOC 容器

拿DataSourceAutoConfiguration举例,它上面加了@ConditionalOnClass({ DataSource.class }),意思是只有当 classpath 里存在DataSource类时才执行这个自动配置。你引入了mybatis-spring-boot-starter,classpath 里就有数据源相关的类,自动配置生效,帮你创建DataSource、SqlSessionFactory。你没引入相关依赖,条件不满足,不执行。

这套"有依赖才自动配置"的机制是 SpringBoot 能实现"零配置启动"的根本原因。它把选择权交给了依赖本身——你引入什么 starter,框架就自动为你装配什么能力。

4.2 条件注解——自动装配的基石

条件是自动装配的决策机制,常用的条件注解有:

注解作用
@ConditionalOnClassclasspath 存在指定类才生效
@ConditionalOnMissingClassclasspath 不存在指定类才生效
@ConditionalOnBean容器中存在指定 Bean 才生效
@ConditionalOnMissingBean容器中不存在指定 Bean 才生效
@ConditionalOnProperty配置文件中存在指定配置才生效
@ConditionalOnWebApplication是 Web 应用才生效

@ConditionalOnMissingBean是最值得关注的,它给了开发者"覆盖默认配置"的机会。比如RedisAutoConfiguration里配置了一个默认的RedisTemplate<String, String>,同时加了@ConditionalOnMissingBean(name = "redisTemplate")。这意味着如果你在项目里自己定义了一个RedisTemplate,SpringBoot 的默认配置就自动退位,用你的。这种设计既保证了开箱即用,又留了自定义的窗口。

4.3 如何写一个自定义 starter

理解自动装配之后,进阶操作就是自定义 starter。产线场景很常见:多个微服务都要用同一个工具包(比如统一的日志切面、统一的接口签名校验),直接拷贝代码是最差方案,封装成 starter 才是正解。

创建一个自定义 starter 的基础结构:

my-common-starter/ ├── pom.xml # 只做依赖管理,不放业务逻辑 └── src/main/java └── com.example.common ├── CommonAutoConfiguration.java └── config/ └── CommonProperties.java src/main/resources └── META-INF └── spring/ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports

CommonAutoConfiguration.java内容:

@Configuration @EnableConfigurationProperties(CommonProperties.class) public class CommonAutoConfiguration { @Bean @ConditionalOnMissingBean public SignFilter signFilter() { return new SignFilter(); } }

AutoConfiguration.imports文件内容一行一个配置类全限定名:

com.example.common.CommonAutoConfiguration

核心点是:自动配置类不加@Component注解,否则组件扫描会重复加载,失去"条件控制"的意义。加载的工作完全交给AutoConfigurationImportSelector去处理。

写 starter 还有一个规范问题:SpringBoot 官方要求把自动配置代码放在独立的XXXAutoConfiguration模块中,业务工具类放在XXX-starter模块中,starter 模块只依赖自动配置模块。这样能避免自动配置类被业务代码意外扫描。小型团队可以简化,但大项目尽量遵守。

5. Web 开发与数据访问实践

5.1 Controller 层与参数接收的细节

Web 层是 SpringBoot 里用得最多但也最容易写错的层。先说参数接收的几个典型写法:

@RestController @RequestMapping("/user") public class UserController { // GET 请求路径参数 :8080/user/1 @GetMapping("/{id}") public Result<User> getUser(@PathVariable Long id) { return success(userService.getById(id)); } // GET 请求查询参数 :8080/user/list?page=1&size=10 @GetMapping("/list") public Result<PageResult<User>> list(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { return success(userService.page(page, size)); } // POST 请求 JSON 参数 @PostMapping public Result<User> save(@RequestBody @Validated UserDTO userDTO) { return success(userService.save(userDTO)); } }

@PathVariable和@RequestParam的区别要拎清楚:前者是 URL 路径的一部分,后者是?后面的键值对。如果你用@RequestParam去接路径参数,或者反过来,接口肯定报错。@RequestBody是接 JSON 请求体的,POST、PUT 这类有 body 的请求才用。

一个容易踩的坑:@RequestParam接不到参数时默认直接报 400,所以必须给非必传参数加defaultValue,或者设置required = false。前端的调用习惯是变量多个空格、空字符串、不传三种情况,后端要做好兜底判断。

JSON 序列化也有学问。默认使用 Jackson,日期格式默认输出的是时间戳(长整型),跟前端对不上时要在配置里指定格式:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

或者更推荐的方式是在实体字段上用@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")单独控制,因为不同的字段格式需求不一定一样。

5.2 整合 MyBatis——从依赖到满血运行

SpringBoot 整合 MyBatis 的流程比 SSM 时代省了太多事。依赖只需要两个:

<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>

注意mybatis-spring-boot-starter的版本要和 SpringBoot 大版本对应。SpringBoot 3.x 用 3.x 的 starter,SpringBoot 2.7 用 2.3.x 的 starter,混用会出现各种诡异的加载错误。

Mapper 接口的写法:

@Mapper public interface UserMapper { User selectById(@Param("id") Long id); List<User> selectList(@Param("keyword") String keyword); }

对应的 XML 放在resources/mapper/UserMapper.xml:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.mapper.UserMapper"> <select id="selectById" resultType="com.example.demo.entity.User"> SELECT * FROM tb_user WHERE id = #{id} </select> </mapper>

两个容易踩的坑必须说明:

第一,@Mapper注解与@MapperScan的取舍。在启动类上加了@MapperScan("com.example.demo.mapper"),就不用在每个 Mapper 接口上写@Mapper。我推荐用@MapperScan,少写注解、统一管理。但是注意扫描包路径别写宽了,一旦扫到不该扫的包,Spring 容器初始化会报找不到 Bean 的错误。

第二,resultType全限定名太啰嗦。配置了mybatis.type-aliases-package: com.example.demo.entity之后,XML 里可以直接写resultType="User"。或者进一步用@Results注解手动映射列名到属性名。

再补充一个 MyBatis 在 SpringBoot 下的缓存问题:默认情况下一级缓存是开启的,二级缓存是关闭的。如果你加了@CacheNamespace启用了二级缓存,注意实体类必须实现Serializable,否则抛NotSerializableException。产线上二级缓存用的不算多,因为多实例部署时缓存同步是个大问题,更推荐引入独立的 Redis 做缓存层。

5.3 定时任务——SpringBoot 里最容易被忽略的坑

SpringBoot 内置定时任务功能,用法非常简单。启动类或配置类加@EnableScheduling,然后业务方法上写@Scheduled(cron = "0 0/5 * * * ?")即可。

实际使用中需要注意:

定时任务默认是单线程执行的!这是新手最容易踩的坑。你写了三个@Scheduled方法,如果第一个任务执行时间长,后面两个会排队等待。解决办法是自定义定时任务线程池:

@Configuration public class ScheduleConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5)); } }

cron 表达式有个"秒级"陷阱。0 0 12 * * ?表示每天中午 12 点执行,这个是对的。但如果你写了0/1 * * * * ?表示每秒执行一次,数据库压力会瞬间拉满。每次写完 cron 表达式建议用在线解析工具验证一下,或者先写成fixedDelay、fixedRate这类相对时间先跑起来测试逻辑。

fixedDelay和fixedRate的区别也容易被忽略。fixedRate是按照固定速率执行:比如每 5 秒一次,不管上一次任务有没有执行完,到点就开启新任务,会出现任务叠加;fixedDelay是固定延迟执行:上一次任务结束之后再等 5 秒执行下一次,不会叠加。有状态的定时任务用fixedDelay更安全。

产线上更完善的方案是引入xxl-job之类的分布式任务调度平台,单机定时任务无法解决多实例重复执行的问题。基础篇先掌握内置方案,心里清楚它适合单机场景即可。

6. 版本、错误排查与面试高频考点

6.1 版本兼容矩阵——少走三年弯路

"springboot版本太高"这个热搜词出现频率很高,原因就是乱升版本引发连锁反应。给一个稳妥的选型建议:

JDK 8 老项目,老老实实用 SpringBoot 2.7.x。不要试图升到 3.x,因为 Spring 6 和 Jakarta EE 9 的包名变更会把项目里所有javax.*的 import 都改掉,工作量巨大。

JDK 17 或 21 新项目,直接用 SpringBoot 3.2.x 或 3.4.x。配套框架版本也尽量选兼容版本:MyBatis starter 3.x、Spring Cloud 2023.x、Hutool 5.8+。

遇到启动报错先别慌,按这个顺序排查:

  1. 看完整的异常栈,从最底部的Caused by开始看
  2. 检查 JDK 版本和 SpringBoot 版本是否匹配
  3. 检查依赖树是否引入了多个版本的同一个库(mvn dependency:tree)
  4. 检查是不是配置了多余的旧版配置类

6.2 端口占用与启动失败实战

启动时最常见的报错是:

Web server failed to start. Port 8080 was already in use.

这是端口被占用。解决方式:

# 查看端口占用进程 lsof -i:8080 # 强制杀掉进程 kill -9 PID

或者更省事的方案:在application.yml把端口改掉,开发环境习惯性用 8081、8082 错开,能减少很多冲突。产线上端口分配应该有规范文档,别随意占用。

还有一类启动失败是程序自己挂的,比如数据库连不上。报错信息通常是Failed to configure a DataSource,这时要看你的项目里有没有引入数据库相关依赖但没配数据源。排查思路是:不需要数据库的功能模块,别引入 mybatis 依赖;引了就要把spring.datasource配好,或者用exclude = DataSourceAutoConfiguration.class排除自动装配。

6.3 SpringBoot 默认使用 CGLIB 代理——面试高频题

"springboot默认使用cglib代理"是搜索热词,也是面试几乎必问的考点。背后逻辑值得讲清楚。

Spring 的 AOP 代理有两种实现方式:

  1. JDK 动态代理:基于接口,代理对象是接口的实现类
  2. CGLIB 代理:基于继承,代理对象是目标类的子类

SpringBoot 2.x 开始默认使用 CGLIB 代理,spring.aop.proxy-target-class=true是默认值。原因很简单:现在很多 Service 类压根不写接口,JDK 动态代理没法代理没有接口的类。

这个默认行为带来的实际影响:如果你的类被 CGLIB 代理,类不能是 final 的,目标方法不能是 private 或 final 的。CGLIB 通过生成子类来代理,final 类没法被继承,final 方法没法被重写。在公司代码里见过有人给 Service 类加了 final 修饰符,结果@Transactional和@Async全部失效,排查了很久才发现是这个原因。

另一个经典面试问法:@Transactional自调用为什么失效?假设同一个类里方法 A 调用方法 B,B 上有@Transactional,你调 A 时 B 的事务不生效。原因是 Spring 的代理对象在外部调用时才生效,自调用走的是 this 引用,绕过了代理。解决方式是拆类或者用AopContext.currentProxy()获取代理对象调用。

6.4 常用工具类与调试技巧

开发 SpringBoot 时有一些实用小技巧能提升效率:

第一,开启开发热更新。引入spring-boot-devtools依赖,改完代码后 IDEA 里按Ctrl+F9编译,应用自动重启。别把它带到产线,它默认只在开发环境生效。

第二,Actuator 监控端点。引入spring-boot-starter-actuator后访问/actuator/health就能看到应用健康状态。产线部署时这个端点对运维特别有用,配好权限暴露/health、/info就够用了。

第三,自定义 Banner。启动时的 Spring 图标可以用banner.txt文件替换,网上有在线 banner 生成器能拼出自己的文字图案。这个纯粹是团队文化装饰,有些公司会把版本号和团队名放在 banner 里,每次启动有归属感。

第四,日志框架直接用 Logback。SpringBoot 默认集成了,不用额外导入。建议配置:

logging: file: name: logs/app.log logback: rollingpolicy: max-file-size: 100MB max-history: 30

定期滚动日志是做运维的基本功,不配置的后果是日志文件无限增长,最后磁盘被打满,应用跟着挂掉。

6.5 常见问题速查表

把这些年见过的典型问题和解决方式汇总成一张表:

现象原因解决方式
接口全部 404启动类不在根包或扫描路径不对检查@SpringBootApplication包位置,或配置scanBasePackages
端口被占用端口冲突lsof -i:端口杀掉进程,或改端口
@Autowired报 null类没有交给 Spring 管理加@Service/@Component注解,或在配置类声明 Bean
配置文件修改不生效配置优先级问题查环境变量、命令行参数、profile 覆盖
内置 Tomcat 无法启动依赖冲突或 JDK 版本太低检查 SpringBoot 版本和 JDK 匹配度
中文乱码请求或响应编码不对确认server.servlet.encoding配置,数据库连接加characterEncoding=utf8
MyBatis XML 找不到mapper-locations 配置错了检查mybatis.mapper-locations路径是否匹配
CGLIB 代理失效类或方法为 final去掉 final 修饰符

这张表基本覆盖了入门阶段遇到的高频问题。遇到没见过的报错,养成先看完整异常栈的习惯——最底层Caused by才是真正的原因,表层异常信息经常只是症状。

7. 从基础到实际项目——一个完整的整合案例

讲了半天理论,最后用一个实际场景把这些基础串起来。假设现在要搭一个极简的商品管理后端,包含查询列表、新增商品两个接口,数据存 MySQL。

项目结构和核心代码:

src/main/java/com/example/shop ├── ShopApplication.java ├── controller/ProductController.java ├── service/ProductService.java ├── mapper/ProductMapper.java ├── entity/Product.java └── dto/ProductDTO.java

启动类:

@SpringBootApplication @MapperScan("com.example.shop.mapper") public class ShopApplication { public static void main(String[] args) { SpringApplication.run(ShopApplication.class, args); } }

Controller:

@RestController @RequestMapping("/api/product") public class ProductController { @Autowired private ProductService productService; @GetMapping("/list") public Result<List<Product>> list(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "20") Integer size) { return Result.success(productService.pageList(page, size)); } @PostMapping public Result<Long> add(@RequestBody @Validated ProductDTO dto) { return Result.success(productService.add(dto)); } }

Service:

@Service public class ProductService { @Autowired private ProductMapper productMapper; @Transactional(rollbackFor = Exception.class) public Long add(ProductDTO dto) { Product product = new Product(); BeanUtils.copyProperties(dto, product); productMapper.insert(product); return product.getId(); } }

@Transactional(rollbackFor = Exception.class)这个写法要注意:默认情况下@Transactional只对RuntimeException回滚,如果你方法里抛的是受检异常(比如IOException),事务不会回滚。产线上统一加上rollbackFor = Exception.class是规范做法。

这个案例麻雀虽小五脏俱全。跑通它,你对 SpringBoot 的 Controller 层、Service 层、Mapper 层的完整调用链,以及配置、事务、参数校验这些基础能力就有了体感。

我个人的建议是:学 SpringBoot 不要沉迷于背面试题,最好的学习路径是搭一个又一个小项目,从"能跑"到"跑得对"再到"跑得稳",前两步靠模仿,最后一步靠踩坑。SpringBoot 的自动装配、starter 机制、配置体系这些概念,等你实际被坑过一次、排查过一次,比看十篇原理文章都记得牢。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询