天天看點

java怎麼重寫覆寫jar_覆寫重寫 原有Spring Bean的幾種方式

目錄

場景

方法1 直接在自己工程中建同包同類名的類進行替換

方法2 采用@Primary注解

方法3 排除需要替換的jar包中的類

方法4 @Bean 覆寫

方法5 使用BeanDefinitionRegistryPostProcessor

場景

什麼情況下要覆寫原有的Spring Bean ? 例如引入的第三方jar包中的某個類有些問題,然有沒有源碼提供或者嫌編譯源碼太費事,這個時間可以考慮覆寫原有的類。

方法1 直接在自己工程中建同包同類名的類進行替換

方式簡單粗暴,可以直接覆寫掉jar包中的類,spring項目會優先加載自定義的類。下面是覆寫 flowable架構中的一個類 FlowableCookieFilter,主要是想修改它裡面的redirectToLogin方法的業務邏輯。包路徑為 org.flowable.ui.common.filter, 直接工程裡面建立一樣路徑一樣類名FlowableCookieFilter即可。

java怎麼重寫覆寫jar_覆寫重寫 原有Spring Bean的幾種方式

方法2 采用@Primary注解

該方法适用于接口實作類,自己建立一個原jar包接口的實作類,然後類上加上@Primary注解,spring則預設加載該類執行個體化出的Bean。下面的例子: 一個接口 RemoteIdmService,原先jar包中隻有一個實作類 RemoteIdmServiceImpl,現在自己工程裡面建立一個 CustomRemoteIdmServiceImpl 繼承RemoteIdmService接口,然後發現所有調用RemoteIdmService接口裡面的方法實際調用走的是CustomRemoteIdmServiceImpl 裡面的方法。

java怎麼重寫覆寫jar_覆寫重寫 原有Spring Bean的幾種方式
java怎麼重寫覆寫jar_覆寫重寫 原有Spring Bean的幾種方式

方法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包中的配置類:

java怎麼重寫覆寫jar_覆寫重寫 原有Spring Bean的幾種方式

直接繼承要覆寫的類,自己重寫裡面方法,使用@Component注入到spring中去

java怎麼重寫覆寫jar_覆寫重寫 原有Spring Bean的幾種方式

方法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();

}

}

控制台列印如下:

java怎麼重寫覆寫jar_覆寫重寫 原有Spring Bean的幾種方式

可以發現,和我們期望的結果的一樣:所有用到 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/