Spring——Spring相关类原理与实战

8 阅读11分钟

摘要

本文深入探讨了 Spring 框架中 InitializingBean 接口的原理与实战应用,该接口是 Spring 提供的一个生命周期接口,用于在 Bean 属性注入完成后执行初始化逻辑。文章详细介绍了接口定义、作用、典型使用场景,并与其他相关概念如 @PostConstruct 和 DisposableBean 进行了对比。

1. InitializingBean原理与实战

InitializingBean 是 Spring 提供的一个生命周期接口,其核心作用是让 Bean 在所有属性注入完成后执行一些初始化逻辑。

1.1. 🧩 接口定义

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

如果你的类实现了这个接口,Spring 会在完成依赖注入后自动调用 afterPropertiesSet() 方法。

1.2. ✅ InitializingBean作用

功能说明
生命周期钩子在 Bean 初始化(注入属性)后执行自定义逻辑
替代 @PostConstruct是一种“接口驱动”的初始化方式
适用于框架开发明确初始化点,便于统一管理

1.3. ✅ 典型使用场景(实战)

1.3.1. 初始化资源、连接等

@Component
public class RedisClient implements InitializingBean {

    private JedisPool jedisPool;

    @Value("${redis.host}")
    private String host;

    @Value("${redis.port}")
    private int port;

    @Override
    public void afterPropertiesSet() throws Exception {
        jedisPool = new JedisPool(host, port);
        System.out.println("Redis 连接池初始化完成");
    }

    public Jedis getConnection() {
        return jedisPool.getResource();
    }
}

📌 实战说明:配置注入完成后,通过 afterPropertiesSet() 初始化 Redis 连接池。

1.3.2. 校验依赖是否注入完整

@Component
public class SomeService implements InitializingBean {

    @Autowired
    private SomeDependency dependency;

    @Override
    public void afterPropertiesSet() {
        if (dependency == null) {
            throw new IllegalStateException("SomeDependency 没有被注入!");
        }
    }
}

1.3.3. 在框架中设置静态访问点(如 XXL-Job 中)

@Override
public void afterPropertiesSet() throws Exception {
    adminConfig = this; // 设置静态访问入口
    xxlJobScheduler = new XxlJobScheduler();
    xxlJobScheduler.init(); // 初始化调度器
}

📌 实战说明:用于启动调度器、加载配置等初始化逻辑,适合中间件开发。

1.4. ✅ InitializingBean总结

  • InitializingBean 提供了一种 标准化的 Bean 初始化入口
  • 推荐用于:底层框架、组件、工具类初始化中,如连接池、调度器、配置校验。
  • 对于业务逻辑,更推荐用 @PostConstruct 注解,简洁易读。

2. @PostConstruct注解原理与实战

@PostConstruct 是 Java 提供的标准注解(来自 javax.annotationjakarta.annotation),在 Spring 中用于定义 Bean 的初始化方法,当 Bean 完成依赖注入之后自动执行。

2.1. ✅ @PostConstruct 是什么?

@PostConstruct
public void init() {
    // 初始化逻辑
}
  • 当 Spring 完成对 Bean 的创建与依赖注入后,会自动调用标注了 @PostConstruct 的方法。
  • 方法 只能有一个、无参数、返回值为 void

2.2. ✅ @PostConstruct与Spring生命周期的关系

Spring 创建一个 Bean 的完整流程如下:

构造函数 -> 依赖注入 -> @PostConstruct -> InitializingBean.afterPropertiesSet() -> Bean 初始化完成

因此,@PostConstruct 执行在 Bean 初始化的早期阶段,非常适合做以下操作:

用途说明
资源初始化建立连接池、定时器、缓存等
参数检查校验注入的配置是否符合要求
注册操作向注册中心、事件总线等注册自己
静态赋值注入静态成员变量或构造辅助工具

2.3. ✅ 实战使用示例

2.3.1. 缓存初始化

@Component
public class DictCache {

    private final DictService dictService;

    private Map<String, String> dictCache = new HashMap<>();

    public DictCache(DictService dictService) {
        this.dictService = dictService;
    }

    @PostConstruct
    public void loadCache() {
        dictCache = dictService.loadAllDict();
        System.out.println("字典缓存加载完毕!");
    }

    public String get(String key) {
        return dictCache.get(key);
    }
}

2.3.2. 参数校验或默认值设置

@Component
public class SmsSender {

    @Value("${sms.gateway.url}")
    private String gatewayUrl;

