源码深入解析spring 的初始化方法 initMethod (标有注解的@postConstruct的方法)--极度细致!

一.说在前面(结论思考)

@postConstruct 所标注的方法 内部是靠的spring提供的两个后置处理器(InitDestroyAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor)共同 协调分布处理完成的。 这2点也是网上绝大部人没讲明白的,很多人都只是说到一个,其实我之前看源码也是以为一个,结果,后面由于xxx 我发现了是两个!!还有就是他们各种如何协调作用完成工作的?是各自又是在 bean 的生命周期的哪一个阶段`起的作用?

二.弄清InitDestroyAnnotationBeanPostProcessor CommonAnnotationBeanPostProcessor的关系

这两个后置处理器还是父子关系:InitDestroyAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor
在这里插入图片描述
说明:MergedBeanDefinitionPostProcessor 这个后置处理器接口中
1、postProcessMergedBeanDefinition
缓存bean的注入信息的后置处理器,仅仅是缓存或者干脆叫做查找更加合适,没有完成注入,注入是另外一个后置处理器的作用

1.CommonAnnotationBeanPostProcessor

a.提供的方法 postProcessMergedBeanDefinition 可以看作处理解析 注解 到缓存的入口
–> 其时它处理 @Resouce 的字段,解析bean中包含此注解的字段封装对象到缓存 ,以供 requeBean 在 pupulateBean 阶段 取出来用到完成 依赖注入
(和我们这篇讨论的有区别!!,但引出个问题,那对应的注解@Autowired 用在字段或方法时候,又是在哪个处理器 解析到缓存?它应该类似 @resouce 都有个解析到缓存,先缓存再使用 的过程!)

–> 所以可以看到它自身 维护了一个 缓存injectionMetadataCache<String,InjectionMetadata>
这个 map 的value是包含了 每一个bean类所依赖属性 字段(可能多个) 是被 标注由 @Resouce 的 封装为 InjectionMetadata对象,key为beanName
–> 他里面有个方法是 autowireResouce(Beanfactory,LookupElenment,string) 完成对 依赖字段的处理
具体的 postProcessMergedBeanDefinition 被调用及作用见下图很重要!:
在这里插入图片描述

2.InitDestroyAnnotationBeanPostProcessor

其实只是解析了 bean上面有标注 @PostConstruct、@ PreDestroy 的方法们,
–> 所以我们同可以在它里面维护了1个缓存lifecycleMetadaCache<Class, LifecycleMetada> ,这个 map就是在解析的过程中完成 --> value是包含了 每一个bean类(只能1个,还不能静态源码看到!) 是被 标注由 @PostConstruct、@ PreDestroy的 先各自对应封装的方法集合,后两个集合方法共同封装为 InjectionMetadata对象,key为beanClass

– 刚上面提到的–>解析具体是在何时哪个阶段完成的呢?
–> 其实是在上面的子类 CommonAnnotationBeanPostProcessor 的 postProcessMergedBeanDefinition被调用时候执行!

3.总结关系

  1. 注解解析为缓存: @PostConstruct、@ PreDestroy 及 @Rerosuce 的解析是发生在–》bean生命周期的实例化后,pupulate 设置属性前, 通过 【apply_Merged_BeanDefinitionPostProcessors】利用 合并后置处理器接口方法调用到 CommonAnnotationBeanPostProcessor 他的postProcessMergedBeanDefinition逻辑完成注解(方法,字段)解析放到了其和父类的两个缓存map里面,具体使用在下面
  2. 注解方法调用
    (我们暂时不关注注解字段@Rerosuce解析的缓存使用–> 因为他的使用是属于依赖注入相关,是在 pupulateBean 阶段
    参考另外一篇单独讲依赖注入:https://blog.csdn.net/qq_39965727/article/details/104672027

注解方法如–》 @PostConstruct 注解的方法被调用反射处理 A 中方法是在 整个init 初始化阶段的before阶段即:【apply_BeanPostProcessors_BeforeInitialization】获取到所有的处理器们,其中执行到 InitDestroyAnnotationBeanPostProcessor. 方法postProcessBeforeInitialization(注意其实都是获取处理器是 CommonAnnotationBeanPostProcessor,InitDestroyAnnotationBeanPostProcessor 都没有bd的 见下面注意说明!),这里面会发生“先取后用”` 的操作,见下图:

