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. winform窗体中webbrowser如何屏蔽脚本错误弹窗

    在构造函数中加入: webBrowser.ScriptErrorsSuppressed = true;

  2. java实现树的一般操作

    https://www.cnblogs.com/dawnyxl/p/9047437.html 树是数据结构中最基本的结构,今天的博客更新一下树的基本操作: 树的节点结构: package tree; ...

  3. Angular系列一:Angular程序架构

    Angular程序架构 Angular程序架构 组件:一段带有业务逻辑和数据的Html服务:用来封装可重用的业务逻辑指令:允许你向Html元素添加自定义行为模块: 环境搭建 安装nodeJs安装好no ...

  4. Django学习之路05

    Django模板层 模板层语法(两类) 变量相关:{{      }} 逻辑相关:{%  %} 给模板传值的方法 #方法1 #通过字典传值,指名道姓,例如下 return render(request ...

  5. 初识JS之数据类型

    JavaScript JavaScript引入方式 Script标签内写代码 <script> // 在这里写你的JS代码 </script> 引入额外的JS文件 <sc ...

  6. 用Python搭建简单的HTTP服务 · Zhangxu's Blog

    分享一个快速用Python搭建简单的HTTP服务的方法. 平时我们可能有需要,传输某个文件到手机,或者工作中某台服务器的电脑. 假如这个手机是个测试手机/服务器,并没有微信QQ之类的软件,而且你也不想 ...

  7. PHP RFI 的小tip

    有关PHP include的帖子网上已经很多了,wooyun的知识库里面也有一篇总结的很好的文章,传送门:http://drops.wooyun.org/tips/3827,今晚在看书的时候看到RFI ...

  8. 阿里为何要用独立APP挖微信微商墙角?

    ​ 微商,这个被很多人看来是逃离马云魔咒,和淘宝抗衡的电商模式,自诞生到狂飙就伴随着种种争议.由于传播效率极强,在很长时间里也一直是不少人口中津津乐道的神话故事和救市良方.以至于,淘宝推出各种手段封杀 ...

  9. IP 多播

    IP 多播 一.IP 多播的基本概念 1.1.简介 不使用多播时需要发送 90 次单播: 使用多播时只需要发送 1 次多播: 1.2.IP 多播的一些特点 多播使用组地址:D 类IP地址支持多播.多播 ...

  10. 记一次手机与PC同步开发Android项目

    目录 -1 前言 0.0 流程简介 1.0 AS创建项目并上传GitHub 2.0 AIDE克隆GitHub项目 能力不足时曲线救国 > 3.0 termux编译AIDE目录下的项目文件 3.1 ...