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

如何動(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

原因

最近在編寫(xiě)單測(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 注解的類(lèi),使用 Mock 注解標(biāo)記的 Bean 替換注解中指定名稱的 Bean。

這種方式類(lèi)似于 mybatis-spring 動(dòng)態(tài)解析 @Mapper 注解的方法(MapperScannerRegistrar 實(shí)現(xiàn)了@Mapper 注解的掃描),但是不一樣的是 mybatis-spring 使用工廠類(lèi)替換接口類(lèi),而我們是用 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 注解的類(lèi),使用 @FakeBeanFor 注解標(biāo)記的 Bean 替換 value 中指定名稱的 Bean。

/**
 * 從當(dāng)前 classpath 讀取 @FakeBeanFor 注解的類(lèi),并替換指定名稱的 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();
                // 獲取類(lèi)名,并創(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è)配置類(lèi),將它放置到 Spring 的自動(dòng)掃描路徑上,就可以自動(dòng)掃描 classpath 下 @FakeBeanFor 指定的類(lèi),并將其加載為 BeanDefinition。

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

注意:

  • 這里 unitcases.enable.fake:true 默認(rèn)開(kāi)啟了替換,如果想要默認(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è)類(lèi)本身,則可以使用 @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層類(lèi)替代jar包中原有的類(lèi)文件。

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

故,考慮采用用新的類(lèi)替換jar包中的類(lèi)。

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

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

新的類(lèi)因?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 + "開(kāi)始實(shí)例化");
        System.out.println("類(lèi)名" + bean.getClass().getName() + "是啥");
        if(StringUtils.contains(value,bean.getClass().getName())){
            System.out.println("找到了需要進(jìn)行替換的類(lèi)。。。。。。。。。。。");
            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)注入新類(lèi)里面的依賴關(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)文章

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

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

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

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

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

    idea中maven項(xiàng)目模塊變成灰色原因及解決方案

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

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

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

    SpringBoot解決jar包沖突的問(wèn)題,簡(jiǎn)單有效

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

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

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

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

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

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

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

    java時(shí)間格式的簡(jiǎn)單整理

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

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

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

最新評(píng)論

且末县| 大埔区| 新竹县| 齐齐哈尔市| 博客| 昔阳县| 东丰县| 四子王旗| 莒南县| 婺源县| 延庆县| 忻城县| 秭归县| 南平市| 长寿区| 平谷区| 遵义市| 龙泉市| 蓬溪县| 泉州市| 甘肃省| 栾川县| 永昌县| 光泽县| 宜黄县| 嘉黎县| 惠安县| 神农架林区| 辉南县| 铜鼓县| 湖口县| 万安县| 肇源县| 海盐县| 东辽县| 青河县| 海门市| 浮山县| 湟源县| 内江市| 长春市|