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 实际上就是一个容器, 底层其实就是一个 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
注册到容器中.
声明一个自己的类, 然后继承需要替换的 Bean 的类型 (这样就可以重写原 Bean 中的某些方法, 从而添加自己的处理逻辑), 然后用 @Replace("defaultConfig") 修饰, 如下:
通过 ExtSpringApplication 启动, 可以看到, 实际 Spring 容器中的 Bean 已经替换成我们自己实现的 Bean 组件了.
来源: https://www.cnblogs.com/ocean234/p/12320633.html