运行 Spring 单元测试的 AOP 问题 [英] AOP problem running Spring unit tests

查看:89
本文介绍了运行 Spring 单元测试的 AOP 问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 Spring Web 应用程序,它被配置为对 AOP 使用 JDK 代理.AOP注解(例如@Transactional)是在接口上声明的,而不是在实现类上声明.

应用程序本身运行良好,但是当我运行单元测试时,它似乎试图将 CGLIB 用于 AOP 功能(而不是 JDK 代理).这会导致测试失败 - 我在下面附加了堆栈跟踪.

我不明白为什么在我运行测试时使用 CGLIB,因为 Spring 配置与应用程序运行时的配置基本相同.一个可能的显着差异是测试配置使用 DataSourceTransactionManager 而不是 JTA 事务管理器.测试类本身都扩展了 AbstractJUnit4SpringContextTests,难道这个类在某种程度上硬连接到使用CGLIB?

Caused by: org.springframework.aop.framework.AopConfigException: 无法生成类 [class $Proxy25] 的 CGLIB 子类:此问题的常见原因包括使用最终类或不可见类;嵌套异常是 java.lang.IllegalArgumentException:无法子类化最终类 $Proxy25在 org.springframework.aop.framework.Cglib2AopProxy.getProxy(Cglib2AopProxy.java:213)在 org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110)在 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:488)在 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:363)在 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:324)在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:361)在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1343)在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:473)... 79 更多引起:java.lang.IllegalArgumentException:不能子类化最终类$Proxy25在 net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:446)在 net.sf.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33)在 net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)在 net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)在 net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)在 net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)在 org.springframework.aop.framework.Cglib2AopProxy.getProxy(Cglib2AopProxy.java:201)... 86 更多

其中一位评论员要求我发布 Spring 配置.我以缩写形式将其包含在下面(即省略了不相关的 bean 和 XML 命名空间).

spring-servlet.xml

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"><属性名称=拦截器"><列表><bean class="com.onebigplanet.web.interceptor.PortalInterceptor"/></list></属性></bean><!-- 方法处理程序适配器--><!-- 通过Controller方法上的注解找到url的映射--><bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"><property name="cacheSeconds" value="0"/><属性名称="webBindingInitializer"><bean class="com.onebigplanet.web.binder.WebBindingInitializer"/></属性></bean></豆类>

applicationContext-service.xml

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/><!-- 注释驱动的事务管理--><tx:annotation-driven transaction-manager="transactionManager"/></豆类>

applicationContext-test.xml 这仅在运行单元测试时包含.它的目的是覆盖其他配置文件中声明的一些bean.

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="location" value="/obp-test.properties"/></bean><!-- 所有 DAO 都应该使用测试数据源 --><bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"><property name="driverClassName" value="${testDataSource.driverClassName}"/><property name="url" value="${testDataSource.url}"/><property name="username" value="${testDataSource.username}"/><property name="password" value="${testDataSource.password}"/></bean><bean id="readOnlyDataSource" class="org.apache.commons.dbcp.BasicDataSource"><property name="driverClassName" value="${testDataSource.driverClassName}"/><property name="url" value="${testDataSource.url}"/><property name="username" value="${testDataSource.username}"/><property name="password" value="${testDataSource.password}"/></bean><!--用这个覆盖 applicationContent-service.xml 中定义的 JTA 事务管理器 bean,因为前者的实现由 JBoss 提供--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><属性名称="数据源" ref="数据源"/></bean><豆类>

解决方案

听起来您是在引用实现类而不是接口.有一个 此处摘录,了解更多详情.

Spring 论坛帖子:混合 JDK 和 CGLIB 代理"

一篇很棒的博客文章,解释了 优点和缺点JDK 与 CGLIB 代理的对比.

I have a Spring web application which is configured to use JDK proxying for AOP. The AOP annotations (such as @Transactional) are declared on the interfaces, rather than the implementation classes.

The application itself works fine, but when I run the unit tests, it seems to be attempting to use CGLIB for the AOP functionality (instead of JDK proxying). This causes the tests to fail - I've appended the stack trace below.

I don't understand why CGLIB is being used when I run the tests, because the Spring configuration is largely the same as when the application is running. One possibly significant difference is that the test configuration uses a DataSourceTransactionManager instead of a JTA transaction manager. The test classes themselves all extend AbstractJUnit4SpringContextTests, could it be that this class is somehow hard-wired to use CGLIB?

Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class $Proxy25]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class $Proxy25
    at org.springframework.aop.framework.Cglib2AopProxy.getProxy(Cglib2AopProxy.java:213)
    at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:488)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:363)
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:324)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:361)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1343)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:473)
    ... 79 more
