簡體   English   中英

在Spring中混合使用JDK和CGLIB代理

[英]Mixing JDK and CGLIB proxies within Spring

我有一個運行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.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建議的'scoped-proxy'的解決方案。

由於我幾乎只使用注釋,我的ServiceExecutor類現在看起來像這樣:

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

到目前為止,一切都很好。 我不知道為什么我必須明確地告訴Spring這個類應該使用CGLIB代理,因為它沒有實現任何接口。 也許這是一個錯誤,我不知道。

非常感謝,Bozho。

這里有些東西不匹配 - 如果是$ProxyXX ,則意味着有一個接口。 確保沒有界面。 其他一些說明:

  • 在你的切入點你可以檢查目標對象是否已經是代理使用(x instanceof Advised) ,然后你可以強制轉換為Advised

  • 您可以使用<aop:scoped-proxy />來定義每個bean的代理策略

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM