一、先把这事儿整明白: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 会做这几件事:

  1. 读取 META-INF/spring.factories 或新的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件
  2. 加载所有候选的自动配置类
  3. 根据 @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:用户没自己定义 DataSource Bean 才生效
  • @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;  // ← 循环依赖
}

解决方案:

  1. 重构代码,打破循环依赖(推荐)
  2. @Lazy 延迟注入
  3. 用 Setter 注入代替构造器注入
  4. 把共同依赖抽出来形成第三方 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 不知道,不会帮你注入依赖。

解决方案:

  1. 让 Spring 创建这个对象(加 @Component 注解)
  2. 从容器里获取 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