5.依赖注入深入学习

2 阅读6分钟

一、依赖注入回顾

1. 什么是依赖注入(DI)?

依赖注入:一种设计模式,将对象的创建和依赖管理交给容器,而不是在对象内部创建依赖。


2. DI 的三种方式

方式说明示例
构造器注入通过构造函数注入public UserService(UserRepository repo) {...}
Setter 注入通过 Setter 方法注入public void setUserRepository(UserRepository repo) {...}
字段注入直接通过 @Autowired 注入字段@Autowired private UserRepository repo;

3. 推荐使用:构造器注入

@Service
public class UserService {
    
    private final UserRepository userRepository;
    
    // ✅ 推荐:构造器注入
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

为什么推荐构造器注入?

优点说明
不可变性使用 final 确保依赖不可变
便于测试可以轻松使用 Mock 对象
必填依赖确保对象创建时依赖已注入
IDE 支持IDE 可以检测到缺失的依赖

二、Bean 的作用域

1. 常用作用域

作用域说明生命周期
Singleton(默认)整个应用只创建一个实例应用启动时创建,应用关闭时销毁
Prototype每次请求都创建新实例每次使用时创建
Request每个 HTTP 请求创建一个实例请求开始时创建,请求结束时销毁
Session每个 HTTP Session 创建一个实例Session 创建时创建,Session 过期时销毁

2. Singleton(单例)

默认作用域:

@Component  // 默认是 Singleton
public class UserService {
    // ...
}

显式指定:

@Component
@Scope("singleton")
public class UserService {
    // ...
}

特点:

  • 整个应用只创建一个实例
  • 线程安全需要自己保证
  • 适用于无状态服务

3. Prototype(原型)

示例:

@Component
@Scope("prototype")
public class UserService {
    // ...
}

特点:

  • 每次使用都创建新实例
  • 线程安全
  • 适用于有状态对象

测试:

@SpringBootTest
public class ScopeTest {
    
    @Autowired
    private ApplicationContext context;
    
    @Test
    public void testPrototypeScope() {
        UserService user1 = context.getBean(UserService.class);
        UserService user2 = context.getBean(UserService.class);
        
        // Prototype:每次都是新实例
        assertNotSame(user1, user2);
    }
}

4. Request 和 Session

注意: 需要启用 Web 环境。

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedBean {
    // 每个 HTTP 请求创建一个实例
}
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionScopedBean {
    // 每个 HTTP Session 创建一个实例
}

三、Bean 的生命周期

1. Bean 生命周期流程

1. 实例化(Instance)
   ↓
2. 属性赋值(Populate Properties)
   ↓
3. BeanNameAware
   ↓
4. BeanFactoryAware
   ↓
5. ApplicationContextAware
   ↓
6. BeanPostProcessor(前置处理)
   ↓
7. InitializingBean / @PostConstruct
   ↓
8. 自定义 init-method
   ↓
9. Bean 准备就绪(Ready for Use)
   ↓
10. @PreDestroy
    ↓
11. DisposableBean
    ↓
12. 自定义 destroy-method
    ↓
13. Bean 销毁(Destroyed)

2. 生命周期回调

@PostConstruct(初始化)

@Component
public class UserService {
    
    private UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @PostConstruct
    public void init() {
        System.out.println("UserService 初始化完成");
        // 初始化逻辑
    }
}

@PreDestroy(销毁)

@Component
public class UserService {
    
    @PreDestroy
    public void destroy() {
        System.out.println("UserService 即将销毁");
        // 清理逻辑
    }
}

3. 完整生命周期示例

@Component
public class LifecycleBean implements BeanNameAware, ApplicationContextAware, 
                                       InitializingBean, DisposableBean {
    
    private String beanName;
    private ApplicationContext applicationContext;
    
    // 1. 构造函数
    public LifecycleBean() {
        System.out.println("1. 构造函数执行");
    }
    
    // 2. BeanNameAware
    @Override
    public void setBeanName(String name) {
        this.beanName = name;
        System.out.println("2. BeanNameAware: " + name);
    }
    
    // 3. ApplicationContextAware
    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.applicationContext = context;
        System.out.println("3. ApplicationContextAware");
    }
    
    // 4. @PostConstruct
    @PostConstruct
    public void postConstruct() {
        System.out.println("4. @PostConstruct");
    }
    
    // 5. InitializingBean
    @Override
    public void afterPropertiesSet() {
        System.out.println("5. InitializingBean.afterPropertiesSet");
    }
    
    // 6. 使用 Bean
    public void doSomething() {
        System.out.println("6. Bean 正在使用中");
    }
    
    // 7. @PreDestroy
    @PreDestroy
    public void preDestroy() {
        System.out.println("7. @PreDestroy");
    }
    
    // 8. DisposableBean
    @Override
    public void destroy() {
        System.out.println("8. DisposableBean.destroy");
    }
}

四、自动装配(@Autowired)

1. @Autowired 的使用位置

位置说明示例
构造器推荐方式@Autowired public Service(Repo repo)
Setter可选依赖@Autowired public void setRepo(Repo repo)
字段不推荐@Autowired private Repo repo;

2. @RequiredArgsConstructor(推荐)

使用 Lombok 简化构造器注入:

@Service
@RequiredArgsConstructor  // 自动生成构造器
public class UserService {
    
    private final UserRepository userRepository;  // final 字段自动注入
    
    private EmailService emailService;  // 非 final 字段不注入
}

等价于:

@Service
public class UserService {
    
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

五、多个 Bean 的处理

1. 问题:多个 Bean 匹配

场景:

// 两个实现类
@Repository
public class MysqlUserRepository implements UserRepository {
    // ...
}

@Repository
public class PostgresUserRepository implements UserRepository {
    // ...
}

// 注入时出错
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;  // ❌ 不知道注入哪个
}