    @PostConstruct
    public void validate() {
        if (gatewayUrl == null || gatewayUrl.isEmpty()) {
            throw new IllegalArgumentException("短信网关地址不能为空!");
        }
    }
}

2.3.3. 设置静态工具类

@Component
public class SpringContextUtil {

    @Autowired
    private ApplicationContext applicationContext;

    public static ApplicationContext context;

    @PostConstruct
    public void init() {
        SpringContextUtil.context = this.applicationContext;
    }

    public static <T> T getBean(Class<T> clazz) {
        return context.getBean(clazz);
    }
}

2.4. ✅ @PostConstruct与InitializingBean 的对比

特性@PostConstructInitializingBean
来源Java 标准注解Spring 接口
侵入性低(无需实现接口)高(必须实现接口)
可读性更清晰、简洁稍显繁琐
推荐✅ 推荐❌ 一般不推荐用于业务代码
场景普通业务初始化框架、组件级初始化

3. @PostConstructInitializingBean区别?

缓存初始化 场景中,@PostConstructInitializingBean 都能完成初始化逻辑,但两者有以下核心区别

  • @PostConstruct 是注解驱动的初始化方式,简洁、解耦、推荐用于业务代码
  • InitializingBean 是接口驱动的初始化方式,侵入性强,推荐用于框架/中间件级别代码

3.1. ✅ 功能与使用上的对比

对比项@PostConstructInitializingBean
本质Java 标准注解(JSR-250)Spring 生命周期接口
编码方式在方法上加注解实现接口、重写方法
方法名任意(如 init()固定:afterPropertiesSet()
方法个数可以多个类中各定义一个一个类只能有一个 afterPropertiesSet()
侵入性✅ 低:无须继承或实现接口❌ 高:必须实现接口
可读性✅ 强:一看注解就知道是初始化❌ 差:容易被忽视
可测试性✅ 好(不会影响类结构)❌ 接口实现影响结构
场景适用业务代码、工具类、缓存加载框架设计、底层组件、可复用模块
推荐程度✅ 更推荐使用⚠️ 更适合框架代码

3.2. ✅ 实际缓存初始化场景对比

3.2.1. 用@PostConstruct 初始化缓存

@Component
public class DictCache {

    @Autowired
    private DictService dictService;

    private Map<String, String> cache = new HashMap<>();

    @PostConstruct
    public void initCache() {
        cache = dictService.loadAllDict();
    }
}

优点

  • 简洁明了
  • 不影响类结构
  • 易于测试和维护

3.2.2. 用InitializingBean 初始化缓存

@Component
public class DictCache implements InitializingBean {

    @Autowired
    private DictService dictService;

    private Map<String, String> cache = new HashMap<>();

    @Override
    public void afterPropertiesSet() throws Exception {
        cache = dictService.loadAllDict();
    }
}

⚠️ 缺点

  • 需要实现接口,影响类设计
  • 方法名固定,不够语义化
  • 可读性差:不能一眼看出这是初始化方法

3.3. ✅ 实际开发推荐

场景推荐方式
普通业务初始化(如缓存、参数)@PostConstruct
框架开发 / 高通用组件(如连接池、调度器)InitializingBean
初始化逻辑中涉及多个阶段、多个顺序❌都不推荐,建议用 @Bean(initMethod = "...") 或配置类

4. DisposableBean原理与实战

DisposableBean 是 Spring 提供的一个生命周期接口,用于在 Bean 被销毁时执行清理逻辑,常用于释放资源、关闭连接、销毁线程池等操作。DisposableBean 接口的 destroy() 方法会在 Spring 容器销毁该 Bean 之前被调用,用于完成资源释放或清理工作。

4.1. 🧩 接口定义

public interface DisposableBean {
    void destroy() throws Exception;
}

Spring 会在 容器关闭前 调用实现类的 destroy() 方法。

4.2. ✅ 释放线程池

@Component
public class TaskManager implements DisposableBean {

    private ExecutorService threadPool = Executors.newFixedThreadPool(10);

    public void submit(Runnable task) {
        threadPool.submit(task);
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("正在关闭线程池...");
        threadPool.shutdown();
    }
}

📝 当 Spring 容器关闭时,destroy() 方法被自动调用,安全关闭线程池。

4.3. ✅ 关闭数据库连接或 Redis 客户端

java


复制编辑
@Component
public class RedisClientWrapper implements DisposableBean {

    private JedisPool jedisPool = new JedisPool("localhost");

    public Jedis getClient() {
        return jedisPool.getResource();
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("关闭 Redis 连接池");
        jedisPool.close();
    }
}

4.4. ✅ 结合 InitializingBean

有时你会在一个类中同时使用初始化和销毁逻辑:

@Component
public class MyService implements InitializingBean, DisposableBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("初始化资源");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("清理资源");
    }
}

