一、事出有因

​ 最近有一个场景,因同一个项目中不同JAR包依赖同一个组件,但依赖组件的版本不同,导致无论使用哪个版本都报错(无法同时兼容两个JAR包中所需的方法调用),经过分析发现差异的部份是在一个BEAN中的方法出入参不同而矣,故考虑通过动态替换掉这个存在兼容性的BEAN,换成我们自己继承自该BEAN类并实现适配兼容方法,从而最终解决组件版本不兼容问题;

二、解决方案困境

​ 但在实现的编码过程中发现,原依赖的那个BEAN并不是普通的通过标注@Compent之类的注解实现的注册的BEAN,而是由自定义的BeanDefinitionRegistryPostProcessor BEAN类中动态注册的BEAN,这样BEAN的注册顺序是“无法确定”的,我原本想通过自定义一个BeanDefinitionRegistryPostProcessor BEAN类,在postProcessBeanDefinitionRegistry方法中通过找到原依赖BEAN的名字,然后移除该名称对应的BEAN定义信息(BeanDefinition),最后再以原BEAN的名字定义并注册成为我自己的适配器的BEAN类,这样就实现了“移花接木”的功能,然而想法是OK的但最终运行起来,发现BEAN并没有成功被替换,究其原因发现,原来我自己定义的BeanDefinitionRegistryPostProcessor BEAN类是优先于原依赖的那个问题BEAN所对应的BeanDefinitionRegistryPostProcessor BEAN类之前执行的,这样就会导致在我的自定义BeanDefinitionRegistryPostProcessor BEAN类postProcessBeanDefinitionRegistry方法中并没有找到原依赖BEAN名字对应的BeanDefinition,也就无法进行正常的替换了,如果说文字难看懂,可以见如下图所示:

三、柳暗花明,终级解决方案

​ 既然问题根源找到,那确保一个自定义的BeanDefinitionRegistryPostProcessor 类被最后定义为Bean、且被最后执行成为关键(至少得比原依赖的那个问题BEAN所对应的BeanDefinitionRegistryPostProcessor BEAN类【如:OldBeanDefinitionRegistryPostProcessor】之后执行才行),因为这样我们才能获得原依赖的问题Bean的BeanDefinition,才能进行正常的替换BeanDefinition,最终达到原来依赖问题Bean的自动都依赖到新的适配器Bean,从而可以控制修改问题方法的中的逻辑(比如:兼容、降级)。当然,我估计此时有人会想说,何必这么麻烦,一个AOP切面不就搞定了吗?通过实现@Around切面,把有问题的方法拦截替换成自己的适配方法逻辑,这种确实也是一种有效手段,但我认为不够优雅,而且代码的可读性不强且未必能覆盖所有方法,比如:如果涉及问题方法内部依赖的内部方法(如protected)过多或依赖的其它BEAN过多时,可能就会导致这个切面类里面要复制一堆的原问题BEAN类中的内部方法到切面类中,但这样带来的风险就是代码重复及原代码更新后导致的不一致等隐性问题,故我的原则是:如果只是简单的替换原有方法且逻辑不复杂的可以使用AOP切面来解决,但如果涉及复杂的业务逻辑且内部依赖过多,这时采取代理、适配或装饰可能更为合适一些。

好了,如下就是我要分享的三种:确保一个自定义的BeanDefinitionRegistryPostProcessor 类被最后定义为Bean、且被最后执行的实现方式。

第一种实现方案

第一种:通过嵌套注册自定义的BeanDefinitionRegistryPostProcessor 类BEAN的方式,这种方式实现思路是:PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors会先执行已获得BeanDefinitionRegistryPostProcessor BEAN集合,执行完这些BEAN集合后(这里我称为第一轮或第一层),会再次尝试获取第二轮、第三轮一直到获取的BeanDefinitionRegistryPostProcessor BEAN集合全部处理完成为止,框架相关代码片段如下:

  1. boolean reiterate = true;
  2. while (reiterate) {
  3. reiterate = false;
  4. postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
  5. for (String ppName : postProcessorNames) {
  6. if (!processedBeans.contains(ppName)) {
  7. currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
  8. processedBeans.add(ppName);
  9. reiterate = true;
  10. }
  11. }
  12. sortPostProcessors(currentRegistryProcessors, beanFactory);
  13. registryProcessors.addAll(currentRegistryProcessors);
  14. invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
  15. currentRegistryProcessors.clear();
  16. }

