在 Spring 中混合使用 JDK 和 CGLIB 代理 [英] Mixing JDK and CGLIB proxies within Spring

查看:46
本文介绍了在 Spring 中混合使用 JDK 和 CGLIB 代理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

一切正常,正如我描述的那样.但是我希望有一些其他注释(由我创建)进入接口并被实现类继承"(就像@Transactional 一样).事实证明,我无法通过 Spring 中对 AOP 的内置支持来做到这一点(至少经过一番研究我无法弄清楚如何做到这一点.接口中的注释在实现类中是不可见的,并且因此该课程不会得到建议).

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

问题是:当我声明一个 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.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsdhttp://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"><!-- 激活bean类中要检测的各种注解:Spring的@Required 和 @Autowired,以及 JSR 250 的 @Resource.--><context:annotation-config/><context:component-scan base-package="mypackage"/><!-- 指示Spring自动执行声明式事务管理在带注释的类上.--><tx:annotation-driven transaction-manager="transactionManager"/><bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/><bean id="logger.advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"><构造函数参数><bean class="mypackage.MethodAnnotationPointcut"><constructor-arg value="mypackage.Trace"/></bean></constructor-arg><构造函数参数><bean class="mypackage.TraceInterceptor"/></constructor-arg></bean></豆类>

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

@Component公共类 ServiceExecutorImpl{@交易公共对象执行(...){...}}

当我尝试在其他 bean 中自动装配它时,例如:

公共类 BaseService {@自动连线私有 ServiceExecutorImpl serviceExecutorImpl;...}

我收到以下异常:

java.lang.IllegalArgumentException:无法将 mypackage.ServiceExecutorImpl 字段 mypackage.BaseService.serviceExecutor 设置为 $Proxy26

这是 Spring 输出的一些行:

13:51:12,672 [main] DEBUG [org.springframework.aop.framework.Cglib2AopProxy] - 创建 CGLIB2 代理:目标源是目标对象的 SingletonTargetSource [mypackage.ServiceExecutorImpl@1eb515]...13:51:12,782 [main] DEBUG [org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator] - 使用 0 个通用拦截器和 1 个特定拦截器为 bean 'serviceExecutorImpl' 创建隐式代理13:51:12,783 [main] DEBUG [org.springframework.aop.framework.JdkDynamicAopProxy] - 创建 JDK 动态代理:目标源是目标对象的 SingletonTargetSource [mypackage.ServiceExecutorImpl$$EnhancerByCGLIB$$2eb5f51@5f31b0]

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

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

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

公共类 TraceInterceptor工具方法拦截器{@覆盖公共对象调用(MethodInvocation 调用)投掷{if( ThreadExecutionContext.getCurrentContext().isLogSuspended() ) {返回调用.proceed();}方法方法 = AopUtils.getMostSpecificMethod( invocation.getMethod(),调用.getThis().getClass());Trace traceAnnotation = method.getAnnotation( Trace.class );if( traceAnnotation != null && traceAnnotation.value() == false ) {ThreadExecutionContext.getCurrentContext().suspendLogging();对象结果 = invocation.proceed();ThreadExecutionContext.getCurrentContext().resumeLogging();返回结果;}ThreadExecutionContext.startNestedLevel();SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy - HH:mm:ss.SSS");Logger.log("时间戳:" + dateFormat.format( new Date() ) );String toString = invocation.getThis().toString();Logger.log("类:" + toString.substring( 0, toString.lastIndexOf( '@' ) ) );Logger.log("方法:" + getMethodName( method ) );Logger.log("参数:");for( 对象参数:invocation.getArguments()) {Logger.log( arg );}很久以前 = System.currentTimeMillis();尝试 {对象结果 = invocation.proceed();Logger.log("返回:");记录器.log(结果);返回结果;} 最后 {很久之后 = System.currentTimeMillis();Logger.log("总执行时间(ms):" + ( after - before ) );ThreadExecutionContext.endNestedLevel();}}//只格式化一个方法名,带参数和返回类型私有字符串 getMethodName(方法方法){StringBuffer methodName = new StringBuffer( method.getReturnType().getSimpleName() + " "+ method.getName() + "(" );Class[] parameterTypes = method.getParameterTypes();if( parameterTypes.length == 0 ) {methodName.append(")" );} 别的 {整数索引;for( index = 0; index <( parameterTypes.length - 1 ); index++ ) {methodName.append( parameterTypes[ index ].getSimpleName() + ", " );}methodName.append( parameterTypes[ index ].getSimpleName() + ")" );}返回 methodName.toString();}}

谢谢!

解决方案

我找到了一个使用 Bozho 建议的作用域代理"的解决方案.

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

@Component@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)公共类 ServiceExecutor{@交易公共对象执行(...){...}}

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

非常感谢,波佐.

I have an application running with Spring, and I'm using AOP in some places. Since I want to use the @Transactional annotation at interface level, I have to allow Spring to create JDK proxies. So, I don't set the proxy-target-class property to true. On the other hand, I don't want to create an interface for every single class I want advised: if the interface just doesn't make sense, I want to have just the implementation, and Spring should create a CGLIB proxy.

Everything was working perfectly, just as I described. But I wanted to have some other annotations (created by me) going in interfaces and being "inherited" by the implementation classes (just like the @Transactional one). Turns out that I can't do that with the built-in support for AOP in Spring (at least I could not figure it out how to do it after some research. The annotation in the interface is not visible in the implementation class, and hence that class does not get advised).

So I decided to implement my own pointcut and interceptor, allowing other method annotations to go on interfaces. Basically, my pointcut look for the annotation on the method and, until not found, in the same method (same name and parameter types) of the interfaces that the class or its superclasses implements.

The problem is: when I declare a DefaultAdvisorAutoProxyCreator bean, that will properly apply this pointcut/interceptor, the behavior of advising classes with no interfaces is broken. Apparently something goes wrong and Spring tries to proxy my classes twice, once with CGLIB and then with JDK.

This is my configuration file:

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

This is the class I want to be proxied, with no interfaces:

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

When I try to autowire it in some other bean, like:

public class BaseService {
   @Autowired
   private ServiceExecutorImpl serviceExecutorImpl;

   ...
}

I get the following exception:

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

This are some lines of the Spring output:

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]

I could supply the full output if someone thinks it will help. I have no idea why Spring is trying to "double-proxy" my class, and why this just happens when I declare the DefaultAdvisorAutoProxyCreator bean.

I have been struggling with this for some time now, and any help or ideas would be very much appreciated.

EDIT:

This is my interceptor source code, as requested. It basically log the method execution (only methods annotated with @Trace get intercepted). If the method is annotated with @Trace(false), the logging is suspended until the method returns.

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();
    }
}

Thanks!

解决方案

I found a solution using the 'scoped-proxy' suggested by Bozho.

Since I'm using almost only annotations, my ServiceExecutor class now looks like this:

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

Until now everything seens to be working fine. I don't know why I have to explicitly tell Spring this class should be proxied using CGLIB, since it does not implement any interface. Maybe it's a bug, I don't know.

Thanks a lot, Bozho.

这篇关于在 Spring 中混合使用 JDK 和 CGLIB 代理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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