Spring扩展:替换IOC容器中的Bean组件 -- @Replace注解

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介:

Spring扩展:替换IOC容器中的Bean组件 -- @Replace注解
1、背景:
工作中是否有这样的场景?一个软件系统会同时有多个不同版本部署,比如我现在做的IM系统,同时又作为公司的技术输出给其他银行,不同的银行有自己的业务实现(比如登陆验证、用户信息查询等); 又或者你的工程里依赖了公司的二方包A,A又依赖了B...这些jar包里的组件都是通过Spring容器来管理的,如果你想改B中某个类的逻辑,但是又不可能让架构组的人帮你打一份特殊版本的B;怎么办呢?是否可以考虑下直接把Spring容器里的某个组件(Bean)替换成你自己实现的Bean?

2、原理&实现
2.1 先看看Spring开放给我们的扩展
Spring框架超强的扩展性毋庸置疑,我们可以通过BeanPostProcessor来简单替换容器中的Bean。

@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
        // 这里的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的组件都会被替换,这很重要,所以这种功能是能直接满足我们上面场景的。

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的工作,使得对应用程序的侵入性变高。

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有部分代码变动。

<groupId>top.hiccup</groupId>
<artifactId>spring-ext</artifactId>
<version>5.2.2.0-SNAPSHOT</version>


对Spring Boot中的SpringApplication做一点扩展,将上面扩展的ConfigurationClassPostProcessor注册到容器中。

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

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

原文地址https://www.cnblogs.com/ocean234/p/12320633.html

相关文章
|
4天前
|
XML Java 数据格式
【SpringFramework】Spring IoC-基于XML的实现
本文主要讲解SpringFramework中IoC和DI相关概念,及基于XML的实现方式。
94 69
|
3天前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
34 21
|
9天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
8天前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
29天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
51 2
|
2月前
|
负载均衡 算法 Java
除了 Ribbon,Spring Cloud 中还有哪些负载均衡组件?
这些负载均衡组件各有特点,在不同的场景和需求下,可以根据项目的具体情况选择合适的负载均衡组件来实现高效、稳定的服务调用。
103 5
|
2月前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
2月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
45 0
|
5月前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
7月前
|
XML Java 数据格式
Spring5系列学习文章分享---第一篇(概述+特点+IOC原理+IOC并操作之bean的XML管理操作)
Spring5系列学习文章分享---第一篇(概述+特点+IOC原理+IOC并操作之bean的XML管理操作)
53 1