实现方式代码如下:

  1. //如下是第一层自定义的BeanDefinitionRegistryPostProcessor BEAN,内部再注册真正用于替换BEAN目的NewBeanDefinitionRegistryPostProcessor BEAN
  2. //author:zuowenjun
  3. @Component
  4. public class FirstDynamicBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
  5. @Override
  6. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
  7. BeanDefinitionBuilder beanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(NewBeanDefinitionRegistryPostProcessor.class);
  8. beanDefinitionRegistry.registerBeanDefinition("newBeanDefinitionRegistryPostProcessor",beanDefinitionBuilder.getBeanDefinition());
  9. System.out.printf("【%1$tF %1$tT.%1$tL】%s,FirstDynamicBeanPostProcessor.postProcessBeanDefinitionRegistry%n", new Date());
  10. }
  11. @Override
  12. public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
  13. System.out.printf("【%1$tF %1$tT.%1$tL】%s,FirstDynamicBeanPostProcessor.postProcessBeanFactory%n", new Date());
  14. }
  15. }
  16. //用于将原依赖的问题Bean替换为同名的新的适配器Bean(下文中所有替换方式最终都要使用该类)
  17. public class NewBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
  18. @Override
  19. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
  20. System.out.printf("【%1$tF %1$tT.%1$tL】%s,NewBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry%n",new Date());
  21. boolean isContainsSpecialBean = ((DefaultListableBeanFactory) beanDefinitionRegistry).containsBean("old问题Bean名称");
  22. if (isContainsSpecialBean) {
  23. beanDefinitionRegistry.removeBeanDefinition("old问题Bean名称");
  24. BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DemoCompentB.class);
  25. beanDefinitionBuilder.addConstructorArgValue(((DefaultListableBeanFactory) beanDefinitionRegistry).getBean(NewBeanAdapter.class)); //NewBeanAdapter为继承自old问题Bean的装饰者、适配器类
  26. AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
  27. beanDefinition.setPrimary(true);
  28. beanDefinitionRegistry.registerBeanDefinition("old问题Bean名称", beanDefinition);
  29. }
  30. }
  31. @Override
  32. public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
  33. System.out.printf("【%1$tF %1$tT.%1$tL】%s,NewBeanDefinitionRegistryPostProcessor.postProcessBeanFactory%n",new Date());
  34. }
  35. }

最终执行的顺序如下:(可以看到NewBeanDefinitionRegistryPostProcessor是在OldBeanDefinitionRegistryPostProcessor之后执行的,这样就可以正常替换Bean定义了)

FirstDynamicBeanPostProcessor.postProcessBeanDefinitionRegistry (第一轮)

OldBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry (第一轮)

NewBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry (第二轮)

FirstDynamicBeanPostProcessor.postProcessBeanFactory

OldBeanDefinitionRegistryPostProcessor.postProcessBeanFactory

NewBeanDefinitionRegistryPostProcessor.postProcessBeanFactory

第二种实现方案

第二种:通过额外定义一个BeanDefinitionRegistryPostProcessor BEAN并实现PriorityOrdered、BeanFactoryAware接口,确保该BEAN最先被执行(Order=0),然后在postProcessBeanDefinitionRegistry方法中通过applicationContext.setDependencyComparator设置自定义的排序器,达到排序BeanDefinitionRegistryPostProcessor BEAN集合的执行顺序,这种方式实现思路是:在执行BeanDefinitionRegistryPostProcessor BEAN集合前会调用sortPostProcessors方法进行排序,而排序规则又依赖于DependencyComparator,通过控制排序规则实现间接控制执行顺序,先看框架的代码片段:

  1. private static void sortPostProcessors(List<?> postProcessors, ConfigurableListableBeanFactory beanFactory) {
  2. Comparator<Object> comparatorToUse = null;
  3. if (beanFactory instanceof DefaultListableBeanFactory) {
  4. comparatorToUse = ((DefaultListableBeanFactory) beanFactory).getDependencyComparator();
  5. }
  6. if (comparatorToUse == null) {
  7. comparatorToUse = OrderComparator.INSTANCE;
  8. }
  9. postProcessors.sort(comparatorToUse);
  10. }
  11. //如下是invokeBeanFactoryPostProcessors方法片段:
  12. sortPostProcessors(currentRegistryProcessors, beanFactory);
  13. registryProcessors.addAll(currentRegistryProcessors);
  14. invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);

