最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

原因

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

方案

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

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

實現(xiàn)

創(chuàng)建 Mock 注解

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

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

/**
 * 從當前 classpath 讀取 @FakeBeanFor 注解的類,并替換指定名稱的 bean
 */
@Slf4j
@Configuration
@ConditionalOnExpression("${unitcases.enable.fake:true}")
// 通過 BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry 可以將 Bean 動態(tài)注入容器
// 通過 BeanFactoryAware 可以自動注入 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);
            // 設置過濾器。僅掃描 @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 對象
                String className = definition.getBeanClassName();
                Class<?> clazz = classNameToClass(className);
                // 解析注解上的 value
                FakeBeanFor annotation = clazz.getAnnotation(FakeBeanFor.class);
                if (annotation == null || StringUtils.isEmpty(annotation.value())) {
                    continue;
                }
                // 使用當前加載的 @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;
        }
        // 反射通過 class 名稱獲取 Class 對象
        private Class<?> classNameToClass(String className) {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                log.error("create instance failed.", e);
            }
            return null;
        }
    }
}

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

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

注意:

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

舉例

假設在容器中定義如下 Service:

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

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

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

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

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

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

Spring中bean替換問題

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

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

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

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

jar包中的老對象是通過@Service注冊到容器中的。

新的類因為是手動配置,不需要添加任何注解。

實現(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("對象" + beanName + "開始實例化");
        System.out.println("類名" + bean.getClass().getName() + "是啥");
        if(StringUtils.contains(value,bean.getClass().getName())){
            System.out.println("找到了需要進行替換的類。。。。。。。。。。。");
            boolean containsBean = defaultListableBeanFactory.containsBean(beanName);
            if (containsBean) {
                //移除bean的定義和實例
                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();
            //注冊新的bean定義和實例
                defaultListableBeanFactory.registerBeanDefinition(beanName, BeanDefinitionBuilder.genericBeanDefinition(tar.getClass()).getBeanDefinition());
                //這里要手動注入新類里面的依賴關(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

在啟動的時候,會找到容器中的老的bean,將其remove掉,然后手動注冊新的bean到容器中。

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

相關(guān)文章

  • Spring七大組件是哪些以及作用

    Spring七大組件是哪些以及作用

    這篇文章主要介紹了Spring七大組件是哪些以及作用,幫助剛接觸spring的朋友更快理解,如果有說的不對的地方還請指正,需要的朋友可以參考下
    2023-03-03
  • Spring中容器創(chuàng)建的四種方式示例

    Spring中容器創(chuàng)建的四種方式示例

    這篇文章主要介紹了Spring中容器創(chuàng)建的四種方式示例,Spring容器是Spring框架的核心部分,它負責管理和組織應用程序中的對象,它提供了一種輕量級的、非侵入式的方式來實現(xiàn)對象的創(chuàng)建、依賴注入和生命周期管理,需要的朋友可以參考下
    2023-10-10
  • idea中maven項目模塊變成灰色原因及解決方案

    idea中maven項目模塊變成灰色原因及解決方案

    這篇文章主要介紹了idea中maven項目模塊變成灰色原因及解決方案,文中通過圖文結(jié)合的方式給大家講解的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下
    2024-03-03
  • Java文件(io)編程_文件字節(jié)流的使用方法

    Java文件(io)編程_文件字節(jié)流的使用方法

    下面小編就為大家?guī)硪黄狫ava文件(io)編程_文件字節(jié)流的使用方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • SpringBoot解決jar包沖突的問題,簡單有效

    SpringBoot解決jar包沖突的問題,簡單有效

    這篇文章主要介紹了SpringBoot解決jar包沖突的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 為什么不要使用 async void的原因分析

    為什么不要使用 async void的原因分析

    這篇文章主要介紹了為什么不要使用 async void的原因分析及解決方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-04-04
  • java中instanceof 關(guān)鍵字作用和實際用途詳解

    java中instanceof 關(guān)鍵字作用和實際用途詳解

    這篇文章主要介紹了java中instanceof 關(guān)鍵字作用和實際用途,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • JAVA HTTP反向代理實現(xiàn)過程詳解

    JAVA HTTP反向代理實現(xiàn)過程詳解

    這篇文章主要介紹了JAVA HTTP反向代理實現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-06-06
  • java時間格式的簡單整理

    java時間格式的簡單整理

    這篇文章主要介紹了java時間格式的簡單整理,文中通過示例介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考一下
    2019-06-06
  • 詳解Spring Boot最核心的27個注解,你了解多少?

    詳解Spring Boot最核心的27個注解,你了解多少?

    這篇文章主要介紹了詳解Spring Boot最核心的27個注解,你了解多少?文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-08-08

最新評論

海盐县| 长治县| 大名县| 梁平县| 墨竹工卡县| 汉阴县| 综艺| 禄劝| 苏尼特右旗| 利川市| 宜宾县| 新邵县| 无为县| 台安县| 宁南县| 济宁市| 长海县| 华宁县| 繁昌县| 普兰店市| 交城县| 南江县| 安陆市| 涞水县| 纳雍县| 大庆市| 都兰县| 绩溪县| 昌黎县| 仙居县| 无为县| 贵港市| 永登县| 普洱| 泗洪县| 修文县| 专栏| 依兰县| 义乌市| 东阳市| 乌拉特前旗|