注意:Java 8 环境下,javax.annotation(@PostConstruct、@PreDestroy、@Resource)是 JDK 自带的,无需额外添加依赖。Spring Boot 2.7 使用 Spring Framework 5.x,完美支持 JSR-250 规范。
一、Bean 的作用域(Scope)
Spring 中的 Bean 默认是 单例(Singleton),即整个容器内只有一个实例。也可以通过 @Scope 指定为 原型(Prototype),每次获取都创建新实例。
1.1 Singleton(单例)—— 默认
- 创建时机:容器启动时(除非设置为
@Lazy)。 - 销毁时机:容器正常关闭时,会调用
@PreDestroy方法。 - 获取结果:每次注入或
getBean()返回同一个对象。
@Component // 默认 singleton
public class MailService {
// ...
}1.2 Prototype(原型)
- 行为:每次获取(
@Autowired或getBean)都会创建一个全新的实例。 生命周期差异:
- 容器负责创建 → 依赖注入 → 调用
@PostConstruct。 - 容器不负责销毁:即使你在原型 Bean 中写了
@PreDestroy,Spring 不会自动调用它。需要你自己写代码清理(例如手动调用销毁方法)。
- 容器负责创建 → 依赖注入 → 调用
- 声明方式:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 或 @Scope("prototype")
public class MailSession {
@PostConstruct
public void init() {
System.out.println("原型 Bean 初始化,每次都会调用");
}
@PreDestroy
public void destroy() {
System.out.println("原型 Bean 销毁 — 不会被 Spring 自动调用");
}
}最佳实践:如果没有特殊需求,优先使用singleton。prototype适合那些有状态且不宜共享的对象,但要记得手动管理资源释放。
二、注入 List —— 自动收集同一类型的所有 Bean
当你需要在一个地方使用某个接口的 所有实现类 时,可以直接注入 List<T>,Spring 会自动把该接口的所有 Bean 放进列表。
示例:多个 Validator 实现
public interface Validator {
void validate(String email, String password, String name);
}
@Component // 自动被收集
public class EmailValidator implements Validator {
@Override
public void validate(String email, String password, String name) {
// email 校验逻辑
}
}
@Component
public class PasswordValidator implements Validator {
@Override
public void validate(String email, String password, String name) {
// password 校验逻辑
}
}
@Component
public class NameValidator implements Validator {
@Override
public void validate(String email, String password, String name) {
// name 校验逻辑
}
}注入 List
@Component
public class Validators {
@Autowired
private List<Validator> validators; // Spring 自动注入所有 Validator 实现
public void validate(String email, String password, String name) {
for (Validator v : validators) {
v.validate(email, password, name);
}
}
}控制顺序 —— @Order
默认顺序不确定,可以用 @Order 注解指定(数字越小优先级越高)。
@Component
@Order(1)
public class EmailValidator implements Validator { ... }
@Component
@Order(2)
public class PasswordValidator implements Validator { ... }
@Component
@Order(3)
public class NameValidator implements Validator { ... }注入的List<Validator>会按照@Order的值升序排列。
三、可选注入 —— @Autowired(required = false)
默认情况下,如果某个依赖的 Bean 不存在,Spring 会抛出 NoSuchBeanDefinitionException。如果你允许该依赖为 null,可以设置 required = false。
@Component
public class MailService {
@Autowired(required = false)
private ZoneId zoneId = ZoneId.systemDefault(); // 找不到 ZoneId 时使用默认值
}典型场景:某些配置是可选的(如时区、外部服务),若有定义则使用,否则退回到默认值。
四、创建第三方 Bean —— @Bean 方法
对于不在我们包内的类(例如 JDK 的 ZoneId、第三方库的 DataSource),无法用 @Component 扫描。此时需要在 @Configuration 类中编写一个 工厂方法,并标记 @Bean。
@Configuration
@ComponentScan
public class AppConfig {
@Bean
public ZoneId zoneId() {
return ZoneId.of("Z");
}
@Bean("customRestTemplate") // 指定 Bean 名称
public RestTemplate myRestTemplate() {
return new RestTemplate();
}
}- Spring 保证该方法 只被调用一次(默认单例)。
- 方法名默认作为 Bean 的名称,也可用
@Bean("name")显式命名。 - 可以依赖其他 Bean:方法参数会自动注入。
@Bean
public DataSource dataSource() {
return HikariDataSource(...);
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) { // 自动注入上面创建的 DataSource
return new JdbcTemplate(dataSource);
}本质上,@Bean方法就是一种 工厂方法,比实现FactoryBean接口更简洁。
五、Bean 的初始化和销毁 —— @PostConstruct 和 @PreDestroy
5.1 为什么需要
- 初始化:在依赖注入完成后,执行一些启动逻辑(打开连接、启动后台线程、校验配置)。
- 销毁:在容器关闭前,释放资源(关闭连接池、停止线程、清理临时文件)。
5.2 使用方式(JSR-250,Java 8 原生支持)
@Component
public class MailService {
@Autowired(required = false)
private ZoneId zoneId = ZoneId.systemDefault();
@PostConstruct
public void init() {
System.out.println("初始化:依赖已注入,zoneId = " + zoneId);
}
@PreDestroy
public void shutdown() {
System.out.println("销毁前:释放资源(如关闭连接池)");
}
}5.3 执行顺序
初始化流程:
- 调用构造方法创建实例
- 依赖注入(
@Autowired、@Resource等) - 调用
@PostConstruct方法 - (如果实现了
InitializingBean)调用afterPropertiesSet() - (如果配置了
init-method)调用自定义方法
销毁流程(容器正常关闭时):
- 调用
@PreDestroy方法 - (如果实现了
DisposableBean)调用destroy() - (如果配置了
destroy-method)调用自定义方法
5.4 重要注意事项
- 方法必须 无参数,返回值任意(通常
void)。 - 方法名任意,不要求
init/destroy。 prototype作用域的 Bean,Spring 只调用@PostConstruct,不会自动调用@PreDestroy。如果原型 Bean 需要清理,你必须自己设计清理逻辑(例如提供cleanup()方法并手动调用)。
5.5 “全自动”的条件
在 Spring Boot 2.7(Java 8)中,只要满足以下条件,@PostConstruct 和 @PreDestroy 就是 全自动 的:
- Bean 是
singleton作用域(默认)。 - Spring Boot 应用正常关闭(按
Ctrl+C、收到SIGTERM、程序正常结束)。Spring Boot 会自动注册 JVM 关闭钩子,触发容器关闭,从而调用@PreDestroy。 - 不要使用
kill -9强行杀死进程(那样 JVM 直接退出,不会执行关闭钩子)。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
// 按 Ctrl+C 时,会看到 @PreDestroy 的输出
}
}六、处理多个同类型 Bean —— 别名、限定符、主次
6.1 问题描述
在 @Configuration 中定义多个同类型的 @Bean:
@Configuration
public class AppConfig {
@Bean
ZoneId createZoneOfZ() { return ZoneId.of("Z"); }
@Bean
ZoneId createZoneOfUTC8() { return ZoneId.of("UTC+08:00"); }
}直接注入 ZoneId 会报错:NoUniqueBeanDefinitionException。
6.2 解决方案
(1) 为每个 Bean 起一个名字(别名)
@Bean("z")
ZoneId createZoneOfZ() { ... }
@Bean
@Qualifier("utc8")
ZoneId createZoneOfUTC8() { ... }(2) 注入时用 @Qualifier 指定名称
@Component
public class MailService {
@Autowired
@Qualifier("z") // 精确指定名称为 "z" 的那个 ZoneId
private ZoneId zoneId;
}或者使用 JSR-250 的 @Resource(name = "z"):
@Component
public class MailService {
@Resource(name = "z")
private ZoneId zoneId;
}(3) 使用 @Primary 指定主要 Bean
当没有使用 @Qualifier 时,Spring 会注入标记 @Primary 的那个 Bean。
@Bean
@Primary // 主要候选
@Qualifier("z")
ZoneId createZoneOfZ() { ... }
@Bean
@Qualifier("utc8")
ZoneId createZoneOfUTC8() { ... }注入时:
@Component
public class MailService {
@Autowired // 无 @Qualifier,默认注入 @Primary 的那个(即 "z")
private ZoneId zoneId;
}6.3 经典应用:多数据源
@Configuration
public class DataSourceConfig {
@Bean
@Primary
DataSource masterDataSource() { ... }
@Bean
@Qualifier("slave")
DataSource slaveDataSource() { ... }
}- 默认注入主数据源(如 DAO 层无需特殊指定)。
- 需要从库时:
@Qualifier("slave")精确注入。
七、FactoryBean —— 工厂模式创建 Bean(了解即可)
7.1 是什么
FactoryBean 是 Spring 提供的工厂接口,允许你自定义 Bean 的创建逻辑。Spring 会先创建 FactoryBean 实例,再调用其 getObject() 方法返回真正的 Bean。
@Component
public class ZoneIdFactoryBean implements FactoryBean<ZoneId> {
private String zone = "Z";
@Override
public ZoneId getObject() throws Exception {
return ZoneId.of(zone);
}
@Override
public Class<?> getObjectType() {
return ZoneId.class;
}
}- 注入/获取时拿到的是
getObject()返回的ZoneId。 - 如果需要获取
FactoryBean本身,可以在名称前加&:context.getBean("&zoneIdFactoryBean")。
7.2 现状
在 Spring Boot 2.7 中,@Bean 方法已经足够简洁强大,完全可以替代 FactoryBean 的大部分场景。除非你要编写非常复杂的创建逻辑,或需要与旧框架集成(如 MyBatis 的 MapperFactoryBean),否则推荐直接使用 @Bean 方法。
八、综合对比与最佳实践
| 功能 | 推荐方案 | 关键点 |
|---|---|---|
| 普通 Bean | @Component | 默认 singleton |
| 原型 Bean | @Scope("prototype") | 每次新建,容器不负责 @PreDestroy |
| 收集所有实现 | @Autowired List<Interface> | 配合 @Order 控制顺序 |
| 可选依赖 | @Autowired(required=false) | 避免异常,可设默认值 |
| 第三方 Bean | @Bean 方法 | 在 @Configuration 中定义 |
| 初始化逻辑 | @PostConstruct | 依赖注入后自动执行 |
| 销毁逻辑 | @PreDestroy | 容器关闭时自动执行(singleton 有效) |
| 同类型多个 Bean | @Qualifier + 命名 | 精确注入;@Primary 设置默认 |
| 复杂工厂 | @Bean 方法 | 一般不用 FactoryBean |
九、Java 8 + Spring Boot 2.7 特别提醒
- 注解包:
@PostConstruct、@PreDestroy、@Resource使用javax.annotation,无需添加任何额外依赖。 - Spring Boot 自动关闭:Spring Boot 应用默认注册 JVM 关闭钩子,只要你正常停止应用(
Ctrl+C、kill不带-9、System.exit()),@PreDestroy就会被调用。 - prototype 的陷阱:如果你的 Bean 是
prototype且需要释放资源,请考虑改用singleton,或自行设计清理方法(如实现DisposableBean并手动调用)。 @Bean方法的默认名称:方法名即 Bean 名称,注意避免重名(同一类型不同方法名)。
评论