实现方式代码如下:

  1. @Component
  2. public static class FirstBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered
  3. , BeanFactoryAware {
  4. private BeanFactory beanFactory;
  5. @Override
  6. public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
  7. this.beanFactory=beanFactory;
  8. }
  9. @Override
  10. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
  11. ((DefaultListableBeanFactory) beanFactory).setDependencyComparator(new OrderComparator(){
  12. @Override
  13. protected int getOrder(Object obj) {
  14. if (obj instanceof NewBeanDefinitionRegistryPostProcessor){ //如果是NewBeanDefinitionRegistryPostProcessor则将它的排序序号设置为最大
  15. return Integer.MAX_VALUE;
  16. }
  17. return super.getOrder(obj)-1; //其余的全部设为比它小1
  18. }
  19. });
  20. System.out.printf("【%1$tF %1$tT.%1$tL】%s,FirstBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry%n", new Date());
  21. }
  22. @Override
  23. public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
  24. System.out.printf("【%1$tF %1$tT.%1$tL】%s,FirstBeanDefinitionRegistryPostProcessor.postProcessBeanFactory%n", new Date());
  25. }
  26. @Override
  27. public int getOrder() {
  28. return 0;//确保
  29. }
  30. }

最终执行的顺序如下:(NewBeanDefinitionRegistryPostProcessor在OldBeanDefinitionRegistryPostProcessor后面执行)

FirstBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry (第1批:实现PriorityOrdered执行)

OldBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry (第3批:普通BEAN执行)

NewBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry (第3批:普通BEAN执行)

FirstBeanDefinitionRegistryPostProcessor.postProcessBeanFactory

OldBeanDefinitionRegistryPostProcessor.postProcessBeanFactory

NewBeanDefinitionRegistryPostProcessor.postProcessBeanFactory

第三种实现方案

第三种:通过自定义DeferredImportSelector类并配合@Import注解,实现NewBeanDefinitionRegistryPostProcessor最后才被注册成为BEAN,最后才有机会执行,这种方式实现思路是:因为DeferredImportSelector的执行时机是在所有@Configuration类型bean解析之后。

实现方式代码如下:

  1. public static class BeansImportSelector implements DeferredImportSelector {
  2. @Override
  3. public String[] selectImports(AnnotationMetadata importingClassMetadata) {
  4. return new String[]{NewBeanDefinitionRegistryPostProcessor.class.getName()};
  5. }
  6. }
  7. @Configuration
  8. @Import(BeansImportSelector.class)
  9. public class BeansConfig {
  10. }

最终执行的顺序如下:(NewBeanDefinitionRegistryPostProcessor在OldBeanDefinitionRegistryPostProcessor后面执行)

OldBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry

NewBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry

OldBeanDefinitionRegistryPostProcessor.postProcessBeanFactory

NewBeanDefinitionRegistryPostProcessor.postProcessBeanFactory

四、引发的思考

如上就是三种实现方式,至于哪种方式最好,这要看具体的场景,第一种、第三种影响面相对较小,而第二种因为涉及更换DependencyComparator,可能影响的是全局。另外之所以会研究如上实现方式,主要原因还是因为我们的项目框架代码没有考虑扩展性及规范性,比如要动态注册BEAN,至少应实现PriorityOrdered或Order接口或指明@Order注解,这样当我们在某些特定场景需要做一下优化或替换时,则可以直接采取相同的方式但指定Order在前或在后即可,也就不用这么复杂了,比如:

  1. @Component
  2. public class OldBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor,Order {
  3. @Override
  4. public int getOrder() {
  5. return 100;
  6. }
  7. ...
  8. }
  9. @Component
  10. public class NewBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor,Order {
  11. @Override
  12. public int getOrder() {
  13. return 101;//只需序号在OldBeanDefinitionRegistryPostProcessor.getOrder之后即可
  14. }
  15. ...
  16. }