Caused by: java.lang.IllegalArgumentException: Cannot subclass final class class $Proxy25
    at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:446)
    at net.sf.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33)
    at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
    at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
    at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
    at org.springframework.aop.framework.Cglib2AopProxy.getProxy(Cglib2AopProxy.java:201)
    ... 86 more

EDIT: One of the commentators requested that I post the Spring configuration. I've included it below in abbreviated form (i.e. irrelevant beans and XML namespaces omitted).

spring-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans>                 
    <!-- ANNOTATION SUPPORT -->
    <!-- Include basic annotation support -->
    <context:annotation-config/>        

    <!-- CONTROLLERS -->
    <!-- Controllers, force scanning -->
    <context:component-scan base-package="com.onebigplanet.web.controller,com.onebigplanet.web.ws.*"/>  

    <!-- Post-processor for @Aspect annotated beans, which converts them into AOP advice -->
    <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator">
        <property name="proxyTargetClass" value="true"/>
    </bean>

    <!-- An @Aspect bean that converts exceptions thrown in POJO service implementation classes to runtime exceptions  -->
    <bean id="permissionAdvisor" class="com.onebigplanet.web.advisor.PermissionAdvisor"/>
    <bean id="businessIntelligenceAdvisor" class="com.onebigplanet.web.advisor.bi.BusinessIntelligenceAdvisor"/>        

    <!-- Finds the controllers and sets an interceptor on each one -->
    <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
        <property name="interceptors">
            <list>
                <bean class="com.onebigplanet.web.interceptor.PortalInterceptor"/>              
            </list>
        </property>
    </bean> 

    <!-- METHOD HANDLER ADAPTER --> 
    <!-- Finds mapping of url through annotation on methods of Controller -->
    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="cacheSeconds" value="0"/>
        <property name="webBindingInitializer">
            <bean class="com.onebigplanet.web.binder.WebBindingInitializer"/>
        </property>
    </bean> 
</beans>

applicationContext-service.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans> 
    <!-- Declares a bunch of bean post-processors -->
    <context:annotation-config/>

    <context:component-scan base-package="com.onebigplanet.service.impl,com.onebigplanet.dao.impl.mysql" annotation-config="false"/>    

    <!-- Property configurer -->
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="/WEB-INF/obp-service.properties" />
    </bean> 

    <!-- Post-processor for @Aspect annotated beans, which converts them into AOP advice -->
    <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>

    <!-- An @Aspect bean that converts exceptions thrown in service implementation classes to runtime exceptions  -->
    <bean id="exceptionAdvisor" class="com.onebigplanet.service.advisor.ExceptionAdvisor"/>
    <bean id="cachingAdvisor" class="com.onebigplanet.service.advisor.CacheAdvisor"/>   
    <bean id="businessIntelligenceAffiliateAdvisor" class="com.onebigplanet.service.advisor.BusinessIntelligenceAffiliateAdvisor"/>

    <!-- Writable datasource -->
    <jee:jndi-lookup id="dataSource" jndi-name="java:/ObpDS"/>

    <!-- ReadOnly datasource -->
    <jee:jndi-lookup id="readOnlyDataSource" jndi-name="java:/ObpReadOnlyDS"/>  

    <!-- Map the transaction manager to allow easy lookup of a UserTransaction -->
    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

    <!-- Annotation driven transaction management -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

applicationContext-test.xml This is only included when running the unit tests. It's purpose is to overwrite some of the beans declared in the other config files.

<?xml version="1.0" encoding="UTF-8"?>
<beans>         
    <!-- Overwrite the property configurer bean such that it reads the test properties file instead -->
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="/obp-test.properties"/>
    </bean> 

    <!-- All DAOs should use the test datasource -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="${testDataSource.driverClassName}"/>
        <property name="url" value="${testDataSource.url}"/>
        <property name="username" value="${testDataSource.username}"/>
        <property name="password" value="${testDataSource.password}"/>
    </bean>

    <bean id="readOnlyDataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="${testDataSource.driverClassName}"/>
        <property name="url" value="${testDataSource.url}"/>
        <property name="username" value="${testDataSource.username}"/>
        <property name="password" value="${testDataSource.password}"/>
    </bean>

    <!-- 
        Overwrite the JTA transaction manager bean defined in applicationContent-service.xml with this one because
        the implementation of the former is provided by JBoss
    -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
<beans>

解决方案

Sounds like you're referencing an implementation class instead of an interface. There is an excerpt here with more detail.

A Spring forum post: "Mixing JDK and CGLIB proxies"

A great blog post explaining pros and cons of JDK vs. CGLIB proxies.

这篇关于运行 Spring 单元测试的 AOP 问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