欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

如何動(dòng)態(tài)替換Spring容器中的Bean

 更新時(shí)間:2022年08月27日 16:46:11   作者:涼茶方便面  
這篇文章主要介紹了如何動(dòng)態(tài)替換Spring容器中的Bean,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

動(dòng)態(tài)替換Spring容器中的Bean

原因

最近在編寫單測(cè)時(shí),發(fā)現(xiàn)使用 Mock 工具預(yù)定義 Service 中方法的行為特別難用,而且無(wú)法精細(xì)化的實(shí)現(xiàn)自定義的行為,因此想要在 Spring 容器運(yùn)行過(guò)程中使用自定義 Mock 對(duì)象,該對(duì)象能夠代替實(shí)際的 Bean 的給定方法。

方案

創(chuàng)建一個(gè) Mock 注解,并且在 Spring 容器注冊(cè)完所有的 Bean 之后,解析 classpath 下所有引入該 Mock 注解的類,使用 Mock 注解標(biāo)記的 Bean 替換注解中指定名稱的 Bean。

這種方式類似于 mybatis-spring 動(dòng)態(tài)解析 @Mapper 注解的方法(MapperScannerRegistrar 實(shí)現(xiàn)了@Mapper 注解的掃描),但是不一樣的是 mybatis-spring 使用工廠類替換接口類,而我們是用 Mock 的 Bean 替換實(shí)際的 Bean。

實(shí)現(xiàn)

創(chuàng)建 Mock 注解

/**
 * 為指定的 Bean 創(chuàng)建 Mock 對(duì)象,需要繼承原始 Bean
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FakeBeanFor {
    String value(); // 需要替換的 Bean 的名稱
}

在 Spring 容器注冊(cè)完所有的 Bean 后,解析 classpath 下引入 @FakeBeanFor 注解的類,使用 @FakeBeanFor 注解標(biāo)記的 Bean 替換 value 中指定名稱的 Bean。

/**
 * 從當(dāng)前 classpath 讀取 @FakeBeanFor 注解的類,并替換指定名稱的 bean
 */
@Slf4j
@Configuration
@ConditionalOnExpression("${unitcases.enable.fake:true}")
// 通過(guò) BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry 可以將 Bean 動(dòng)態(tài)注入容器
// 通過(guò) BeanFactoryAware 可以自動(dòng)注入 BeanFactory
public class FakeBeanConfiguration implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
    private BeanFactory beanFactory;
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        log.debug("searching for classes annotated with @FakeBeanFor");
        // 自定義 Scanner 掃描 classpath 下的指定注解
        ClassPathFakeAnnotationScanner scanner = new ClassPathFakeAnnotationScanner(registry);
        try {
            List<String> packages = AutoConfigurationPackages.get(this.beanFactory); // 獲取包路徑
            if (log.isDebugEnabled()) {
                for (String pkg : packages) {
                    log.debug("Using auto-configuration base package: {}", pkg);
                }
            }
            scanner.doScan(StringUtils.toStringArray(packages)); // 掃描所有加載的包
        } catch (IllegalStateException ex) {
            log.debug("could not determine auto-configuration package, automatic fake scanning disabled.", ex);
        }
    }
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
        // empty
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
    private static class ClassPathFakeAnnotationScanner extends ClassPathBeanDefinitionScanner {
        ClassPathFakeAnnotationScanner(BeanDefinitionRegistry registry) {
            super(registry, false);
            // 設(shè)置過(guò)濾器。僅掃描 @FakeBeanFor
            addIncludeFilter(new AnnotationTypeFilter(FakeBeanFor.class));
        }
        @Override
        public Set<BeanDefinitionHolder> doScan(String... basePackages) {
            List<String> fakeClassNames = new ArrayList<>();
            // 掃描全部 package 下 annotationClass 指定的 Bean
            Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
            GenericBeanDefinition definition;
            for (BeanDefinitionHolder holder : beanDefinitions) {
                definition = (GenericBeanDefinition) holder.getBeanDefinition();
                // 獲取類名,并創(chuàng)建 Class 對(duì)象
                String className = definition.getBeanClassName();
                Class<?> clazz = classNameToClass(className);
                // 解析注解上的 value
                FakeBeanFor annotation = clazz.getAnnotation(FakeBeanFor.class);
                if (annotation == null || StringUtils.isEmpty(annotation.value())) {
                    continue;
                }
                // 使用當(dāng)前加載的 @FakeBeanFor 指定的 Bean 替換 value 里指定名稱的 Bean
                if (getRegistry().containsBeanDefinition(annotation.value())) {
                    getRegistry().removeBeanDefinition(annotation.value());
                    getRegistry().registerBeanDefinition(annotation.value(), definition);
                    fakeClassNames.add(clazz.getName());
                }
            }
            log.info("found fake beans: " + fakeClassNames);
            return beanDefinitions;
        }
        // 反射通過(guò) class 名稱獲取 Class 對(duì)象
        private Class<?> classNameToClass(String className) {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                log.error("create instance failed.", e);
            }
            return null;
        }
    }
}