错误:

No qualifying bean of type 'UserRepository' available: 
expected single matching bean but found 2

2. 解决方案 1:@Primary

@Repository
@Primary  // 优先注入这个 Bean
public class MysqlUserRepository implements UserRepository {
    // ...
}

3. 解决方案 2:@Qualifier

@Service
public class UserService {
    
    @Autowired
    @Qualifier("mysqlUserRepository")  // 指定 Bean 名称
    private UserRepository userRepository;
}

4. 解决方案 3:参数名匹配

@Service
public class UserService {
    
    private final UserRepository mysqlUserRepository;  // 参数名与 Bean 名匹配
    
    public UserService(UserRepository mysqlUserRepository) {
        this.mysqlUserRepository = mysqlUserRepository;
    }
}

六、条件注解

1. 常用条件注解

注解作用示例
@ConditionalOnClassclasspath 中存在指定类时生效@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingClassclasspath 中不存在指定类时生效@ConditionalOnMissingClass(RedisTemplate.class)
@ConditionalOnBean容器中存在指定 Bean 时生效@ConditionalOnBean(DataSource.class)
@ConditionalOnMissingBean容器中不存在指定 Bean 时生效@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty配置文件中满足指定属性时生效@ConditionalOnProperty(name="app.cache.enabled")

2. 条件注解示例

@ConditionalOnProperty

@Configuration
@ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        // 只有 app.cache.enabled=true 时才创建 Bean
    }
}

application.properties:

app.cache.enabled=true

@ConditionalOnMissingBean

@Configuration
public class DataSourceConfig {
    
    @Bean
    @ConditionalOnMissingBean(DataSource.class)  // 用户没有自定义时才创建
    public DataSource dataSource() {
        // 创建默认 DataSource
    }
}

用户自定义:

@Configuration
public class CustomDataSourceConfig {
    
    @Bean
    public DataSource customDataSource() {
        // 用户自定义 DataSource(优先级更高)
        return new CustomDataSource();
    }
}

七、Bean 的创建时机

1. 懒加载(Lazy Loading)

默认行为:

  • Singleton Bean 在应用启动时创建

懒加载:

  • Bean 在第一次使用时创建

2. @Lazy 注解

@Component
@Lazy  // 懒加载
public class HeavyService {
    public HeavyService() {
        System.out.println("HeavyService 创建(可能耗时较长)");
    }
}

注入懒加载 Bean:

@Service
public class UserService {
    
    @Autowired
    @Lazy  // 懒加载
    private HeavyService heavyService;
}

3. 测试懒加载

@SpringBootTest
public class LazyLoadTest {
    
    @Autowired
    private ApplicationContext context;
    
    @Test
    public void testLazyLoad() {
        System.out.println("应用启动");
        
        // 第一次使用时才创建
        HeavyService heavyService = context.getBean(HeavyService.class);
        heavyService.doSomething();
        
        // 第二次使用:使用缓存的实例
        HeavyService heavyService2 = context.getBean(HeavyService.class);
        assertSame(heavyService, heavyService2);
    }
}

八、循环依赖

1. 什么是循环依赖?

定义: 两个或多个 Bean 互相依赖,导致无法创建。

示例:

@Service
public class ServiceA {
    
    @Autowired
    private ServiceB serviceB;
}

@Service
public class ServiceB {
    
    @Autowired
    private ServiceA serviceA;  // 循环依赖
}

2. Spring 如何处理循环依赖?

Singleton 作用域:

  • Spring 使用三级缓存解决 Singleton 循环依赖
  • 构造器注入无法解决循环依赖

Prototype 作用域:

  • 不解决循环依赖,会抛出异常

3. 解决循环依赖的方法

方法 1:使用 Setter 注入(推荐)

@Service
public class ServiceA {
    
    private ServiceB serviceB;
    
    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

@Service
public class ServiceB {
    
    private ServiceA serviceA;
    
    @Autowired
    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

方法 2:使用 @Lazy

@Service
public class ServiceA {
    
    @Autowired
    @Lazy  // 懒加载,打破循环依赖
    private ServiceB serviceB;
}

@Service
public class ServiceB {
    
    @Autowired
    private ServiceA serviceA;
}

方法 3:重构代码(最佳)

问题根源: 设计不合理,应该拆分职责。

重构:

// 公共接口
public interface CommonService {
    void doCommonThing();
}

@Service
public class ServiceA implements CommonService {
    
    @Autowired
    private CommonService commonService;
    
    @Override
    public void doCommonThing() {
        // ...
    }
}

@Service
public class ServiceB implements CommonService {
    
    @Autowired
    private CommonService commonService;
    
    @Override
    public void doCommonThing() {
        // ...
    }
}

九、最佳实践

1. 依赖注入方式选择

场景推荐方式
必填依赖构造器注入 + final
可选依赖Setter 注入
字段注入不推荐使用

2. Bean 作用域选择

场景推荐作用域
无状态服务Singleton(默认)
有状态对象Prototype
请求级别数据Request
Session 级别数据Session

3. 命名规范

类型规范示例
Bean 名称类名首字母小写userService
配置类*ConfigDataSourceConfig

十、总结

概念说明
依赖注入将对象的创建交给容器
构造器注入推荐方式,使用 final
Bean 作用域Singleton、Prototype、Request、Session
生命周期@PostConstruct、@PreDestroy
自动装配@Autowired、@Primary、@Qualifier
条件注解根据条件创建 Bean
懒加载首次使用时创建
循环依赖使用 Setter 注入或 @Lazy