由于代理而导致Spring Bean注入失败

时间:2015-06-04 15:45:50

标签: java spring hibernate junit mockito

Spring Version :3.2.4.RELEASE和3.2.9.RELEASE

Mockito版本:1.8.5

我一直在尝试将H2测试引入旧项目进行集成测试,我遇到了一些问题。由于事务的传播方式,我需要模拟一个自动连接的类。我以前做过这个,但我现在遇到了严重的问题。初始化测试时抛出以下错误消息:

  

org.springframework.beans.factory.BeanCreationException:创建名为'com.stuff.XMLITCase'的bean时出错:注入资源依赖关系失败;嵌套异常是org.springframework.beans.factory.BeanNotOfRequiredTypeException:名为'TheProcessor'的bean必须是[com.stuff.XMLBatchFileProcessor]类型,但实际上是[$ Proxy118]类型       在org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:307)

深入研究这个问题,事实证明这个bean实际上是一个代理。如果我们检查AbstractBeanFactory(第239行),我们可以看到代理:

  

sharedInstance = {$ Proxy117 @ 7035}“com.stuff.XMLBatchFileProcessor@66c540d0”    h = {org.springframework.aop.framework.JdkDynamicAopProxy@7039}

唯一的问题是,我不知道这是从哪里来的。我已经查看了配置和依赖项,并且无法找到应该发生的任何地方。

项目设置

不幸的是我不能为此提供一个示例项目,但我会检查我的测试配置。我有一个我为测试扩展的根类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/spring/spring-test-context.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public abstract class AbstractIntegrationTest {
}

这只是在一些spring配置中加载,并在每次测试后回滚事务。

Spring配置也没什么奇怪的,尽管我的另一个模块和这个模块之间存在一个区别。这是事务管理器和会话工厂:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="hibernateSessionFactory"/>
</bean>

<bean id="hibernateSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
...
</bean>

在我的其他模块中,我使用的是entityManagerFactory,以及不同的事务管理器:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>

实际的类有一些自动连接的字段,以及通常的@Service注释:

@Service(value = "TheProcessor")
public final class XMLBatchFileProcessor extends BatchFileProcessor implements IXMLBatchProcessor {

最后,实际测试如下:

public class XMLITCase extends AbstractIntegrationTest {

    @Resource(name = "TheProcessor")
    @InjectMocks
    private XMLBatchFileProcessor xmlProcessor;

    @Mock
    private ProcessHelper processHelper;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void test() throws Exception {
        Assert.assertNotNull(xmlProcessor);
    }

}

如果我用接口替换XMLBatchFileProcessor并自动加载该字段,那么编译就没有任何问题。但是,Mockito永远不会将自动装配的bean替换为模拟对象。如果确实如此,那么我就不会打扰@Resource注释并命名服务,从而避免代理问题。

对此有任何帮助将是值得赞赏的。我会专注于会议工厂和那里的差异,但很可能我完全错过了其他东西。

修改

继续Sotirios的评论,我今天早上又看了一眼,确实错过了xmlProcessor有一个@Transactional注释,因此意味着该类需要被代理。如果我删除final声明并让CGLib增强它,那么当调用initMocks(this)时,Mockito会替换bean。但是,当调用一个方法时,CGLib似乎用Spring增强版替换所有bean,因此覆盖了Mockito版本。

在具有@Transactional注释的类的集成测试中使用Mockito和Spring的正确方法是什么?

2 个答案:

答案 0 :(得分:9)

好吧,一旦我意识到由于@Transactional注释而导致该类被代理,问题的解决方案就变得更加清晰了。我需要做的是打开代理,并直接在其上设置模拟对象:

所以在我的AbstractIntegrationTest

/**
 * Checks if the given object is a proxy, and unwraps it if it is.
 *
 * @param bean The object to check
 * @return The unwrapped object that was proxied, else the object
 * @throws Exception
 */
public final Object unwrapProxy(Object bean) throws Exception {
    if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
        Advised advised = (Advised) bean;
        bean = advised.getTargetSource().getTarget();
    }
    return bean;
}

然后在我的@Before

@Mock
private ProcessHelper processHelper;

@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);

    IXMLBatchProcessor iXMLBatchProcessor = (IXMLBatchProcessor) unwrapProxy(xmlProcessor);
    ReflectionTestUtils.setField(iXMLBatchProcessor , "processHelper", processHelper);
}

这会保留所有@Autowired类,同时注入正确的模拟对象。

答案 1 :(得分:1)

您可以使用提供方法的类 AopTestUtils 优化接受的响应:

  • getTargetObject 解包顶级代理(如果存在)
  • getUltimateTargetObject 解开所有级别的代理,如果它们
    存在