4.5. 🛠️ 替代方案:@PreDestroy

Spring 也支持使用 @PreDestroy 注解实现销毁逻辑:

@PreDestroy
public void cleanup() {
    System.out.println("释放资源 @PreDestroy");
}

4.5.1. 🔍 区别总结:

对比项DisposableBean@PreDestroy
来源Spring 接口Java 标准注解(JSR-250)
入侵性高(实现接口)低(注解)
推荐程度❌ 较低✅ 更推荐
方法限制固定 destroy()方法名可自定义
结构清晰❌ 不利于多继承✅ 解耦、灵活

4.6. ✅ 总结

项目说明
接口名DisposableBean
方法destroy()
调用时机Bean 被销毁前(如容器关闭)
常见用途释放线程池、关闭连接池、清理缓存等
推荐替代使用 @PreDestroy更灵活、非侵入式
使用场景资源管理类、任务调度类、连接池等生命周期敏感组件

如果你在业务中需要在 Spring 应用关闭时清理资源,推荐使用 @PreDestroy;如果你正在写一个框架、组件,或者需要精确控制 Bean 生命周期,则可以使用 DisposableBean

5. SmartInitializingSingleton原理与实战

SmartInitializingSingleton 是 Spring 框架中的一个 高级扩展接口,用于在 所有单例 Bean 完全初始化完成后 执行某段逻辑。它常用于 组件启动逻辑的延迟执行,比如像 XXL-JOB 的执行器、缓存预热、动态注册。

5.1. SmartInitializingSingleton 是什么?

public interface SmartInitializingSingleton {
    void afterSingletonsInstantiated();
}
  • Spring 会在所有单例 Bean 实例化并依赖注入完成后(即 Spring 容器即将就绪时)调用这个接口的实现。
  • InitializingBean.afterPropertiesSet() 不同,它只调用一次,且是在所有单例准备完毕后调用。

5.2. 与InitializingBean区别?

