在运行时替换bean

10

这个代码库是一个典型的基于Spring的企业级代码库,大约有1.5百万行代码。我们有相当多的Spring上下文文件。测试基础设施存在问题。

对于测试用例,我创建了另一组test-spring文件(主要导入相关项目的Spring上下文),并为某些bean包含外部服务的模拟bean。所有测试类都使用相同的上下文配置文件,90%的情况下一切正常。

但在某些情况下,可能会有一个bean需要被mock。但我不希望编辑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.
}

这在某些情况下是有效的。但有时,希望在代码库中使用 TransactionManager bean 的所有地方设置这个新的临时bean tx

代理类在我看来不是一个好的解决方案,因为我将不得不用包装器包装所有的bean。这正是我理想中的解决方案:

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

BeanPostProcessor 看起来是一个替代建议,但我在使用它时遇到了一些小问题。


@M.Deinum谢谢您。是的,那种方法可以解决问题,但这也意味着我会为每个测试类文件拥有不同的配置类集合,从而创建类污染。 - Jatin
如果它是空的,那么它怎么能接收我的其他bean呢?为了清晰起见:http://pastebin.com/WNsijY0p - Jatin
你真的读了我的初始评论吗?在你的测试中添加一个 public static class TestConfig,并用 @Configuration 注释该类。测试支持将自动检测此配置类。 - M. Deinum
啊哈,现在我明白了。我不会仅仅为了每个自定义配置都有一个单独的类而走这条路。如果我可以通过某种机制替换bean并让基础设施完成脏活,那么需要的类和代码就会少得多。 - Jatin
再次感谢您的时间 :) - Jatin
显示剩余6条评论
1个回答

8
想象一下,您已经将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>

接下来的代码片段将会替换上下文中所有的A类bean:

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"));
}

这个示例是使用Java 8 + Spring 4.1创建的。然而,将代码修改为旧版本的Java和Spring也很简单。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接