未命名文档
侧边栏壁纸
  • 累计撰写 36 篇文章
  • 累计收到 1 条评论

未命名文档

ASN__
2026-06-02 / 0 评论 / 3 阅读 / 正在检测是否收录...
注意: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(原型)

  • 行为:每次获取(@AutowiredgetBean)都会创建一个全新的实例。
  • 生命周期差异

    • 容器负责创建 → 依赖注入 → 调用 @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 自动调用");
    }
}
最佳实践:如果没有特殊需求,优先使用 singletonprototype 适合那些有状态且不宜共享的对象,但要记得手动管理资源释放。

二、注入 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 执行顺序

初始化流程

  1. 调用构造方法创建实例
  2. 依赖注入(@Autowired@Resource 等)
  3. 调用 @PostConstruct 方法
  4. (如果实现了 InitializingBean)调用 afterPropertiesSet()
  5. (如果配置了 init-method)调用自定义方法

销毁流程(容器正常关闭时):

  1. 调用 @PreDestroy 方法
  2. (如果实现了 DisposableBean)调用 destroy()
  3. (如果配置了 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 特别提醒

  1. 注解包@PostConstruct@PreDestroy@Resource 使用 javax.annotation,无需添加任何额外依赖。
  2. Spring Boot 自动关闭:Spring Boot 应用默认注册 JVM 关闭钩子,只要你正常停止应用(Ctrl+Ckill 不带 -9System.exit()),@PreDestroy 就会被调用。
  3. prototype 的陷阱:如果你的 Bean 是 prototype 且需要释放资源,请考虑改用 singleton,或自行设计清理方法(如实现 DisposableBean 并手动调用)。
  4. @Bean 方法的默认名称:方法名即 Bean 名称,注意避免重名(同一类型不同方法名)。

0

评论

博主关闭了所有页面的评论