Spring擴充套件:替換IOC容器中的Bean元件 -- @Replace註解
阿新 • • 發佈:2020-03-22
## 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`的檢查的,這時候需要我們做一點點擴充套件。
![](https://img2020.cnblogs.com/blog/631355/202003/631355-20200311113436887-1896545792.png)
#### 實現自己的ClassPathBeanDefinitionScanner
目前的想法是直接重寫`checkCandidate`方法,通過判斷Bean的類上是否有@Replace註解,來決定是否通過檢查。
![](https://img2020.cnblogs.com/blog/631355/202003/631355-20200315182700231-1169329733.png)
依次往上擴充套件就到了`ConfigurationClassPostProcessor`,這是Spring中非常重要的一個容器後置處理器BeanFactoryPostProcessor(上面我們用的是Bean後處理器:BeanPostProcessor),重寫processConfigBeanDefinitions方法就可以引入自己實現的ClassPathBeanDefinitionScanner。
具體細節可以參考:[https://github.com/hiccup234/spring-ext.git](https://github.com/hiccup234/spring-ext.git)
## 3、使用示例
直接在專案中增加如下座標(Maven中央倉庫),目前這個版本是對Spring的5.2.2.RELEASE做擴充套件,新版本的Spring其相對3.X、4.X有部分程式碼變動。
```
```
對Spring Boot中的`SpringApplication`做一點擴充套件,將上面擴充套件的`ConfigurationClassPostProcessor`註冊到容器中。
![](https://img2020.cnblogs.com/blog/631355/202003/631355-20200320000634938-1282888581.png)
宣告一個自己的類,然後繼承需要替換的Bean的型別(這樣就可以重寫原Bean中的某些方法,從而新增自己的處理邏輯),然後用@Replace("defaultConfig")修飾,如下:
![](https://img2020.cnblogs.com/blog/631355/202003/631355-20200320011649108-521562749.png)
通過ExtSpringApplication啟動,可以看到,實際Spring容器中的Bean已經替換成我們自己實現的Bean元件了。
![](https://img2020.cnblogs.com/blog/631355/202003/631355-20200320010734038-11831001