Spring扩展:替换IOC容器中的Bean组件 -- @Replace注解
1、背景:
工作中是否有这样的场景?一个软件系统会同时存在多个不同版本,比如我现在做的IM系统,同时又作为公司的技术输出给其他银行,不同的银行有自己的业务实现(登陆验证、用户信息查询等)。或者你的工程里依赖了其他第三方的jar,这些jar包里的组件都是通过Spring容器来管理的,如果你想修改某个类里面的部分逻辑,怎么办呢?是否可以考虑下直接把Spring容器里的某个组件(Bean)替换成你自己实现的Bean?
2、原理&实现
2.1 先看看Spring开放给我们的扩展
Spring框架超强的扩展性毋庸置疑,我们可以通过BeanPostProcessor来简单替换容器中的Bean(Spring中主要有两种后处理器:BeanFactoryPostProcessor和BeanPostProcessor)。
@Component
public class MyBeanPostProcessor implements ApplicationContextAware, BeanPostProcessor {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("defaultConfig")) {
// 如果遇到需要替换的Bean,我们直接换成自己实现的Bean即可(这里可以把就得removeBeanDefinition,然后注册新的registerBeanDefinition)
// 这里的myConfig要继承自defaultConfig,否则引用的地方会报错
return applicationContext.getBean("myConfig");
}
return bean;
}
}
优点:
- 直接利用Spring原生的扩展,可以平滑升级
- 实现简单,易操作好理解,对于只需要替换少数几个Bean的情况下推荐这种方式
缺点:
- beanName硬编码在代码里,虽然可以把替换关系配置在properties里,但是在多版本部署,替换Bean较多时,维护这种关系将是一种负担
- 仅仅是替换了Bean对象,对于容器中元数据如BeanDefinition等等均是原对象的,存在一定局限性
2.2 更灵活一点的替换方式
Spring容器是可以直接动态注册Bean的,而且如果注册的Bean与容器中现有的Bean同名,则会直接替换现有Bean,这样可以绕过Spring启动时解析BeanDefinition的checkCandidate检查。更重要的是其他引用原来Bean的组件的地方都会替换成新注册的Bean,所以这种功能是能直接满足我们上面场景的。
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(DynamicRegisteringBean.class);
springApplication.setAllowBeanDefinitionOverriding(true);
ConfigurableApplicationContext ctx = springApplication.run(args);
System.out.println(((DefaultConfig) ctx.getBean("defaultConfig")).getName());
ctx.getBean(UseBean.class).printName();
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(MyConfig.class);
beanDefinitionBuilder.addPropertyValue("name", "this is a DynamicConfig");
BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) ctx;
// 动态注入同名Bean,会直接覆盖之前的Bean,并且容器中其他Bean对当前Bean的引用也会被更新
beanDefinitionRegistry.registerBeanDefinition("defaultConfig", beanDefinitionBuilder.getBeanDefinition());
System.out.println(((DefaultConfig) ctx.getBean("defaultConfig")).getName());
ctx.getBean(UseBean.class).printName();
}
上面代码中MyConfig继承了DefaultConfig,并重写了getName方法,且MyConfig默认是没有被@Component注解修饰的,完整代码可参考:https://github.com/hiccup234/spring-ext/tree/master/src/test/java/top/hiccup/spring/ext/test/replace/dynamic
运行结果如下:

相比2.1中通过BeanPostProcessor来实现替换Bean组件,动态注册的方法会更灵活一些,也不存在元数据不匹配的问题,但是又引入一个新的问题:需要我们自己手动创建BeanDefinition,这相当于要应用程序去关注和完成解析Bean的工作,使得Spring对应用程序的侵入性变高。
2.3 更优雅一点的替换方式
Spring实际上就是一个容器,底层其实就是一个ConcurrentHashMap。如果要替换Map中的Entry,再次调用put方法设置相同的key不同的value就可以了。同理,如果要替换Spring容器中的Bean组件,那么我们重新定义一个同名的Bean并注册进去就可以了。当然直接申明两个同名的Bean是过不了Spring中ClassPathBeanDefinitionScanner的检查的,这时候需要我们做一点点扩展。

实现自己的ClassPathBeanDefinitionScanner
目前的想法是直接重写checkCandidate方法,通过判断Bean的类上是否有@Replace注解,来决定是否通过检查。

依次往上扩展就到了ConfigurationClassPostProcessor,这是Spring中非常重要的一个容器后置处理器BeanFactoryPostProcessor(上面我们用的是Bean后处理器:BeanPostProcessor),重写processConfigBeanDefinitions方法就可以引入自己实现的ClassPathBeanDefinitionScanner。具体细节可以参考:https://github.com/hiccup234/spring-ext.git
3、使用示例
直接在项目中增加如下坐标(Maven中央仓库),目前这个版本是对Spring的5.2.2.RELEASE做扩展,新版本的Spring其相对3.X、4.X有部分代码变动。
<dependency>
<groupId>top.hiccup</groupId>
<artifactId>spring-ext</artifactId>
<version>5.2.2.0-SNAPSHOT</version>
</dependency>
对Spring Boot中的SpringApplication做一点扩展,将上面扩展的ConfigurationClassPostProcessor类以RootBeanDefinition注册到容器中。

声明一个自己的类,然后继承需要替换的Bean的类型(这样就可以重写原Bean中的某些方法,从而添加自己的处理逻辑),然后用@Replace("defaultConfig")修饰,如下:

