iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >如何动态替换Spring容器中的Bean
  • 562
分享到

如何动态替换Spring容器中的Bean

2024-04-02 19:04:59 562人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

目录动态替换spring容器中的Bean原因方案实现Spring中bean替换问题动态替换Spring容器中的Bean 原因 最近在编写单测时,发现使用 Mock 工具预定义 Ser

动态替换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 注解


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FakeBeanFor {
    String value(); // 需要替换的 Bean 的名称
}

在 Spring 容器注册完所有的 Bean 后,解析 classpath 下引入 @FakeBeanFor 注解的类,使用 @FakeBeanFor 注解标记的 Bean 替换 value 中指定名称的 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到容器中。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。 

--结束END--

本文标题: 如何动态替换Spring容器中的Bean

本文链接: https://www.lsjlt.com/news/166182.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何动态替换Spring容器中的Bean
    目录动态替换Spring容器中的Bean原因方案实现Spring中bean替换问题动态替换Spring容器中的Bean 原因 最近在编写单测时,发现使用 Mock 工具预定义 Ser...
    99+
    2024-04-02
  • Spring容器中已经存在的Bean替换示例
    目录一、背景二、需求三、实现思路四、实现步骤1、模拟第三方jar包实现并加入Spring容器中2、自己提供一个实现3、替换掉jar包默认的实现4、进行测试一、背景 我们在开发的过程中...
    99+
    2024-04-02
  • BeanDefinitionRegistryPostProcessor如何动态注册Bean到Spring
    目录1、理论2、实战代码总结下1、理论 一般如果想将类注册到spring容器,让spring来完成实例化,常用方式如下: xml中通过bean节点来配置;使用@Service、@Co...
    99+
    2024-04-02
  • Spring中动态注入Bean的方法
    这篇文章将为大家详细讲解有关Spring中动态注入Bean的方法,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。一、基于标注的方式注入实例需要在Bean初始化之时,其依赖的对象必须初始化完毕。如果被注入的对...
    99+
    2023-05-30
    spring bean
  • Spring Boot如何动态创建Bean示例代码
    前言本文主要给大家介绍了关于Spring Boot动态创建Bean的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。SpringBoot测试版本:1.3.4.RELEASE参考代码如下:package com.sp...
    99+
    2023-05-31
    springboot 动态创建 bean
  • 向Spring IOC容器动态注册bean实现方式是什么
    本篇内容主要讲解“向Spring IOC容器动态注册bean实现方式是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“向Spring IOC容器动态注册bean实现方式是什...
    99+
    2023-07-02
  • spring hibernate实现动态替换表名(分表)的方法
    1.概述其实最简单的办法就是使用原生sql,如 session.createSQLQuery("sql"),或者使用jdbcTemplate。但是项目中已经使用了hql的方式查询,修改起来又累,风险又大!所以,必须找到一种比较好的解决方案,...
    99+
    2023-05-31
    spring hibernate 动态
  • 利用JSP 如何实现获取spring容器中的bean
    这篇文章给大家介绍利用JSP 如何实现获取spring容器中的bean,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。JSP 获取spring容器中bean的方法总结方案1(Web中使用):ApplicationCont...
    99+
    2023-05-31
    jsp spring bean
  • jquery如何替换文本中的内容
    本篇内容介绍了“jquery如何替换文本中的内容”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! ...
    99+
    2024-04-02
  • 如何动态创建和修改Spring的bean配置文件
    这篇文章给大家介绍如何动态创建和修改Spring的bean配置文件,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。今天本来打算写Spring温故知新系列的第二篇,不过突然想起一直都忘了学怎么用java来操作XML,这么重...
    99+
    2023-06-17
  • 我是如何替换Spring Cloud Netflix的?
    作者:Piotr Mińkowski,“Mastering Spring Cloud”一书作者原文链接:https://dzone.com/articles/microservices-with-spring-cloud-alibaba如果...
    99+
    2023-06-05
  • Spring容器中添加bean的5种方式
    目录@Configuration + @Bean @Componet + @ComponentScan @Import注解导入 @Import直接导入类 @Import + Impo...
    99+
    2024-04-02
  • jquery如何替换a标签中间的内容
    这篇文章主要介绍jquery如何替换a标签中间的内容,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完! jquery替换a标签中间内容的方法:1、利用“$(a...
    99+
    2024-04-02
  • spring IOC容器的Bean管理XML自动装配过程
    目录什么是自动装配?自动装配过程1. 创建 2 个类2. 配置文件3. 测试方法什么是自动装配? 在之前的内容中,每给属性注入值都要一个个的用 property 标签来完成,比如: ...
    99+
    2024-04-02
  • JavaScript中替换所有匹配内容及正则替换如何实现
    小编给大家分享一下JavaScript中替换所有匹配内容及正则替换如何实现,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!由于JavaScript 的 replace 只能替换一次,因此另外...
    99+
    2024-04-02
  • Spring容器中添加bean的方式有哪些
    这篇文章主要介绍“Spring容器中添加bean的方式有哪些”,在日常操作中,相信很多人在Spring容器中添加bean的方式有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Spring容器中添加bean...
    99+
    2023-06-20
  • python如何替换字符串的内容
    Python中可以使用replace()函数来替换字符串的内容。replace()函数接受两个参数,第一个参数是要被替换的字符串,第...
    99+
    2024-02-29
    python
  • jquery如何替换标签里的内容
    jquery如何替换标签里的内容,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。在jquery中,可以利用html()方法替换标...
    99+
    2024-04-02
  • Sql如何批量替换所有表中内容
    这篇文章主要讲解了“Sql如何批量替换所有表中内容”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Sql如何批量替换所有表中内容”吧! ...
    99+
    2024-04-02
  • 教你如何用Java替换Word中带有${}的内容
    目录一、概述二、处理含List的内容2.1概述2.2处理方式三、程序处理3.1引入pom.xml3.2实体bean:UserBean3.3替换工具类:WordUtils一、概述 1、...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作