项目InitializingBeanSmartInitializingSingleton
调用时机单个 Bean 初始化后所有单例 Bean 初始化后
触发方式每个 Bean 单独触发容器完成所有初始化后统一触发
应用场景Bean 自己初始化逻辑全局依赖逻辑(如依赖其他 Bean 已完成)
替代方式@PostConstruct无注解替代方式(需接口或 SmartLifecycle
实现目标Bean 自己初始化等待所有 Bean 初始化再统一处理

5.3. 实战场景和原理解析

5.3.1. 🌟 场景一:自动注册任务(如 XXL-JOB)

@Component
public class XxlJobSpringExecutor extends XxlJobExecutor
implements SmartInitializingSingleton {
    @Override
    public void afterSingletonsInstantiated() {
        initJobHandlerMethodRepository(applicationContext); // 扫描 @XxlJob
        super.start(); // 启动注册
    }
}

📌 原因:只有当所有 Bean 都加载完,才能扫描所有 Bean 中的方法。

5.3.2. 🌟 场景二:事件总线初始化(如 Guava EventBus)

@Component
public class EventBusRegister implements SmartInitializingSingleton {

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public void afterSingletonsInstantiated() {
        // 注册所有 @EventListener 的 Bean
        applicationContext.getBeansWithAnnotation(MyEventListener.class)
        .forEach((name, bean) -> EventBusFactory.getBus().register(bean));
    }
}

5.3.3. 🌟 场景三:注册中心上报(如 Dubbo、Nacos)

@Component
public class ServiceRegistry implements SmartInitializingSingleton {

    @Autowired
    private ServiceMetaInfo localService;

    @Override
    public void afterSingletonsInstantiated() {
        // 等所有 Bean 初始化完后统一注册服务信息
        registryCenter.register(localService);
    }
}

5.4. 源码调用顺序

Spring 在 DefaultListableBeanFactory.preInstantiateSingletons() 方法中:

if (bean instanceof SmartInitializingSingleton) {
    ((SmartInitializingSingleton) bean).afterSingletonsInstantiated();
}

该方法会在所有单例 Bean 初始化完成后,逐个调用实现了 SmartInitializingSingleton 的类的回调方法

5.5. ✅ 总结对比

项目InitializingBeanSmartInitializingSingleton
接口方法afterPropertiesSet()afterSingletonsInstantiated()
调用时机Bean 实例化 + 属性注入后所有单例实例化完成后
调用频率每个 Bean 调一次所有单例后只调一次(全局)
适用场景Bean 自己的初始化需要获取所有 Bean 后统一处理
替代注解@PostConstruct无明确替代,最末生命周期回调

5.6. ✅ 实战建议

需求建议实现
初始化当前 Bean 用@PostConstructInitializingBean
需要等待所有单例准备好后统一处理(如注册、扫描)SmartInitializingSingleton

如果你在项目中需要:

  • 自动发现所有某类 Bean(如任务、监听器、规则处理器)
  • 注册第三方服务(Nacos、Dubbo、Job)
  • 需要确保 Spring 容器启动完成再初始化逻辑

推荐用 SmartInitializingSingleton ,这是一种更稳妥的方式。

6. ApplicationContextAware原理与实战

ApplicationContextAware 是 Spring 提供的一个回调接口,它的作用是让一个 Bean 在被初始化时能够获取到 Spring 容器的 ApplicationContext 对象,从而可以在非 Spring 管理的类中手动获取 Bean、访问上下文等。

6.1. 核心作用

ApplicationContextAware 接口只有一个方法:

void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

当 Spring 容器创建一个实现了该接口的 Bean 时,会自动调用这个方法,把 ApplicationContext 注入进来。常用于以下场景:

  • 在非 Spring 管理的类中获取 Bean
  • 实现工具类,让项目中可以静态方式获取 Bean
  • 执行一些与容器上下文相关的初始化逻辑

6.2. ✅ 常见使用场景汇总

场景描述
1. 静态工具类在静态方法中获取 Spring Bean 实例
2. 非 Spring 管理类在第三方库/线程/监听器/过滤器中获取 Spring Bean
3. 策略模式注册表动态扫描所有策略实现类,统一注册到 Map 中
4. 多数据源初始化在初始化阶段读取 Spring 上下文中的配置信息
5. 动态代理或 BeanFactory 后置处理配合 BeanPostProcessor、FactoryBean 等使用
6. 动态 Bean 装载/卸载获取 BeanDefinitionRegistry /ConfigurableApplicationContext

6.3. ✅ 实战场景示例一:策略模式自动注册

6.3.1. 背景:

在风控系统中,你有多个处理策略,比如:

  • 身份校验策略
  • 信用评分策略
  • 黑名单策略

你想把这些策略都注册到一个 Map 中,用 key 做选择。

6.3.2. 策略接口和实现类

public interface RiskStrategy {
    String getStrategyCode(); // 每个策略的唯一标识
    void execute();
}
@Component
public class IdVerificationStrategy implements RiskStrategy {
    @Override
    public String getStrategyCode() {
        return "ID_VERIFICATION";
    }

    @Override
    public void execute() {
        System.out.println("执行身份校验策略");
    }
}
@Component
public class CreditScoreStrategy implements RiskStrategy {
    @Override
    public String getStrategyCode() {
        return "CREDIT_SCORE";
    }

    @Override
    public void execute() {
        System.out.println("执行信用评分策略");
    }
}

6.3.3. 策略工厂类(实现 ApplicationContextAware)

@Component
public class RiskStrategyFactory implements ApplicationContextAware {

    private static final Map<String, RiskStrategy> STRATEGY_MAP = new HashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, RiskStrategy> beans = applicationContext.getBeansOfType(RiskStrategy.class);
        for (RiskStrategy strategy : beans.values()) {
            STRATEGY_MAP.put(strategy.getStrategyCode(), strategy);
        }
    }

    public static RiskStrategy getStrategy(String code) {
        return STRATEGY_MAP.get(code);
    }
}

6.3.4. 使用方式

public class RiskEngine {

    public void process(String code) {
        RiskStrategy strategy = RiskStrategyFactory.getStrategy(code);
        if (strategy != null) {
            strategy.execute();
        } else {
            System.out.println("无匹配策略");
        }
    }
}

6.4. ✅ 实战意义

  • 你不需要手动维护策略 Bean
  • Spring 启动时自动扫描并注册
  • 新增策略只需新增实现类即可,无需修改工厂类

6.5. ✅ 总结:框架项目中的价值

场景ApplicationContextAware 的价值
风控/策略引擎自动收集所有策略实现类,实现高扩展性
插件化开发注册所有插件、规则、流程节点等
通用工具类在任何位置动态获取 Bean
多数据源获取并操作 Spring 容器的元数据或 Bean 定义
微服务网关获取全局拦截器、过滤器、自定义组件等

博文参考