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

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

问题描述

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

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

因此,我决定实现自己的 pointcut interceptor ,从而允许其他方法注释在接口上进行.基本上,我的切入点在该方法上寻找注解,直到找不到该类或超类所实现的接口的相同方法(名称和参数类型相同)为止.

问题是:当我声明一个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中时,例如:

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

谢谢!

解决方案

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

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

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

直到现在,一切似乎都可以正常工作.我不知道为什么我必须明确地告诉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天全站免登陆