一、先把这事儿整明白:Spring到底在干嘛
Spring 框架说白了就是个”大管家”。你写一堆 Java 类,告诉 Spring “我是谁、我需要啥”,Spring 帮你把东西都组装起来,对象的生命周期、依赖关系、事务管理、AOP 切面——它全给你管了。
但问题是,早期的 Spring 配置太恶心了。XML 文件写得比代码还长,改个配置得重启服务器,排查问题全靠猜。程序员们苦 XML 久矣,于是 Spring 团队推出了 Spring Boot,把配置能自动化的全部自动化,能零配置的绝不让你写一行。
你刚学 Spring 卡在配置上?正常,这是必经之路。咱们今天就把这块掰开揉碎了讲清楚。
二、自动装配(Auto-Configuration)是咋回事
2.1 核心思想:条件化的配置类
Spring Boot 的自动装配,本质就是一堆条件化的配置类。你告诉 Spring Boot “我要用某个功能”,它自己判断”行,我帮你配上”,前提是你 classpath 里有对应的依赖。
比如你加了 spring-boot-starter-web,Spring Boot 就自动帮你:
- 配置嵌入式 Tomcat
- 配置 DispatcherServlet
- 配置 Jackson 序列化
- 配置错误页面处理
你啥都不用管,直接写代码就行。
2.2 背后的魔法:@SpringBootApplication
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
@SpringBootApplication 是个组合注解,它干了这几件事:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration // ← 自动装配的核心
@ComponentScan(exclude = {
"org.springframework.boot.autoconfigure.AutoConfigurationPackages"
})
public @interface SpringBootApplication {
// ...
}
重点看 @EnableAutoConfiguration,这个注解告诉 Spring Boot “喂,请帮我自动配置”。
2.3 EnableAutoConfiguration 咋工作的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) // ← 关键类
public @interface EnableAutoConfiguration {
// ...
}
@Import(AutoConfigurationImportSelector.class) 这行是真正的入口。AutoConfigurationImportSelector 会做这几件事:
- 读取
META-INF/spring.factories或新的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件 - 加载所有候选的自动配置类
- 根据
@Conditional注解过滤,只保留当前环境需要的配置
2.4 一个具体例子:DataSource 自动配置
假设你加了 spring-boot-starter-jdbc 依赖,并且 application.properties 里配了:
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
Spring Boot 会自动创建 DataSource Bean:
@Configuration
@ConditionalOnClass({ DataSource.class, DataSourceProperties.class })
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
public class DataSourceAutoConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConditionalOnMissingBean
public DataSource dataSource(DataSourceProperties properties) {
return createDataSource(properties, properties.determineTestDatabaseType());
}
}
注意这些 @Conditional 注解:
@ConditionalOnClass:classpath 里有DataSource类才生效@ConditionalOnMissingBean:用户没自己定义DataSourceBean 才生效@ConditionalOnProperty:属性文件里有对应配置才生效
这三个条件同时满足,Spring Boot 才会帮你自动配置 DataSource。
三、@Conditional 家族全家福
Spring Boot 提供了一整套条件化注解,你要逐个搞清楚:
| 注解 | 作用 |
|---|---|
@ConditionalOnClass |
classpath 有指定类才加载 |
@ConditionalOnMissingClass |
classpath 没有指定类才加载 |
@ConditionalOnBean |
容器里有指定 Bean 才加载 |
@ConditionalOnMissingBean |
容器里没有指定 Bean 才加载 |
@ConditionalOnProperty |
配置属性有指定值才加载 |
@ConditionalOnWebApplication |
Web 应用才加载 |
@ConditionalOnNotWebApplication |
非 Web 应用才加载 |
@ConditionalOnExpression |
SpEL 表达式为 true 才加载 |
实战例子:自定义自动配置
假设你写了一个工具类 RedisTemplate,想让它自动配置:
第一步:创建配置类
@Configuration
@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnProperty(prefix = "my.redis", name = "enabled", havingValue = "true")
@AutoConfigureAfter({ RedisAutoConfiguration.class })
public class MyRedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
第二步:注册自动配置
在 src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中添加:
com.example.MyRedisAutoConfiguration
第三步:使用方只需要加依赖和配置
my.redis.enabled=true
然后直接注入使用:
@RestController
public class UserController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
User user = (User) redisTemplate.opsForValue().get("user:" + id);
return user;
}
}
四、依赖注入的几种姿势
Spring 支持好几种注入方式,各有优劣,你得知道啥时候用啥:
4.1 构造器注入(推荐)
@Component
public class OrderService {
private final UserRepository userRepository;
private final PaymentService paymentService;
private final NotificationService notificationService;
// 构造器注入,参数越多越能看出依赖关系
@Autowired
public OrderService(UserRepository userRepository,
PaymentService paymentService,
NotificationService notificationService) {
this.userRepository = userRepository;
this.paymentService = paymentService;
this.notificationService = notificationService;
}
public Order createOrder(Long userId, List<Long> productIds) {
// ...
}
}
优点:
- 依赖关系一目了然
- 可以声明
final,不可变 - 单元测试友好,可以直接 new 对象传 mock
- 防止循环依赖(构造器注入在循环依赖时会直接报错)
4.2 Setter 注入
@Component
public class OrderService {
@Autowired
private UserRepository userRepository;
@Autowired
private PaymentService paymentService;
public Order createOrder(Long userId, List<Long> productIds) {
// ...
}
}
适用场景:
- 依赖可选时(用
@Autowired(required = false)) - 需要重新设置依赖时
- legacy 代码维护
缺点:
- 依赖关系不直观
- 可能出现部分依赖未注入的情况
4.3 字段注入(争议最大)
@Component
public class OrderService {
@Autowired
private UserRepository userRepository;
@Autowired
private PaymentService paymentService;
}
争议点:
- 优点:代码简洁,看起来清爽
- 缺点:依赖关系隐藏,单元测试麻烦,容易写出循环依赖
我的建议: 新项目用构造器注入,老项目维护用字段注入,别纠结。
五、Bean 生命周期全解析
理解 Bean 生命周期,排查问题会快很多:
1. 实例化 Bean(Instantiation)
↓
2. 填充属性(Populate Bean)
↓
3. 初始化前回调(BeanPostProcessor.postProcessBeforeInitialization)
↓
4. 执行初始化方法
- @PostConstruct 注解的方法
- InitializingBean.afterPropertiesSet()
- 自定义 init-method
↓
5. 初始化后回调(BeanPostProcessor.postProcessAfterInitialization)
↓
6. Bean 可用
↓
7. 容器关闭
↓
8. 销毁前回调
- @PreDestroy 注解的方法
- DisposableBean.destroy()
- 自定义 destroy-method
实战例子:监听 Bean 生命周期
@Component
public class MyBeanLifecycleListener {
@PostConstruct
public void init() {
System.out.println("Bean 初始化完成");
}
@PreDestroy
public void cleanup() {
System.out.println("Bean 销毁前清理资源");
}
}
或者实现接口:
@Component
public class MyBean implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("初始化完成");
}
@Override
public void destroy() throws Exception {
System.out.println("销毁完成");
}
}
六、常见坑位与避坑指南
6.1 坑一:循环依赖
现象:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'xxx': Requested bean is currently in creation:
Is there an unresolvable circular reference?
原因: A 依赖 B,B 依赖 A,Spring 不知道先创建谁。
例子:
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB; // ← 循环依赖
}
@Component
public class ServiceB {
@Autowired
private ServiceA serviceA; // ← 循环依赖
}
解决方案:
- 重构代码,打破循环依赖(推荐)
- 用
@Lazy延迟注入 - 用 Setter 注入代替构造器注入
- 把共同依赖抽出来形成第三方 Bean
@Component
public class ServiceA {
@Autowired
@Lazy // ← 延迟加载,打破循环
private ServiceB serviceB;
}
6.2 坑二:Bean 作用域混乱
现象: 共享 Bean 被修改后影响其他请求
例子:
@Component // 默认是 singleton,所有请求共享一个实例
public class RequestContext {
private String userId;
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserId() {
return this.userId;
}
}
问题: 多线程访问时,userId 会被覆盖。
解决方案:
@Scope("prototype") // 每次请求创建新实例
@Component
public class RequestContext {
private String userId;
// ...
}
或者用 ThreadLocal:
@Component
@Scope("singleton")
public class RequestContext {
private static final ThreadLocal<String> USER_ID = new ThreadLocal<>();
public void setUserId(String userId) {
USER_ID.set(userId);
}
public String getUserId() {
return USER_ID.get();
}
public void clear() {
USER_ID.remove();
}
}
6.3 坑三:@Autowired 注入为 null
现象: 手动 new 出来的对象,里面的 @Autowired 字段全是 null
例子:
public class MyHelper {
@Autowired
private SomeService someService; // ← null!
public void doSomething() {
someService.doXxx(); // NPE!
}
}
// 调用方
MyHelper helper = new MyHelper(); // ← 问题在这
helper.doSomething();
原因: Spring 只管理自己创建的 Bean。你手动 new 的对象,Spring 不知道,不会帮你注入依赖。
解决方案:
- 让 Spring 创建这个对象(加
@Component注解) - 从容器里获取 Bean,而不是手动 new
@Component
public class MyHelper {
@Autowired
private SomeService someService;
public void doSomething() {
someService.doXxx(); // 正常
}
}
// 正确用法
@Autowired
private MyHelper helper; // Spring 创建并注入依赖
helper.doSomething();
6.4 坑四:事务注解不生效
现象: 加了 @Transactional 但事务没生效
原因 1:方法不是 public
@Transactional // ← 不生效!只有 public 方法才会被代理
private void doSomething() {
// ...
}
原因 2:同类方法调用
@Service
public class MyService {
public void methodA() {
methodB(); // ← 同类调用,绕过了代理,事务不生效
}
@Transactional
public void methodB() {
// ...
}
}
解决方案:
@Service
public class MyService {
@Autowired
private MyService self; // ← 注入自己
public void methodA() {
self.methodB(); // ← 通过代理调用,事务生效
}
@Transactional
public void methodB() {
// ...
}
}
原因 3:异常被捕获未抛出
@Transactional
public void doSomething() {
try {
// 数据库操作
} catch (Exception e) {
// 异常被吞掉,事务不回滚
}
}
解决方案:
@Transactional(rollbackFor = Exception.class)
public void doSomething() {
try {
// 数据库操作
} catch (Exception e) {
throw new RuntimeException(e); // ← 重新抛出,事务回滚
}
}
6.5 坑五:多数据源配置冲突
现象: 项目里有多个 DataSource,Spring 不知道用哪个
解决方案: 用 @Primary 指定默认数据源
@Configuration
public class DataSourceConfig {
@Bean
@Primary // ← 默认数据源
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
public DataSource primaryDataSource() {
return primaryDataSourceProperties()
.initializeDataSourceBuilder()
.build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSourceProperties secondaryDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public DataSource secondaryDataSource() {
return secondaryDataSourceProperties()
.initializeDataSourceBuilder()
.build();
}
}
使用的时候指定用哪个:
@Repository
public class UserMapper {
// 默认用 primary
}
@Repository
@Qualifier("secondaryDataSource")
public class OrderMapper {
// 用 secondary
}
七、自动配置的优先级与排除
7.1 排除某个自动配置
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
RedisAutoConfiguration.class
})
public class MyApplication {
// ...
}
或者在配置文件里:
spring.autoconfigure.exclude=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration
7.2 条件排除
@SpringBootApplication(excludeName = {
"com.example.MyCustomAutoConfiguration"
})
7.3 自定义自动配置优先级
@Configuration
@AutoConfigureBefore({ DataSourceAutoConfiguration.class }) // 在 DataSource 之前
@AutoConfigureAfter({ MyOtherAutoConfiguration.class }) // 在 MyOther 之后
public class MyAutoConfiguration {
// ...
}
八、调试自动配置的秘诀
8.1 开启调试日志
logging.level.org.springframework.boot.autoconfigure=DEBUG
启动时会打印所有自动配置类的评估结果:
Automatic configuration report:
Positive matches:
-----------------
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.core.JdbcOperations' (OnClassCondition)
- @ConditionalOnProperty (spring.datasource.url) matched (OnPropertyCondition)
Negative matches:
-----------------
RedisAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'org.springframework.data.redis.connection.RedisConnectionFactory' (OnClassCondition)
8.2 查看 Bean 注册情况
”`java @Autowired private
