Spring 由于代理,Bean 注入失败
Spring Bean Injection Failing Due To Proxy
Spring 版本:3.2.4.RELEASE 和 3.2.9.RELEASE
Mockito 版本:1.8.5
我一直在尝试将 H2 测试引入旧项目以进行集成测试,并且 运行 遇到了一些问题。由于交易传播的方式,我需要模拟一个自动装配的 class。我以前做过,但现在 运行 遇到了严重的问题。初始化测试时抛出以下错误消息:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.stuff.XMLITCase': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'TheProcessor' must be of type [com.stuff.XMLBatchFileProcessor], but was actually of type [$Proxy118]
at 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}
唯一的问题是,我不知道这是从哪里来的。我已经检查了配置和依赖项,但找不到应该发生这种情况的任何地方。
项目设置
很遗憾,我无法为此提供示例项目,但我会检查一下我的测试配置。我有一个根class,我为测试扩展了它:
@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>
实际上class有一些自动装配的字段,以及通常的@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
注释,因此意味着 class 需要被代理。如果我删除 final
声明并让 CGLib 增强它,那么 Mockito 会在调用 initMocks(this)
时替换 bean。然而,当一个方法被调用时,CGLib 似乎用 Spring 增强版本替换了所有 beans,因此覆盖了 Mockito 版本。
在带有 @Transactional
注释的 class 的集成测试中使用 Mockito 和 Spring 的正确方法是什么?
好吧,一旦我意识到 class 由于 @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
class 都完好无损,同时注入了正确的模拟对象。
您可以使用提供以下方法的 class AopTestUtils
优化已接受的响应:
getTargetObject
展开顶级代理(如果存在)
getUltimateTargetObject
展开所有级别的代理,如果它们
存在
Spring 版本:3.2.4.RELEASE 和 3.2.9.RELEASE
Mockito 版本:1.8.5
我一直在尝试将 H2 测试引入旧项目以进行集成测试,并且 运行 遇到了一些问题。由于交易传播的方式,我需要模拟一个自动装配的 class。我以前做过,但现在 运行 遇到了严重的问题。初始化测试时抛出以下错误消息:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.stuff.XMLITCase': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'TheProcessor' must be of type [com.stuff.XMLBatchFileProcessor], but was actually of type [$Proxy118] at 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}
唯一的问题是,我不知道这是从哪里来的。我已经检查了配置和依赖项,但找不到应该发生这种情况的任何地方。
项目设置
很遗憾,我无法为此提供示例项目,但我会检查一下我的测试配置。我有一个根class,我为测试扩展了它:
@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>
实际上class有一些自动装配的字段,以及通常的@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
注释,因此意味着 class 需要被代理。如果我删除 final
声明并让 CGLib 增强它,那么 Mockito 会在调用 initMocks(this)
时替换 bean。然而,当一个方法被调用时,CGLib 似乎用 Spring 增强版本替换了所有 beans,因此覆盖了 Mockito 版本。
在带有 @Transactional
注释的 class 的集成测试中使用 Mockito 和 Spring 的正确方法是什么?
好吧,一旦我意识到 class 由于 @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
class 都完好无损,同时注入了正确的模拟对象。
您可以使用提供以下方法的 class AopTestUtils
优化已接受的响应:
getTargetObject
展开顶级代理(如果存在)getUltimateTargetObject
展开所有级别的代理,如果它们
存在