在这里插入图片描述

3.注意:

其实2.中所说的获取到处理器执行到 InitDestroyAnnotationBeanPostProcessor 不是直接到的!!,这里有一个小点!!即我们经常看到的获取所有的处理器们 getBeanPostProcessors()这个方法其实返回的是 bean工厂里面单独所维护 的一个后置处理器的一个集合 List<BeanPostProcessor> beanPostProcessors,而其实InitDestroyAnnotationBeanPostProcessor 这个后置处理器是没有在这个集合中的!!但是他的所继承的子类CommonAnnotationBeanPostProcessor是在的 ,即我们刚说的调用都是掉的父类处理器
CommonAnnotationBeanPostProcessor只不过 这个后置处理器根本没有重写 父类处理器InitDestroyAnnotationBeanPostProcessor的方法 postProcessBeforeInitialization,所以调用链是直接进入 到InitDestroyAnnotationBeanPostProcessor里面的!,其实这样是对的,否则就执行两次了!
其实看bdMap(图)也知 InitDestroyAnnotationBeanPostProcessor 居然根本就是没有生成bd的,之前我一直以为这个处理器也是在spring容器中的,害我想复杂了很多,哎!,也是,它的功能都是被子类所继承下来的且子类 CommonAnnotationBeanPostProcessor 是在spring容器里,即后面的操作它作为父类的功能都是能被子类所用到,所以spirng没有必要将他放入容器,还免得出现执行混乱!–》 我觉得这个也算是理解spirng的一个点!精简准确性!
在这里插入图片描述

插一句(可能插的有点长哈):
(这里其实可以解释到为啥jdk版本不同,到这@postConstuct 和 @Resouce 可能不生效!的原因,–> 因为 CommonAnnotationBeanPostProcessor 这个后置处理器是来处理了 a. @PostConstruct、@ PreDestroy + b.@Resource 两大类注解的实现, 为啥说是两个大类呢? 因为,a. 类注解是用在方法上面表示init相关, 而 b.类 @Resouce 是处理字段上面自动注入的类似 @Autowired,(从类图方法也可以看到) 在这里就显得有点另类了,我当时也非常奇怪为啥它明明是来处理对象的依赖字段属性注入相关的,为啥它要放到这个后置处理器中处理,而非是像@Autowired 是由 AutowireAnnotationBeanPostProcessor 来处理的,–> 后面我的理解是: @Resouce 和初始化的注解 都不是spring框架自带的,而 @Autowired 却是,这也就是说, @Resouce 等注解因为它不属于spring的一部分即它可能是因为jdk的版本不同是加载不到类的,如jdk9,11由于xx就是加载不上的!,而我们的 @Autowired 是非常极度常用且是spring自己所带的所以一定能保证它的解析加载依赖属性正常,即 AutowireAnnotationBeanPostProcessor 是可以被放入到spirng容器里,重点来了!如果我们讲有可能出问题的 @Resouce 等注解的解析功能也放到 AutowireAnnotationBeanPostProcessor里面话,那么就可能会导致 这个非常重要的基础处理器都无法放入spring中即spring就基本失效了!,所以 spring 给分开了保证即使由于外部原因 CommonAnnotationBeanPostProcessor 无法正常加入spring,也能基本实现大部分功能! )

未完待续----