通过ExtSpringApplication启动,可以看到,实际Spring容器中的Bean已经替换成我们自己实现的Bean组件了。

Spring扩展:替换IOC容器中的Bean组件 -- @Replace注解的更多相关文章
- spring在IoC容器中装配Bean详解
1.Spring配置概述 1.1.概述 Spring容器从xml配置.java注解.spring注解中读取bean配置信息,形成bean定义注册表: 根据bean定义注册表实例化bean: 将bean ...
- 【spring源码学习】spring的IOC容器之自定义xml配置标签扩展namspaceHandler向IOC容器中注册bean
[spring以及第三方jar的案例]在spring中的aop相关配置的标签,线程池相关配置的标签,都是基于该种方式实现的.包括dubbo的配置标签都是基于该方式实现的.[一]原理 ===>sp ...
- Spring基础——在 IOC 容器中 Bean 之间的关系
一.在 Spring IOC 容器中 Bean 之间存在继承和依赖关系. 需要注意的是,这个继承和依赖指的是 bean 的配置之间的关系,而不是指实际意义上类与类之间的继承与依赖,它们不是一个概念. ...
- Spring学习--实现 FactoryBean 接口在 Spring IOC 容器中配置 Bean
Spring 中有两种类型的 bean , 一种是普通的 bean , 另一种是工厂 bean , 即 FactroyBean. 工厂 bean 跟普通 bean 不同 , 其返回的对象不是指定类的一 ...
- Spring IOC容器中注入bean
一.基于schema格式的注入 1.基本的注入方式 (属性注入方式) 根据setXxx()方法进行依赖注入,Spring只会检查是否有setter方法,是否有对应的属性不做要求 <bean id ...
- Spring重点—— IOC 容器中 Bean 的生命周期
一.理解 Bean 的生命周期,对学习 Spring 的整个运行流程有极大的帮助. 二.在 IOC 容器中,Bean 的生命周期由 Spring IOC 容器进行管理. 三.在没有添加后置处理器的情况 ...
- String框架搭建的基本步骤,及从 IOC & DI 容器中获取 Bean(spring框架bean的配置)--有实现数据库连接池的链接
Spring框架的插件springsource-tool-suite-3.4.0.RELEASE-e4.3.1-updatesite(是一个压缩包)导入步骤: eclipse->help-> ...
- Spring源码分析-从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(一)?
阅前提醒 全文较长,建议沉下心来慢慢阅读,最好是打开Idea,点开Spring源码,跟着下文一步一步阅读,更加便于理解.由于笔者水平优先,编写时间仓促,文中难免会出现一些错误或者不准确的地方,恳请各位 ...
- 【String注解驱动开发】如何按照条件向Spring容器中注册bean?这次我懂了!!
写在前面 当bean是单实例,并且没有设置懒加载时,Spring容器启动时,就会实例化bean,并将bean注册到IOC容器中,以后每次从IOC容器中获取bean时,直接返回IOC容器中的bean,不 ...
随机推荐
- 腾讯云服务器(centos7.2)上安装MySQL
1.到MySQL官网找到相应的版本 https://dev.mysql.com/downloads/repo/yum/ 找到下面的Linux7,即CentOS7(CentOS是Red Hat旗下的)点 ...
- Mac 环境docker 安装jenkins
网上很多的教程是讲的是Linux 上的Docker安装Jenkins,但是我用的是Mac,所以参考之前的前辈写的文章,记录一下自己的安装过程.非常感谢参考文章的前辈写的文章. 参考Docker安装Je ...
- Harbor快速搭建企业级Docker仓库
Github: https://github.com/goharbor/harbor 改端口安装: https://www.cnblogs.com/huangjc/p/6420355.html 相关博 ...
- react render渲染的几种情况
1. 首次加载 2. setState改变组件内部state. 注意: 此处是说通过setState方法改变. 3. 接受到新的props
- 第12章 Reference-RIL运行框架
Reference-RIL完成两部分处理逻辑: 与LibRIL交互完成RIL消息的处理. 与Modem通信模块交互完成AT命令的执行. Reference-RIL的运行机制 主要涉及以下几个方面: R ...
- android编译架构之添加C项目
1. 增加一个项目与android编译中枢息息相关.特别需要告诉编译中枢的一些特别信息. 例如: A 这个项目target名字是什么 B 这个项目编译类型是什么,bin?c?lib?or jar? ...
- numpy学习总结
Contents Numpy是一个用python实现的科学计算包,主要提供矩阵运算的功能,而矩阵运算在机器学习领域应用非常广泛,Numpy一般与Scrapy.matplotlib一起使用. Numpy ...
- 美团CodeM 资格赛第一题
美团外卖的品牌代言人袋鼠先生最近正在进行音乐研究.他有两段音频,每段音频是一个表示音高的序列.现在袋鼠先生想要在第二段音频中找出与第一段音频最相近的部分. 具体地说,就是在第二段音频中找到一个长度和第 ...
- node--静态服务器
1.同步读取文件 const data = fs.readFileSync('./model/mime.json'); // 这里是添加了可以正常链接其他格式文件的服务器 const http = ...
- c/s用户与服务器之间的传输
####第一页 <%@ page language="java" contentType="text/html; charset=UTF-8" pageE ...