【WebWiki】04 · Spring Boot
02 Spring Boot
WebWiki · 后端框架篇
面向已掌握 Java/Spring 基础、需深入理解 Spring Boot 机制的工程师。重点在 IoC、AOP、自动配置、事务传播等"知其所以然"的内容,代码示例带中文注释。
1. 核心概念
IoC(控制反转):对象的创建与装配交给容器管理。DI(依赖注入) 是实现手段。传统方式里 new 出的对象自己管理依赖,IoC 把"控制权"反转给 Spring 容器,容器按配置把依赖"注入"进来,降低耦合、便于测试与替换实现。
@Service // 声明为 Bean,默认单例
@RequiredArgsConstructor // Lombok 构造器注入(推荐,利于单测)
public class OrderService {
private final OrderRepository orderRepository; // 容器在启动时注入
private final PaymentClient paymentClient;
}
| 注入方式 | 建议 |
|---|---|
| 构造器注入 | 推荐:不可变(final)、可暴露循环依赖问题、方便单测 |
| Setter 注入 | 可选:适合可选依赖、需要重新赋值 |
字段注入 @Autowired |
尽量避免:无法声明 final、单测难 mock、隐藏依赖 |
1.1 Bean 的作用域
| scope | 说明 |
|---|---|
singleton(默认) |
容器级单例,整个应用共享一个实例 |
prototype |
每次获取 getBean 或注入都新建 |
request / session |
Web 环境,每次请求/会话一个 |
application |
ServletContext 级单例 |
单例的线程安全:单例 Bean 被所有请求共享,若内部有可变成员变量就会有并发问题。无状态 Service(只调方法不改字段)是安全的;需要保存请求态请用方法局部变量或
ThreadLocal。
1.2 Bean 生命周期(面试高频)
容器启动到 Bean 可用经历:
- 实例化:反射调用构造器
new出对象(此时依赖还是 null); - 属性填充(populate):注入
@Autowired/@Value等依赖; - Aware 回调:
BeanNameAware/BeanFactoryAware/ApplicationContextAware注入容器信息; - 初始化前:
@PostConstruct注解方法 /BeanPostProcessor.postProcessBeforeInitialization; - 初始化:
InitializingBean.afterPropertiesSet; - 初始化后:
BeanPostProcessor.postProcessAfterInitialization(AOP 代理就在这里生成); - 就绪:Bean 可被使用;
- 销毁:容器关闭时
@PreDestroy/DisposableBean.destroy。
@Component
public class LifecycleDemo implements InitializingBean, DisposableBean {
@PostConstruct
void init() { System.out.println("1. @PostConstruct"); } // 早于 afterPropertiesSet
@Override
public void afterPropertiesSet() { System.out.println("2. 属性填完"); }
@PreDestroy
void cleanup() { System.out.println("3. 容器关闭清理"); }
}
BeanPostProcessor 是 Spring 扩展的核心钩子:AOP、事务(
@Transactional)、@Async全部靠它在初始化后织入代理。理解这一点就懂了"为什么@Transactional必须外部调用才生效"——代理只在 Bean 完全初始化后才套上去。
2. 高频注解速查
| 注解 | 作用 |
|---|---|
@RestController |
@Controller + @ResponseBody,返回 JSON |
@RequestMapping / @GetMapping / @PostMapping |
路由映射 |
@PathVariable / @RequestParam / @RequestBody |
取参:路径 / query / JSON 体 |
@Service @Repository @Component |
分层声明 Bean |
@Configuration @Bean |
Java 配置类(方法返回的对象注册为 Bean) |
@Value / @ConfigurationProperties |
读配置(前者单值,后者批量绑定前缀) |
@Transactional |
声明式事务 |
@Async @Scheduled |
异步方法 / 定时任务 |
@ControllerAdvice @ExceptionHandler |
全局异常处理 |
2.1 容易被忽略的注解细节
@Configuration的proxyBeanMethods:true(默认)时@Bean方法互相调用返回同一单例(通过 CGLIB 代理);设false(Lite 模式)则每次调用都是新实例,启动更快,适合纯注册 Bean 的配置类。@Import:导入其他配置类 /ImportSelector/ImportBeanDefinitionRegistrar,是自动配置的底层手段之一。@Conditional系列:@ConditionalOnClass/@ConditionalOnMissingBean/@ConditionalOnProperty—— 自动配置按条件装配的开关。@Lazy:延迟初始化(首次使用时才创建),可打破部分循环依赖、加速启动。@Order/@Priority:控制同类型 Bean 的注入顺序或@Bean候选优先级。
3. 自动配置原理(面试与排错必备)
3.1 全流程
@SpringBootApplication包含@EnableAutoConfiguration;- 启动时读取所有依赖 jar 中
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(2.7 前为spring.factories); - 每个自动配置类用
@ConditionalOnClass / @ConditionalOnMissingBean条件装配; - 覆盖默认行为的正道:自己定义同类型 Bean(
@ConditionalOnMissingBean会退让),或改配置项。
3.2 以 DataSource 自动配置为例
spring-boot-autoconfigure.jar
└── META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports
→ 含 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
DataSourceAutoConfiguration 大致逻辑:
@AutoConfiguration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) // 类路径有这些类才生效
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 关键:用户自己定义了 DataSource 就跳过自动配置
@ConditionalOnProperty(prefix = "spring.datasource", name = "url")
public DataSource dataSource(DataSourceProperties props) {
return props.initializeDataSourceBuilder().build(); // 按 yaml 里的 url/用户名/密码建连接池
}
}
3.3 SPI 机制
Spring Boot 的自动配置本质是 Java SPI 思想的增强:JDK 原生 SPI 读 META-INF/services,按接口全限定名找实现类;Spring 用 SpringFactoriesLoader(现 AutoConfiguration.imports)批量加载,并通过 @Conditional 做按需过滤,比原生 SPI 更灵活、不会无脑加载全部实现。
排错口诀:自动配置"不生效"十有八九是:① 依赖没引入(
@ConditionalOnClass不满足);② 你自定义的 Bean 类型/名字冲突;③ 配置项前缀写错。用spring-boot-actuator的/actuator/conditions看"哪些自动配置生效/未生效及原因",比猜快得多。
4. 配置文件
spring:
datasource:
url: jdbc:mysql://localhost:3306/mall?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: ${DB_PWD} # 环境变量占位,密码不进代码库
redis:
host: 127.0.0.0
port: 6379
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
# 多环境:application-dev.yml / application-prod.yml
spring:
profiles:
active: dev
类型安全的配置绑定:
@Data
@ConfigurationProperties(prefix = "biz.order") // 绑定前缀,IDE 可提示
public class OrderProperties {
private int expireMinutes = 15; // 未支付订单超时(默认值兜底)
private int maxItemsPerOrder = 99;
}
@Configuration
@EnableConfigurationProperties(OrderProperties.class) // 让容器管理该配置类
public class BizConfig {
@Bean
public OrderExpireChecker checker(OrderProperties props) {
return new OrderExpireChecker(props.getExpireMinutes());
}
}
配置优先级(由高到低摘录):命令行参数
--xxx> 环境变量 >application-{profile}.yml>application.yml> 默认配置。排错时不确定哪个值生效,开debug: true看configprops报告。
5. 统一返回与全局异常
// 统一响应体
public record R<T>(int code, String msg, T data) {
public static <T> R<T> ok(T data) { return new R<>(0, "success", data); }
public static R<Void> fail(int code, String msg) { return new R<>(code, msg, null); }
}
@RestControllerAdvice // = @ControllerAdvice + @ResponseBody,拦截所有 @RestController
public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public R<Void> biz(BizException e) { return R.fail(e.getCode(), e.getMessage()); }
@ExceptionHandler(MethodArgumentNotValidException.class) // @Validated 校验失败
public R<Void> valid(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors().stream()
.map(f -> f.getField() + ": " + f.getDefaultMessage())
.collect(Collectors.joining("; "));
return R.fail(400, msg);
}
@ExceptionHandler(Exception.class) // 兜底,避免把堆栈透给用户
public R<Void> unknown(Exception e) {
log.error("系统异常", e);
return R.fail(500, "系统繁忙,请稍后重试");
}
}
参数校验:
public record CreateOrderReq(
@NotNull Long skuId,
@Min(1) @Max(99) Integer count, // 数量 1~99
@NotBlank @Pattern(regexp = "^1\\d{10}$") String phone) {} // 手机号 11 位
@PostMapping("/orders")
public R<OrderVO> create(@RequestBody @Validated CreateOrderReq req) { ... } // @Validated 触发校验
顺序注意:
@ExceptionHandler匹配最具体异常优先。把Exception兜底放最后,否则会"吞掉"更精确的 BizException。
6. 事务(@Transactional)深入
6.1 使用要点
- 加在公共方法上,从外部调用才生效(AOP 代理限制);
- 默认只回滚
RuntimeException,受检异常需@Transactional(rollbackFor = Exception.class); - 事务内不要做 RPC/HTTP 调用与大循环(拉长连接占用);
self方法内部调用事务方法无效——拆到另一个 Bean 或用TransactionTemplate。
@Transactional(rollbackFor = Exception.class)
public void createOrder(CreateOrderReq req) {
orderRepository.insert(order);
stockService.deduct(req.skuId(), req.count()); // 同库本地事务一起回滚
// 注意:发 MQ/调支付等外部动作应放在事务提交后(TransactionSynchronization 或编码式拆分)
}
6.2 七种传播行为(Propagation)
| 传播行为 | 行为 | 场景 |
|---|---|---|
REQUIRED(默认) |
有事务则加入,没有则新建 | 绝大多数业务方法 |
SUPPORTS |
有则加入,无则以非事务执行 | 查询方法可选事务 |
MANDATORY |
必须在已有事务中,否则抛异常 | 强制调用方开启事务 |
REQUIRES_NEW |
挂起当前事务,新建独立事务 | 日志/审计,主事务回滚不影响它 |
NOT_SUPPORTED |
挂起当前事务,非事务执行 | 不需事务的批量处理 |
NEVER |
必须在非事务中,有事务则抛异常 | 禁止事务上下文 |
NESTED |
在当前事务内开保存点嵌套子事务 | 子操作可部分回滚(依赖 JDBC Savepoint,并非所有数据库支持) |
// 示例:主事务回滚,审计日志仍要保留 —— 用 REQUIRES_NEW 隔离
@Transactional
public void place(OrderReq req) {
orderRepo.save(order); // 主事务
auditService.log(req); // 该方法 @Transactional(REQUIRES_NEW),独立提交
if (req.getCount() <= 0) throw new BizException("数量非法"); // 主事务回滚,但日志已落库
}
REQUIRES_NEW vs NESTED:
REQUIRES_NEW是真正两个物理事务,外层回滚不影响内层;NESTED是同一事务内的保存点,外层回滚会把内层一起回滚(内层可单独回滚到保存点)。
6.3 隔离级别与只读优化
@Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true) // 只读提示让数据库/ORM 优化
public List<Order> query(Long userId) { ... }
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ_UNCOMMITTED | 可能 | 可能 | 可能 |
| READ_COMMITTED(常用) | 防止 | 可能 | 可能 |
| REPEATABLE_READ(MySQL 默认) | 防止 | 防止 | 基本防止(MVCC + 间隙锁) |
| SERIALIZABLE | 防止 | 防止 | 防止(串行,性能最差) |
7. AOP 切面实战与原理
7.1 动态代理两种方式
Spring AOP 默认按目标对象类型选代理:
- JDK 动态代理:目标实现接口时用,基于
InvocationHandler,生成的代理类也实现这些接口; - CGLIB:目标无接口 / 类代理时用,继承目标类重写方法(因此 final 类/方法无法被代理)。
@Aspect @Component
public class LogAspect {
// 切点:匹配 com.mall 下所有 service 的所有方法
@Pointcut("execution(* com.mall..service..*(..))")
public void biz() {}
@Around("biz()")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
return pjp.proceed(); // 执行原方法
} finally {
log.info("{} cost {}ms", pjp.getSignature(), System.currentTimeMillis() - start);
}
}
}
7.2 通知类型与执行顺序
@Around → @Before → 原方法 → @AfterReturning/@AfterThrowing → @After → @Around 收尾。
典型应用:接口日志、权限校验、分布式锁注解、方法限流、防重提交。
原理一句话:Spring 在 Bean 初始化后(
postProcessAfterInitialization)扫描@Aspect,为有切点匹配的 Bean 生成代理。调用方法时先进入代理→执行拦截链(通知)→最后proceed到目标方法。所以"同类内调用事务/切面失效"就是因为绕过了代理、直接this.方法()。
7.3 自定义注解 + AOP 实战(分布式锁)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
String key(); // SpEL 表达式,如 "#orderId"
long expire() default 30; // 锁过期秒数
}
@Aspect @Component
public class LockAspect {
@Around("@annotation(lock)")
public Object around(ProceedingJoinPoint pjp, DistributedLock lock) throws Throwable {
String realKey = parseSpel(lock.key(), pjp); // 解析 SpEL 得到真实 key
if (!redisLock.tryLock(realKey, lock.expire())) {
throw new BizException("操作过于频繁");
}
try { return pjp.proceed(); }
finally { redisLock.unlock(realKey); }
}
}
8. 常用整合(starter 接入步骤)
| 组件 | starter | 要点 |
|---|---|---|
| MyBatis-Plus | mybatis-plus-boot-starter |
BaseMapper CRUD、分页插件、逻辑删除、自动填充 |
| Spring Data JPA | spring-boot-starter-data-jpa |
方法名派生查询、审计字段 |
| Redis | spring-boot-starter-data-redis |
RedisTemplate + Jackson 序列化配置 |
| Web | spring-boot-starter-web |
内嵌 Tomcat,HTTPS/压缩在 yaml 配 |
| 定时 | spring-boot-starter-quartz 或 xxl-job |
集群部署用 xxl-job 避免重复执行 |
| 文档 | springdoc-openapi |
/swagger-ui 自动接口文档 |
8.1 RedisTemplate 序列化坑
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> t = new RedisTemplate<>();
t.setConnectionFactory(factory);
// 默认 JDK 序列化会存乱码/二进制;生产用 JSON(Jackson)
Jackson2JsonRedisSerializer<Object> json = new Jackson2JsonRedisSerializer<>(Object.class);
t.setKeySerializer(RedisSerializer.string()); // key 用 String,可读
t.setValueSerializer(json); // value 用 JSON
t.setHashKeySerializer(RedisSerializer.string());
t.setHashValueSerializer(json);
return t;
}
8.2 MyBatis-Plus 分页
// 注册分页插件(否则 page 不生效,只是普通查询)
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor i = new MybatisPlusInterceptor();
i.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return i;
}
}
// 使用
Page<Order> page = orderService.page(new Page<>(pageNum, pageSize),
new LambdaQueryWrapper<Order>()
.eq(Order::getUserId, userId)
.ge(Order::getCreateTime, since)
.orderByDesc(Order::getId));
8.3 springdoc-openapi(Swagger 替代)
springdoc:
swagger-ui:
path: /swagger-ui.html # 访问路径
api-docs:
path: /v3/api-docs
安全提示:生产环境务必用 Spring Security 或网关把
/swagger-ui、/actuator收敛,禁止公网暴露。
9. 工程结构与启动
mall-parent
├── mall-common # 通用工具、统一返回、异常
├── mall-api # Feign 接口 + DTO(供消费方依赖)
├── mall-user-service # 用户服务(可独立部署)
├── mall-order-service # 订单服务
└── mall-gateway # 网关
打包与运行:
mvn clean package -DskipTests
java -jar -Xms1g -Xmx1g -Dspring.profiles.active=prod mall-order.jar
健康检查与指标:引入 spring-boot-starter-actuator,暴露 /actuator/health, /actuator/prometheus 给 K8s 探针与 Prometheus 抓取。
management:
endpoints:
web:
exposure:
include: health,info,prometheus,conditions # conditions 用来查自动配置
endpoint:
health:
show-details: when-authorized
10. 常见排错清单
| 现象 | 原因 / 排查 |
|---|---|
启动报 Consider defining a bean of type 'X' |
没加 @Service/@Component,或主启动类扫描包不在其子包下(@SpringBootApplication 只扫当前及子包) |
@Transactional 不回滚 |
方法非 public、同类内调用(this.)、抛的是受检异常未配 rollbackFor |
| 循环依赖报错 | 构造器注入互相依赖;可用 @Lazy 破环或改为 setter 注入 |
| 配置项读不到 | 前缀拼错、忘记 @EnableConfigurationProperties、被更高优先级源覆盖 |
| 接口 404 | 路径/方法不匹配、Controller 没被扫描、@RestController 误写成 @Controller |
| 返回中文乱码 | produces = "application/json;charset=UTF-8" 或全局配置 MessageConverter 编码 |
11. 小结
- IoC 把对象生命周期交给容器,Bean 经历"实例化→注入→初始化→就绪→销毁",后置处理器是 AOP/事务的织入点;
- 自动配置 =
@EnableAutoConfiguration扫描AutoConfiguration.imports+@Conditional按需装配,覆盖默认靠自定义同型 Bean; - AOP 底层是 JDK 动态代理(有接口)或 CGLIB(无接口),同类调用绕过代理导致切面/事务失效;
- 事务掌握七种传播行为与隔离级别,外部调用 + 正确
rollbackFor是铁律; - 整合 starter 注意序列化(Redis)、分页插件(MP)、文档/监控的生产收敛。
至此 Java 基础与 Spring Boot 框架机制已串成线;前端部分见《01-前端Vue3》《02-Vue整合实操》。

浙公网安备 33010602011771号