Java 在运行时替换bean

Java 在运行时替换bean,java,spring,unit-testing,Java,Spring,Unit Testing,代码库是典型的基于spring的企业代码库,大约有150万行代码。我们有很多spring上下文文件。下面的测试是个问题 对于测试用例,我创建了另一组测试spring文件(主要是导入相关的项目spring上下文),对于少数bean,它包含用于外部服务的模拟bean。所有测试类都使用相同的上下文配置文件集,并且90%的情况下都是这样 但在某些情况下,我会嘲笑一个豆子。但我不希望编辑spring-text.xml(因为它会干扰所有类),也不希望每个测试类都有一组单独的xml。一个非常简单的说法是: @

代码库是典型的基于spring的企业代码库,大约有150万行代码。我们有很多spring上下文文件。下面的测试是个问题

对于测试用例,我创建了另一组测试spring文件(主要是导入相关的项目spring上下文),对于少数bean,它包含用于外部服务的模拟bean。所有测试类都使用相同的上下文配置文件集,并且90%的情况下都是这样

但在某些情况下,我会嘲笑一个豆子。但我不希望编辑spring-text.xml(因为它会干扰所有类),也不希望每个测试类都有一组单独的xml。一个非常简单的说法是:

@Autowired
@Qualifier("fundManager")
FundManager fundManager;

@Test
public void testSomething(){
    TransactionManager tx = mock(TransactionManager.class);
    fundManager.setTransactionManager(tx);
    //now all is well.
}
这在某些情况下有效。但有时,需要将这个新的临时bean
tx
设置在整个代码库中使用TransactionManagerbean的地方

代理类IMHO不是一个很好的解决方案,因为这样我就必须用包装器包装所有bean。这正是我理想的追求:

@Test
public void testSomething(){
    TransactionManager tx = mock(TransactionManager.class);
    replace("transactionManagerBean",tx);
    //For bean with id:transactionManagerBean should be replace with `tx`
}

BeanPostProcessor
看起来像是另一个建议,但我遇到了一些问题。

想象一下你将bean a注入到bean B中:

public static class A {
}

public static class B {
    @Autowired
    private A a;

    @Override
    public String toString() {
        return "B [a=" + a + ']';
    }
}
和spring上下文来初始化应用程序:

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <context:annotation-config/>
    <bean id="a" class="test.Test$A"/>
    <bean id="b" class="test.Test$B"/>
</beans>

这个例子是用Java8+Spring4.1制作的。但是,修改Java和Spring的旧版本的代码很简单。

为什么不创建一个测试配置来覆盖/替换所需的bean?这基本上是开箱即用的,没有任何黑客攻击。在您的测试用例中,假设您使用XML add
@ImportResource
加载配置,则创建一个带有
@Configuration
注释的
公共静态类。现在为要覆盖的Bean定义
@Bean
方法
@Bean public TransactionManager TransactionManager(){return mock(TransactionManager.class);}
@M.Deinum谢谢。是的,这是可行的,但这也意味着我会为每个测试类文件设置不同的配置类,因此会产生类污染。为什么试图替换Bean的污染会更糟呢?其次,在您的解决方案中,将
@Bean public TransactionManager TransactionManager(){return mock(TransactionManager.class)}
似乎没有用xml中的id
transactionManager
覆盖bean。它仍然引用xml中提供的bean,这取决于您如何加载配置。您必须在测试类上放置一个空的
@ContextConfiguration
,否则您的配置将不会被使用
public static void main(String[] args) throws Exception {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("test.xml");

    System.out.println(ctx.getBean("b"));

    final A replacement = new A();
    for (String name : ctx.getBeanDefinitionNames()) {
        final Object bean = ctx.getBean(name);
        ReflectionUtils.doWithFields(bean.getClass(),
            field -> {
                field.setAccessible(true);
                field.set(bean, replacement);
            },
            // Here you can provide your filtering.
            field -> field.getType().equals(A.class)
        );
    }

    System.out.println(ctx.getBean("b"));
}