【问题标题】:Mixing JDK and CGLIB proxies within Spring在 Spring 中混合使用 JDK 和 CGLIB 代理
【发布时间】:2011-11-30 02:18:08
【问题描述】:

我有一个使用 Spring 运行的应用程序,并且我在某些地方使用 AOP。由于我想在接口级别使用@Transactional 注释,我必须允许 Spring 创建 JDK 代理。所以,我没有将 proxy-target-class 属性设置为 true。另一方面,我不想为我想要建议的每个类创建一个接口:如果接口没有意义,我只想拥有实现,Spring 应该创建一个 CGLIB 代理。

正如我所描述的,一切都运行良好。但是我想在接口中添加一些其他注释(由我创建)并被实现类“继承”(就像@Transactional 一样)。事实证明,使用 Spring 中对 AOP 的内置支持我无法做到这一点(至少经过一些研究我无法弄清楚如何去做。接口中的注释在实现类中不可见,并且因此该课程不会得到建议)。

所以我决定实现我自己的pointcutinterceptor,允许其他方法注解在接口上进行。基本上,我的切入点查找方法上的注释,直到找不到,在类或其超类实现的接口的相同方法(相同的名称和参数类型)中。

问题是:当我声明一个 DefaultAdvisorAutoProxyCreator bean 时,它将正确应用这个切入点/拦截器,建议没有接口的类的行为被破坏。显然出了点问题,Spring 尝试两次代理我的类,一次使用 CGLIB,然后使用 JDK。

这是我的配置文件:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
    http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">

    <!-- Activates various annotations to be detected in bean classes: Spring's 
        @Required and @Autowired, as well as JSR 250's @Resource. -->
    <context:annotation-config />

    <context:component-scan base-package="mypackage" />

    <!-- Instruct Spring to perform declarative transaction management automatically 
        on annotated classes. -->
    <tx:annotation-driven transaction-manager="transactionManager" />

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />

    <bean id="logger.advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <constructor-arg>
            <bean class="mypackage.MethodAnnotationPointcut">
                <constructor-arg value="mypackage.Trace" />
            </bean>
        </constructor-arg>
        <constructor-arg>
            <bean class="mypackage.TraceInterceptor" />
        </constructor-arg>
    </bean>
</beans>

这是我要代理的类,没有接口:

@Component
public class ServiceExecutorImpl
{
    @Transactional
    public Object execute(...)
    {
        ...
    }
}

当我尝试在其他 bean 中 autowire 它时,例如:

public class BaseService {
   @Autowired
   private ServiceExecutorImpl serviceExecutorImpl;

   ...
}

我得到以下异常:

java.lang.IllegalArgumentException: Can not set mypackage.ServiceExecutorImpl field mypackage.BaseService.serviceExecutor to $Proxy26

这是 Spring 输出的一些行:

13:51:12,672 [main] DEBUG [org.springframework.aop.framework.Cglib2AopProxy] - Creating CGLIB2 proxy: target source is SingletonTargetSource for target object [mypackage.ServiceExecutorImpl@1eb515]
...
13:51:12,782 [main] DEBUG [org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'serviceExecutorImpl' with 0 common interceptors and 1 specific interceptors
13:51:12,783 [main] DEBUG [org.springframework.aop.framework.JdkDynamicAopProxy] - Creating JDK dynamic proxy: target source is SingletonTargetSource for target object [mypackage.ServiceExecutorImpl$$EnhancerByCGLIB$$2eb5f51@5f31b0]

如果有人认为这会有所帮助,我可以提供完整的输出。我不知道为什么 Spring 试图“双重代理”我的课程,以及为什么当我声明 DefaultAdvisorAutoProxyCreator bean 时才会发生这种情况。

我已经为此苦苦挣扎了一段时间,非常感谢任何帮助或想法。

编辑:

这是我的拦截器源代码,根据要求。它基本上记录了方法的执行(只有用 @Trace 注释的方法才会被拦截)。如果该方法使用@Trace(false) 进行注释,则日志记录将暂停,直到该方法返回。

public class TraceInterceptor
    implements
        MethodInterceptor
{

    @Override
    public Object invoke(
        MethodInvocation invocation )
        throws Throwable
    {
        if( ThreadExecutionContext.getCurrentContext().isLogSuspended() ) {
            return invocation.proceed();
        }

        Method method = AopUtils.getMostSpecificMethod( invocation.getMethod(),
            invocation.getThis().getClass() );
        Trace traceAnnotation = method.getAnnotation( Trace.class );

        if( traceAnnotation != null && traceAnnotation.value() == false ) {
            ThreadExecutionContext.getCurrentContext().suspendLogging();
            Object result = invocation.proceed();
            ThreadExecutionContext.getCurrentContext().resumeLogging();
            return result;
        }

        ThreadExecutionContext.startNestedLevel();
        SimpleDateFormat dateFormat = new SimpleDateFormat( "dd/MM/yyyy - HH:mm:ss.SSS" );
        Logger.log( "Timestamp: " + dateFormat.format( new Date() ) );

        String toString = invocation.getThis().toString();
        Logger.log( "Class: " + toString.substring( 0, toString.lastIndexOf( '@' ) ) );

        Logger.log( "Method: " + getMethodName( method ) );
        Logger.log( "Parameters: " );
        for( Object arg : invocation.getArguments() ) {
            Logger.log( arg );
        }

        long before = System.currentTimeMillis();
        try {
            Object result = invocation.proceed();
            Logger.log( "Return: " );
            Logger.log( result );
            return result;
        } finally {
            long after = System.currentTimeMillis();
            Logger.log( "Total execution time (ms): " + ( after - before ) );
            ThreadExecutionContext.endNestedLevel();
        }
    }

    // Just formats a method name, with parameter and return types
    private String getMethodName(
        Method method )
    {
        StringBuffer methodName = new StringBuffer( method.getReturnType().getSimpleName() + " "
            + method.getName() + "(" );
        Class<?>[] parameterTypes = method.getParameterTypes();

        if( parameterTypes.length == 0 ) {
            methodName.append( ")" );
        } else {
            int index;
            for( index = 0; index < ( parameterTypes.length - 1 ); index++ ) {
                methodName.append( parameterTypes[ index ].getSimpleName() + ", " );
            }
            methodName.append( parameterTypes[ index ].getSimpleName() + ")" );
        }
        return methodName.toString();
    }
}

谢谢!

【问题讨论】:

    标签: spring aop spring-aop cglib


    【解决方案1】:

    这里有些不匹配 - 如果有 $ProxyXX,则表示有接口。确保没有接口。其他一些注意事项:

    • 在您的切入点中,您可以使用(x instanceof Advised) 检查目标对象是否已经是代理,然后您可以强制转换为Advised

    • 你可以使用&lt;aop:scoped-proxy /&gt;来定义每个bean的代理策略

    【讨论】:

    • 好吧,我猜 CGLIB 代理实现了一些接口。但我认为这是我无法控制的,对吧?我编写的原始类没有实现任何接口,甚至没有扩展任何类(当然,Object 除外)。我不知道检查 Advised 是否对我有帮助,因为 Spring (由于@Transactional)为这个类提供了建议,而不是我写的任何切入点。我会看看 看看它是否可以帮助我。谢谢!
    • 我用拦截器的源代码编辑了我的问题。有什么线索吗?
    【解决方案2】:

    我找到了使用 Bozho 建议的“范围代理”的解决方案。

    由于我几乎只使用注释,我的 ServiceExecutor 类现在看起来像这样:

    @Component
    @Scope( proxyMode = ScopedProxyMode.TARGET_CLASS )
    public class ServiceExecutor
    {
        @Transactional
        public Object execute(...)
        {
            ...
        }
    }
    

    到目前为止,一切看起来都运行良好。我不知道为什么我必须明确告诉 Spring 这个类应该使用 CGLIB 代理,因为它没有实现任何接口。也许这是一个错误,我不知道。

    非常感谢,博卓。

    【讨论】:

    • 由于 @Transactional 注释,我被代理了,因为 Spring 必须在 execute(...) 方法调用之前和之后执行一些操作,以确保您有一个活动的事务。
    猜你喜欢
    • 2015-08-09
    • 1970-01-01
    • 1970-01-01
    • 2012-05-26
    • 1970-01-01
    • 2013-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多