亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么動態替換Spring容器中的Bean

發布時間:2022-08-29 17:17:20 來源:億速云 閱讀:215 作者:iii 欄目:開發技術

這篇文章主要講解了“怎么動態替換Spring容器中的Bean”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“怎么動態替換Spring容器中的Bean”吧!

    動態替換Spring容器中的Bean

    原因

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

    方案

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

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

    實現

    創建 Mock 注解

    /**
     * 為指定的 Bean 創建 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 動態注入容器
    // 通過 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();
                    // 獲取類名,并創建 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,這樣就可以只在單測環境下的 application.properties 文件中設置指定條件使得該 Configuration 生效。

    注意:

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

    舉例

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

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

    在單測環境下希望能夠改變它的行為,但是又不想修改這個類本身,則可以使用 @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!”
        }
    }

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

    Spring中bean替換問題

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

    項目原因,引用了一些成型產品的jar包,已經不能對其進行修改了。

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

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

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

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

    實現的方法如下:

    @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());
                    //這里要手動注入新類里面的依賴關系
                    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到容器中。

    感謝各位的閱讀,以上就是“怎么動態替換Spring容器中的Bean”的內容了,經過本文的學習后,相信大家對怎么動態替換Spring容器中的Bean這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    邢台县| 葫芦岛市| 福泉市| 上犹县| 斗六市| 兰州市| 杭州市| 凤阳县| 奉贤区| 平陆县| 紫阳县| 蓬莱市| 汾阳市| 云浮市| 南京市| 饶平县| 南开区| 岳阳市| 晋州市| 富阳市| 连平县| 仁寿县| 西吉县| 新竹市| 基隆市| 天祝| 清镇市| 和政县| 图们市| 南漳县| 双流县| 西林县| 六盘水市| 广饶县| 万荣县| 呼图壁县| 泸水县| 沛县| 唐海县| 平果县| 伊春市|