有點(diǎn)兒不一樣的是這是一個(gè)配置類,將它放置到 Spring 的自動(dòng)掃描路徑上,就可以自動(dòng)掃描 classpath 下 @FakeBeanFor 指定的類,并將其加載為 BeanDefinition。

在 FakeBeanConfiguration 上還配置了 ConditionalOnExpression,這樣就可以只在單測(cè)環(huán)境下的 application.properties 文件中設(shè)置指定條件使得該 Configuration 生效。

注意:

  • 這里 unitcases.enable.fake:true 默認(rèn)開啟了替換,如果想要默認(rèn)關(guān)閉則需要設(shè)置 unitcases.enable.fake:false,并且在單測(cè)環(huán)境的 application.properties 文件設(shè)置 unitcases.enable.fake=true。

舉例

假設(shè)在容器中定義如下 Service:

@Service
public class HelloService {
    public void sayHello() {
        System.out.println("hello real world!");
    }
}

在單測(cè)環(huán)境下希望能夠改變它的行為,但是又不想修改這個(gè)類本身,則可以使用 @FakeBeanFor 注解:

@FakeBeanFor("helloService")
public class FakeHelloService extends HelloService {
    @Override
    public void sayHello() {
        System.out.println("hello fake world!");
    }
}

通過(guò)繼承實(shí)際的 Service,并覆蓋 Service 的原始方法,修改其行為。在單測(cè)中可以這樣使用:

@SpringBootTest
@RunWith(SpringRunner.class)
public class FakeHelloServiceTest {
    @Autowired
    private HelloService helloService;
    
    @Test
    public void testSayHello() {
        helloService.sayHello(); // 輸出:“hello fake world!”
    }
}

總結(jié):通過(guò)自定義的 Mock 對(duì)象動(dòng)態(tài)替換實(shí)際的 Bean 可以實(shí)現(xiàn)單測(cè)環(huán)境下比較難以使用 Mock 框架實(shí)現(xiàn)的功能,如將原本的異步調(diào)用邏輯修改為同步調(diào)用,避免單測(cè)完成時(shí),異步調(diào)用還未執(zhí)行完成的場(chǎng)景。

Spring中bean替換問(wèn)題

需求:通過(guò)配置文件,能夠使得新的一個(gè)service層類替代jar包中原有的類文件。

項(xiàng)目原因,引用了一些成型產(chǎn)品的jar包,已經(jīng)不能對(duì)其進(jìn)行修改了。

故,考慮采用用新的類替換jar包中的類。

實(shí)現(xiàn)思路:在配置文件中配置新老類的全類名,讀取配置文件后,通過(guò)spring初始化bean的過(guò)程中,移除spring容器中老類的bean對(duì)象,手動(dòng)注冊(cè)新對(duì)象進(jìn)去,bean名稱和老對(duì)象一致即可。

jar包中的老對(duì)象是通過(guò)@Service注冊(cè)到容器中的。

新的類因?yàn)槭鞘謩?dòng)配置,不需要添加任何注解。

實(shí)現(xiàn)的方法如下:

