目錄
場景
方法1 直接在自己工程中建同包同類名的類進行替換
方法2 采用@Primary注解
方法3 排除需要替換的jar包中的類
方法4 @Bean 覆寫
方法5 使用BeanDefinitionRegistryPostProcessor
場景
什麼情況下要覆寫原有的Spring Bean ? 例如引入的第三方jar包中的某個類有些問題,然有沒有源碼提供或者嫌編譯源碼太費事,這個時間可以考慮覆寫原有的類。
方法1 直接在自己工程中建同包同類名的類進行替換
方式簡單粗暴,可以直接覆寫掉jar包中的類,spring項目會優先加載自定義的類。下面是覆寫 flowable架構中的一個類 FlowableCookieFilter,主要是想修改它裡面的redirectToLogin方法的業務邏輯。包路徑為 org.flowable.ui.common.filter, 直接工程裡面建立一樣路徑一樣類名FlowableCookieFilter即可。
方法2 采用@Primary注解
該方法适用于接口實作類,自己建立一個原jar包接口的實作類,然後類上加上@Primary注解,spring則預設加載該類執行個體化出的Bean。下面的例子: 一個接口 RemoteIdmService,原先jar包中隻有一個實作類 RemoteIdmServiceImpl,現在自己工程裡面建立一個 CustomRemoteIdmServiceImpl 繼承RemoteIdmService接口,然後發現所有調用RemoteIdmService接口裡面的方法實際調用走的是CustomRemoteIdmServiceImpl 裡面的方法。
方法3 排除需要替換的jar包中的類
使用 @ComponentScan 裡面的 excludeFilters 功能排除調用要替換的類,然後自己寫個類繼承替換的類即可。下面的例子是替換掉 jar包中的PersistentTokenServiceImpl類
@SpringBootApplication
@ComponentScan(excludeFilters ={
@ComponentScan.Filter(type =
FilterType.ASSIGNABLE_TYPE,classes ={
PersistentTokenServiceImpl.class})})
publicclassApplication{
publicstaticvoidmain(String[]args){
newSpringApplication(Test.class).run(args);
}
}@Service
publicclassMyPersistentTokenServiceImplextendsPersistentTokenServiceImpl{
@Override
publicTokensaveAndFlush(Tokentoken){
// 覆寫該方法的業務邏輯
tokenCache.put(token.getId(),token);
idmIdentityService.saveToken(token);
returntoken;
}
@Override
publicvoiddelete(Tokentoken){
// 覆寫該方法的業務邏輯
tokenCache.invalidate(token.getId());
idmIdentityService.deleteToken(token.getId());
}
@Override
publicTokengetPersistentToken(StringtokenId){
// 覆寫該方法的業務邏輯
returngetPersistentToken(tokenId,false);
}
}
方法4 @Bean 覆寫
該場景針對,架構jar包中有@ConditionalOnMissingBean注解,這種注解是說明如果你也建立了一個一樣的Bean則架構就不自己再次建立這個bean了,這樣你可以覆寫自己的bean。原jar包中的配置類:
直接繼承要覆寫的類,自己重寫裡面方法,使用@Component注入到spring中去
方法5 使用BeanDefinitionRegistryPostProcessor
關于 BeanDefinitionRegistryPostProcessor 、 BeanFactoryPostProcessor可以參考下面的博文:https://blog.csdn.net/ztchun/article/details/90814135
BeanDefinitionRegistryPostProcessor 說白了就是可以在初始化Bean之前修改Bean的屬性,甚至替換原先準備要執行個體化的bean。
實戰示範:
假設jar包中有一個類 MyTestService,正常情況下它會被spring自動掃描到注入IOC容器中去。
packagecom.middol.mytest.config.beantest.register.jar;
importorg.springframework.stereotype.Service;
importjavax.annotation.PostConstruct;
importjavax.annotation.PreDestroy;
@Service("myTestService")
publicclassMyTestService{
privateStringname1;
privateStringname2;
privateStringname3;
publicMyTestService(){
this.name1 ="";
this.name2 ="";
this.name3 ="";
}
publicMyTestService(Stringname1,Stringname2,Stringname3){
this.name1 =name1;
this.name2 =name2;
this.name3 =name3;
}
@PostConstruct
publicvoidinit(){
System.out.println("MyTestService init");
}
@PreDestroy
publicvoiddestory(){
System.out.println("MyTestService destroy");
}
publicvoidshow(){
System.out.println("------------------------");
System.out.println("我是jar中通過注解@Service主動加入Spring的IOC裡面的");
System.out.println("------------------------");
}
publicStringgetName1(){
returnname1;
}
publicvoidsetName1(Stringname1){
this.name1 =name1;
}
publicStringgetName2(){
returnname2;
}
publicvoidsetName2(Stringname2){
this.name2 =name2;
}
publicStringgetName3(){
returnname3;
}
publicvoidsetName3(Stringname3){
this.name3 =name3;
}
}
自己工程中繼承該類,并且覆寫裡面的show中的方法
packagecom.middol.mytest.config.beantest.register;
importcom.middol.mytest.config.beantest.register.jar.MyTestService;
publicclassMyTestServiceIpmlextendsMyTestService{
publicMyTestServiceIpml(){
}
publicMyTestServiceIpml(Stringname1,Stringname2,Stringname3){
super(name1,name2,name3);
}
@Override
publicvoidshow(){
System.out.println("------------------------");
System.out.println("我是被BeanDefinitionRegistry手動注冊到Spring的IOC裡面的");
System.out.println("------------------------");
}
}
然後 實作 BeanDefinitionRegistryPostProcessor 接口,修改原來bean定義,主要檢視postProcessBeanDefinitionRegistry方法的實作,先清空原bean定義,注冊我們自己的bean定義來達到替換的目的。
packagecom.middol.mytest.config.beantest.register;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.BeansException;
importorg.springframework.beans.factory.config.ConfigurableListableBeanFactory;
importorg.springframework.beans.factory.support.BeanDefinitionBuilder;
importorg.springframework.beans.factory.support.BeanDefinitionRegistry;
importorg.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
importorg.springframework.stereotype.Component;
importorg.springframework.web.bind.annotation.RestController;
importjava.util.Map;
@Component
publicclassMyBeanDefinitionRegistryPostProcessorimplementsBeanDefinitionRegistryPostProcessor{
privateLoggerlogger =LoggerFactory.getLogger(this.getClass());
@Override
publicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistrybeanDefinitionRegistry)throwsBeansException{
logger.info("bean 定義檢視和修改...");
StringbeanName ="myTestService";
// 先移除原來的bean定義
beanDefinitionRegistry.removeBeanDefinition(beanName);
// 注冊我們自己的bean定義
BeanDefinitionBuilderbeanDefinitionBuilder =BeanDefinitionBuilder.rootBeanDefinition(MyTestServiceIpml.class);
// 如果有構造函數參數, 有幾個構造函數的參數就設定幾個 沒有就不用設定
beanDefinitionBuilder.addConstructorArgValue("構造參數1");
beanDefinitionBuilder.addConstructorArgValue("構造參數2");
beanDefinitionBuilder.addConstructorArgValue("構造參數3");
// 設定 init方法 沒有就不用設定
beanDefinitionBuilder.setInitMethodName("init");
// 設定 destory方法 沒有就不用設定
beanDefinitionBuilder.setDestroyMethodName("destory");
// 将Bean 的定義注冊到Spring環境
beanDefinitionRegistry.registerBeanDefinition("myTestService",beanDefinitionBuilder.getBeanDefinition());
}
@Override
publicvoidpostProcessBeanFactory(ConfigurableListableBeanFactoryconfigurableListableBeanFactory)throwsBeansException{
// bean的名字為key, bean的執行個體為value
MapbeanMap =configurableListableBeanFactory.getBeansWithAnnotation(RestController.class);
logger.info("所有 RestController 的bean {}",beanMap);
}
}
寫一個 業務類BusinessTestService測試一下,期望結果:所有用到 MyTestService的地方實際調用的變成了MyTestServiceIpml裡面的方法。
packagecom.middol.mytest.config.beantest.register;
importcom.middol.mytest.config.beantest.register.jar.MyTestService;
importorg.springframework.stereotype.Service;
importjavax.annotation.PostConstruct;
importjavax.annotation.Resource;
@Service
publicclassBusinessTestService{
@Resource
privateMyTestServicemyTestService;
@PostConstruct
publicvoidinit(){
System.out.println(myTestService.getName1());
System.out.println(myTestService.getName2());
System.out.println(myTestService.getName3());
// 看看到底是哪一個Bean
myTestService.show();
}
}
控制台列印如下:
可以發現,和我們期望的結果的一樣:所有用到 MyTestService的地方實際調用的變成了MyTestServiceIpml裡面的方法 !
OVER …
轉自:https://www.wangt.cc/2020/10/%E8%A6%86%E7%9B%96%E9%87%8D%E5%86%99-%E5%8E%9F%E6%9C%89spring-bean%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F/