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注解的更多相关文章

  1. spring在IoC容器中装配Bean详解

    1.Spring配置概述 1.1.概述 Spring容器从xml配置.java注解.spring注解中读取bean配置信息,形成bean定义注册表: 根据bean定义注册表实例化bean: 将bean ...

  2. 【spring源码学习】spring的IOC容器之自定义xml配置标签扩展namspaceHandler向IOC容器中注册bean

    [spring以及第三方jar的案例]在spring中的aop相关配置的标签,线程池相关配置的标签,都是基于该种方式实现的.包括dubbo的配置标签都是基于该方式实现的.[一]原理 ===>sp ...

  3. Spring基础——在 IOC 容器中 Bean 之间的关系

    一.在 Spring IOC 容器中 Bean 之间存在继承和依赖关系. 需要注意的是,这个继承和依赖指的是 bean 的配置之间的关系,而不是指实际意义上类与类之间的继承与依赖,它们不是一个概念. ...

  4. Spring学习--实现 FactoryBean 接口在 Spring IOC 容器中配置 Bean

    Spring 中有两种类型的 bean , 一种是普通的 bean , 另一种是工厂 bean , 即 FactroyBean. 工厂 bean 跟普通 bean 不同 , 其返回的对象不是指定类的一 ...

  5. Spring IOC容器中注入bean

    一.基于schema格式的注入 1.基本的注入方式 (属性注入方式) 根据setXxx()方法进行依赖注入,Spring只会检查是否有setter方法,是否有对应的属性不做要求 <bean id ...

  6. Spring重点—— IOC 容器中 Bean 的生命周期

    一.理解 Bean 的生命周期,对学习 Spring 的整个运行流程有极大的帮助. 二.在 IOC 容器中,Bean 的生命周期由 Spring IOC 容器进行管理. 三.在没有添加后置处理器的情况 ...

  7. String框架搭建的基本步骤,及从 IOC & DI 容器中获取 Bean(spring框架bean的配置)--有实现数据库连接池的链接

    Spring框架的插件springsource-tool-suite-3.4.0.RELEASE-e4.3.1-updatesite(是一个压缩包)导入步骤: eclipse->help-> ...

  8. Spring源码分析-从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(一)?

    阅前提醒 全文较长,建议沉下心来慢慢阅读,最好是打开Idea,点开Spring源码,跟着下文一步一步阅读,更加便于理解.由于笔者水平优先,编写时间仓促,文中难免会出现一些错误或者不准确的地方,恳请各位 ...

  9. 【String注解驱动开发】如何按照条件向Spring容器中注册bean?这次我懂了!!

    写在前面 当bean是单实例,并且没有设置懒加载时,Spring容器启动时,就会实例化bean,并将bean注册到IOC容器中,以后每次从IOC容器中获取bean时,直接返回IOC容器中的bean,不 ...

随机推荐

  1. firefox上网慢

    由于Chromium浏览器,打字的时候经常会跳字母,所以就换了firefox浏览器,但是FF上网的时候特别慢,而且大部分时间是花费在解析域名上.因此到网上找了许多资料,最终解决方法如下. 1.安装dn ...

  2. 吴裕雄--天生自然python学习笔记:Python MySQL - mysql-connector 驱动

    本章节我们为大家介绍使用 mysql-connector 来连接使用 MySQL, mysql-connector 是 MySQL 官方提供的驱动器. 我们可以使用 pip 命令来安装 mysql-c ...

  3. python开发之Pandas

    正确的对DataFrame reverse运算 data.reindex(index=data.index[::-]) or simply: data.iloc[::-] will reverse y ...

  4. Java找出两个链表的第一个公共节点

    题目描述输入两个链表,找出它们的第一个公共结点. 我的思路:因为是链表,长度都是未知的,不能盲目的两个一起开始自增判断. 首先需要得到 L1的长度 和 L2的长度,让较长的那个先走 (length1- ...

  5. CIA Hive Beacon Infrastructure复现1——使用Apache mod_rewrite实现http流量分发

    0x00 前言 2017年11月9日维基解密公布一个代号为Vault8的文档,包含服务器远程控制工具Hive的源代码和开发文档.开发文档中的框架图显示Hive支持流量分发功能,若流量有效,转发至Hon ...

  6. 台式机安装CentOS7.6 Minimal ISO系统并增加图形化桌面

    需求:公司测试环境因业务原因,需要在台式电脑上安装带桌面的CentOS系统,因同事有一个7.6版本Minimal ISO镜像的安装U盘,为了图方便没有去下载everything ISO镜像,而是待同事 ...

  7. CentOS卸载旧版本内核

    CentOS卸载旧版本内核 查看正在使用的内核 uname -a 查看系统中的全部内核 rpm -qa | grep kernel 卸载多余内核 yum remove kernel-x.xx.x

  8. golang xml解析

    第二章里还提到了xml的解析部分.之前有想整理下encoding包下常用的几个文件格式的处理.这次刚好整理下xml的部分.先上例子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 ...

  9. 整合 KAFKA+Flink 实例(第一部分,趟坑记录)

    2017年后,一大波网络喧嚣,说流式处理如何牛叉,如何高大上,抱歉,工作满负荷,没空玩那个: 今年疫情隔离在家,无聊,开始学习 KAFKA+Flink ,目前的打算是用爬虫抓取网页数据,传递到Kafk ...

  10. prometheus服务发现机制

    一. Prometheus与服务发现 1.1 目前支持的服务发现方式 二. 案例 2.1 基于文件的服务发现 2.2 基于Consul的服务发现 三.本地测试 3.1 基于文件的服务发现 1.测试环境 ...