@Component
public class MyBeanPostProcessor implements ApplicationContextAware, BeanPostProcessor {
    @Autowired
    private AutowireCapableBeanFactory beanFactory;
    @Autowired
    private DefaultListableBeanFactory defaultListableBeanFactory;
    static HashMap ReplaceClass;
    static  String value = null;
    static {
        try {
            value = PropertiesLoaderUtils.loadAllProperties("你的配置文件路徑").getProperty("replaceClass");
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("properties value........"+value);
    }
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("對(duì)象" + beanName + "開始實(shí)例化");
        System.out.println("類名" + bean.getClass().getName() + "是啥");
        if(StringUtils.contains(value,bean.getClass().getName())){
            System.out.println("找到了需要進(jìn)行替換的類。。。。。。。。。。。");
            boolean containsBean = defaultListableBeanFactory.containsBean(beanName);
            if (containsBean) {
                //移除bean的定義和實(shí)例
                defaultListableBeanFactory.removeBeanDefinition(beanName);
            }
            String temp = value;
            String tar_class = temp.split(bean.getClass().getName())[1].split("#")[1].split(",")[0];
            System.out.println(tar_class);
            try {
            Class tar = Class.forName(tar_class);
            Object obj = tar.newInstance();
            //注冊(cè)新的bean定義和實(shí)例
                defaultListableBeanFactory.registerBeanDefinition(beanName, BeanDefinitionBuilder.genericBeanDefinition(tar.getClass()).getBeanDefinition());
                //這里要手動(dòng)注入新類里面的依賴關(guān)系
                beanFactory.autowireBean(obj);
                return obj;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    }

配置文件中的格式采用下面的樣式 :

replaceClass=gov.df.fap.service.OldTaskBO#gov.df.newmodel.service.NewTaskBO

在啟動(dòng)的時(shí)候,會(huì)找到容器中的老的bean,將其remove掉,然后手動(dòng)注冊(cè)新的bean到容器中。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。 

相關(guān)文章

  • 五分鐘教你手寫 SpringBoot 本地事務(wù)管理實(shí)現(xiàn)

    五分鐘教你手寫 SpringBoot 本地事務(wù)管理實(shí)現(xiàn)

    這篇文章主要介紹了五分鐘教你手寫 SpringBoot 本地事務(wù)管理實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • 詳解Eclipse提交項(xiàng)目到GitHub以及解決代碼沖突

    詳解Eclipse提交項(xiàng)目到GitHub以及解決代碼沖突

    這篇文章主要介紹了詳解Eclipse提交項(xiàng)目到GitHub以及解決代碼沖突,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-03-03
  • java 創(chuàng)建自定義數(shù)組

    java 創(chuàng)建自定義數(shù)組

    本篇文章是關(guān)于java 如何自己創(chuàng)建自定義數(shù)組,這里給大家一個(gè)小實(shí)例,希望能幫助有所需要的同學(xué)
    2016-07-07
  • java中給實(shí)體對(duì)象屬性的空值賦默認(rèn)值

    java中給實(shí)體對(duì)象屬性的空值賦默認(rèn)值

    這篇文章主要介紹了java中給實(shí)體對(duì)象屬性的空值賦默認(rèn)值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Java中Date和LocalDate區(qū)別與介紹

    Java中Date和LocalDate區(qū)別與介紹

    本文主要介紹了Java中Date和LocalDate區(qū)別與介紹,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • spring?boot集成jasypt?并實(shí)現(xiàn)自定義加解密的詳細(xì)步驟

    spring?boot集成jasypt?并實(shí)現(xiàn)自定義加解密的詳細(xì)步驟

    由于項(xiàng)目中的配置文件?配置的地方過(guò)多,現(xiàn)將配置文件統(tǒng)一放到nacos上集中管理?且密碼使用加密的方式放在配置文件中,配置文件的加密使用加密庫(kù)jasypt,本文給大家介紹spring boot集成jasypt并實(shí)現(xiàn)自定義加解密,感興趣的朋友一起看看吧
    2023-08-08
  • 校驗(yàn)非空的注解@NotNull如何取得自定義的message

    校驗(yàn)非空的注解@NotNull如何取得自定義的message

    這篇文章主要介紹了校驗(yàn)非空的注解@NotNull如何取得自定義的message,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • JAVA8 STREAM COLLECT GROUPBY分組實(shí)例解析

    JAVA8 STREAM COLLECT GROUPBY分組實(shí)例解析

    這篇文章主要介紹了JAVA8 STREAM COLLECT GROUPBY分組實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • java實(shí)現(xiàn)的二級(jí)聯(lián)動(dòng)菜單效果

    java實(shí)現(xiàn)的二級(jí)聯(lián)動(dòng)菜單效果

    這篇文章主要介紹了java實(shí)現(xiàn)的二級(jí)聯(lián)動(dòng)菜單效果,結(jié)合實(shí)例形式分析了java前臺(tái)頁(yè)面布局及與后臺(tái)交互構(gòu)造聯(lián)動(dòng)菜單的相關(guān)技巧,需要的朋友可以參考下
    2016-08-08
  • ElasticSearch學(xué)習(xí)之多條件組合查詢驗(yàn)證及示例分析

    ElasticSearch學(xué)習(xí)之多條件組合查詢驗(yàn)證及示例分析

    這篇文章主要為大家介紹了ElasticSearch 多條件組合查詢驗(yàn)證及示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02

最新評(píng)論