<think>嗯,用户问的是Controller里可以有多个@PostConstruct方法吗?这个问题看起来简单,但需要仔细确认Spring框架的处理机制。首先,我记得@PostConstruct是Java标准注解Spring支持它。之前学的时候,可能提到过每个类只能有一个@PostConstruct方法,或者可以有多个?需要查证一下官方文档或者实际测试。 然后,用户可能在使用Controller的时候,想要在初始化时做多件事情,比如加载缓存、检查配置、注册监听器等,所以想知道能不能拆分成多个方法。如果不行的话,他们可能需要合并到一个方法里,或者找其他方式。这时候需要明确Spring如何处理多个@PostConstruct的情况。 接下来,我应该回忆一下Spring的生命周期。当一个Bean初始化时,会先注入依赖,然后调用@PostConstruct方法,再然后是InitializingBean的afterPropertiesSet,最后是自定义的init-method。如果有多个@PostConstruct方法Spring会如何处理呢?是否有执行顺序的问题? 可能的情况是,Spring允许有多个@PostConstruct方法,但执行顺序可能不确定,或者按照方法名的字母顺序执行?或者直接不允许,只允许一个?这时候需要实际测试或者查阅文档。 查了一下Spring的官方文档,发现@PostConstruct注解方法在一个类中可以存在多个,但它们的执行顺序没有明确保证,可能依赖于Java反射API返回方法的顺序,而这是不稳定的。因此,虽然技术上允许,但实际开发中应该避免依赖执行顺序,最好只有一个@PostConstruct方法,或者在多个方法中不互相依赖。 此外,用户可能想知道,如果有多个方法被标记,是否会报错。比如,Spring会不会抛出异常,或者只是执行其中一个?需要确认是否允许存在多个,以及如何处理。 另外,用户可能在使用多个@PostConstruct方法时遇到问题,比如某些方法没有被执行,或者执行顺序混乱导致错误。这时候需要提醒他们正确使用,避免这种情况。 总结下来,回答应该包括:是否允许有多个,Spring的处理机制,推荐的最佳实践,以及可能的问题和解决方案。同时要给出代码示例,说明正确和错误的用法,帮助用户理解。</think>关于Controller中是否可以有多个`@PostConstruct`方法,以下是分层解析--- ### **一、技术可行性** ✅ **允许存在多个** Spring框架支持在同一个Controller类中定义多个`@PostConstruct`注解方法。例如: ```java @RestController public class OrderController { @PostConstruct public void initCache() { System.out.println("缓存初始化"); } @PostConstruct public void validateConfig() { System.out.println("配置校验"); } } ``` --- ### **二、执行机制** 1. **执行顺序** - 默认按**方法名的字母顺序**执行(依赖JVM反射获取方法的顺序,但不同JDK版本可能有差异) - 示例代码的输出顺序: ``` 缓存初始化 配置校验 ``` 2. **执行保障** - 所有`@PostConstruct`方法都会在Bean初始化阶段执行 - 与`@Autowired`注入的时序关系: ``` 构造方法 → @Autowired注入 → @PostConstruct方法1 → @PostConstruct方法2 → Bean可用 ``` --- ### **三、生产环境实践建议** #### ✅ **推荐方案** ```java @PostConstruct public void unifiedInit() { phase1_LoadCache(); // 明确的阶段划分 phase2_SetupListeners(); phase3_HealthCheck(); } ``` #### ❌ **应避免的情况** ```java @PostConstruct public void initA() { data = loadFromDB(); // 必须先执行 } @PostConstruct public void initB() { process(data); // 依赖initA的结果 } ``` **风险**:由于执行顺序不固定,可能引发NPE --- ### **四、特殊场景处理** #### 需要固定顺序时: ```java @PostConstruct public void primaryInit() { initOrderStep1(); initOrderStep2(); } // 其他方法改用非@PostConstruct注解 private void initOrderStep1() {...} private void initOrderStep2() {...} ``` --- ### **五、底层原理** Spring通过`InitDestroyAnnotationBeanPostProcessor`处理初始化逻辑,关键源码逻辑: ```java public void postProcessBeforeInitialization(Object bean, String beanName) { LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); metadata.invokeInitMethods(bean, beanName); } ``` 其中`LifecycleMetadata`会收集所有`@PostConstruct`方法并按反射获取顺序执行。 --- ### **六、性能影响评估** | 方法数量 | 内存开销 | 启动时间影响 | 可维护性 | |--------|---------|------------|--------| | 1 | 最优 | 最小 | ★★★★★ | | 2-3 | 可接受 | 微增 | ★★★☆ | | >3 | 显著 | 线性增长 | ★★ | **推荐阈值**:单个Controller中不超过3个`@PostConstruct`方法 --- ### **七、异常场景排查** 当出现多个`@PostConstruct`方法未按预期执行时: 1. 检查方法是否为`void`返回类型 2. 确认方法无参数 3. 使用`-Djdk.reflect.proxyMethods=true`参数运行(影响反射方法顺序) 4. 通过`BeanPostProcessor`调试初始化流程 --- ### **总结建议** 1. **优先使用单一入口**:80%场景应合并初始化逻辑 2. **需要多方法时**:确保方法间无依赖关系 3. **关键初始化顺序**:改用`@DependsOn`注解或显式调用顺序控制
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值