Проблема АОП при выполнении весенних юнит-тестов - PullRequest
9 голосов
/ 04 декабря 2008

У меня есть веб-приложение Spring, настроенное на использование JDK-прокси для AOP. Аннотации AOP (такие как @Transactional) объявляются на интерфейсах, а не на классах реализации.

Само приложение работает нормально, но когда я запускаю модульные тесты, оно, похоже, пытается использовать CGLIB для функциональности AOP (вместо прокси JDK). Это приводит к сбою тестов - я добавил трассировку стека ниже.

Я не понимаю, почему CGLIB используется при запуске тестов, потому что конфигурация Spring в основном такая же, как и при запуске приложения. Одно, возможно, существенное отличие состоит в том, что в тестовой конфигурации вместо диспетчера транзакций JTA используется DataSourceTransactionManager . Сами тестовые классы расширяют AbstractJUnit4SpringContextTests , может быть, этот класс каким-то образом запрограммирован для использования 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

РЕДАКТИРОВАТЬ: Один из комментаторов попросил, чтобы я опубликовал конфигурацию Spring . Я включил его ниже в сокращенной форме (т.е. нерелевантные компоненты и пространства имен XML опущены).

весна-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 Это включено только при запуске модульных тестов. Его цель - переписать некоторые компоненты, объявленные в других файлах конфигурации.

<?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>

Ответы [ 3 ]

8 голосов
/ 06 декабря 2008

Звучит так, как будто вы ссылаетесь на класс реализации вместо интерфейса. Здесь есть выдержка с более подробной информацией.

Сообщение на форуме Spring: "Смешивание JDK и CGLIB прокси"

Отличное сообщение в блоге, объясняющее плюсы и минусы прокси-серверов JDK и CGLIB .

3 голосов
/ 06 декабря 2008

Привет, Жан, прокси-серверы CGLib создаются путем создания подкласса класса для прокси - вы пытаетесь прокси-сервер другого прокси-сервера, который не разрешен, поскольку сами прокси являются конечными классами. Следовательно:

Caused by: java.lang.IllegalArgumentException: Cannot subclass final class class $Proxy25

2 голосов
/ 27 января 2010

Я не знаю, было ли решение уже предоставлено, и я также уверен, что исходный запросчик должен был найти решение, так как это запрос на один год. Однако для общественного интереса позвольте мне упомянуть это здесь. Spring использовал CGLIB из-за следующего объявления.

<!-- 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>

Для свойства должно быть установлено значение false, чтобы CGLIB не запускался вместо JDK Dynamic Proxying.

<property name="proxyTargetClass" value="false"/>

Надеюсь, это поможет.

...