确保某个BeanDefinitionRegistryPostProcessor Bean被最后执行的几种实现方式的更多相关文章

  1. 恶意软件开发——shellcode执行的几种常见方式

    一.什么是shellcode? shellcode是一小段代码,用于利用软件漏洞作为有效载荷.它之所以被称为"shellcode",是因为它通常启动一个命令shell,攻击者可以从 ...

  2. Spring中bean的初始化和销毁几种实现方式

    Bean的生命周期 : 创建bean对象 – 属性赋值 – 初始化方法调用前的操作 – 初始化方法 – 初始化方法调用后的操作 – --- 销毁前操作 – 销毁方法的调用. [1]init-metho ...

  3. springBoot启动时让方法自动执行的几种实现方式

    一.开篇名义 在springBoot中我们有时候需要让项目在启动时提前加载相应的数据或者执行某个方法,那么实现提前加载的方式有哪些呢?接下来我带领大家逐个解答 1.实现ServletContextAw ...

  4. Spring第一课:基于XML装配bean(四),三种实例化方式:默认构造、静态工厂、实例工厂

    Spring中基于XML中的装配bean有三种方式: 1.默认构造 2.静态工厂 3.实例工厂 1.默认构造 在我们在Spring的xml文件中直接通过:     <bean id=" ...

  5. Spring Bean初始化之后执行指定方法

    转: Spring Bean初始化之后执行指定方法 2017年07月31日 15:59:33 vircens 阅读数:24807   Spring Bean初始化之后执行指定方法 在运用Spring进 ...

  6. Spring中初始化bean和销毁bean的时候执行某个方法的详解

    关于在spring  容器初始化 bean 和销毁前所做的操作定义方式有三种: 第一种:通过注解@PostConstruct  和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作 ...

  7. 使用定时器判断确保某个标签有值才执行方法, 控制js代码执行先后顺序

    使用定时器判断确保某个标签有值才执行方法: var wait = setInterval(function(){ var diqu = $("#diqu").val(); //确保 ...

  8. Spring中Bean的配置:基于XML文件的方式

    Bean的配置一共有两种方式:一种是基于XML文件的方式,另一种是基于注解的方式.本文主要介绍基于XML文件的方式 <bean id="helloWorld" class=& ...

  9. 三个线程T1,T2,T3.保证顺序执行的三种方法

    经常看见面试题:有三个线程T1,T2,T3,有什么方法可以确保它们按顺序执行.今天手写测试了一下,下面贴出目前想到的3种实现方式 说明:这里在线程中我都用到了sleep方法,目的是更容易发现问题.之前 ...

随机推荐

  1. HTTP的传输编码(Transfer-Encoding:chunked) / net::ERR_INVALID_CHUNKED_ENCODING

    https://blog.csdn.net/m0_37668842/article/details/89138733 https://www.cnblogs.com/jamesvoid/p/11297 ...

  2. Kill pending windows service

    Get-Service winrm -Verbose $winrmService=Get-CimInstance -ClassName win32_Service |? {$_.Name -eq &q ...

  3. Chapter Zero 0.2.2 内存

    目录 内存 内存的多通道设计 DRAM 和 SRAM 只读存储器(ROM) RAM.ROM以及硬盘的区别(转自百度) 内存 CPU的数据都是来自主存储器(main memory),个人计算机的主寄存器 ...

  4. 4.Redis客户端的使用

    标题 : 4.Redis客户端的使用 目录 : Redis 序号 : 4 Console.WriteLine($"北京和天津之间的距离是:{distance}公里"); #### ...

  5. 利用windows api共享内存通讯

    主要涉及CreateFile,CreateFileMapping,GetLastError,MapViewOfFile,sprintf,OpenFileMapping,CreateProcess Cr ...

  6. 5分钟看懂Code128条形码

    什么是Code128条形码? 相信大家看到这个都不陌生吧 1.前言 条形码种类很多,常见的大概有二十多种码制,其中包括:Code39码(标准39码).Codabar码(库德巴码).Code25码(标准 ...

  7. 深入理解JavaScript中的箭头

    箭头函数可以使我们的代码更加简洁,如下: var sum = (a,b) => a+b; JavaScript 充满了我们需要编写在其他地方执行的小函数的情况. 例如: arr.forEach( ...

  8. canvas画布基本知识点总结

    HTML5的canvas元素使用JavaScript画图: <canvas width="600" height="400"> </canva ...

  9. 如何在github中插入图片,链接,图片链接(给图片加上链接),文字+图片链接,的实战分享!

    如何在github中插入图片,链接,图片链接(给图片加上链接),文字+图片链接,的实战分享! markdown 1.文字链接: [link-Text](link-URL) [home](https:/ ...

  10. web components & publish custom element & npm

    web components & publish custom element & npm https://www.webcomponents.org/publish Polymer ...