18

Spring で実行するアプリケーションがあり、いくつかの場所で AOP を使用しています。インターフェイス レベルで @Transactional アノテーションを使用したいので、Spring が JDK プロキシを作成できるようにする必要があります。したがって、proxy-target-classプロパティを true に設定しません。一方で、アドバイスが欲しいクラスごとにインターフェースを作成したくはありません。インターフェースが意味をなさない場合は、実装だけが必要であり、Spring は CGLIB プロキシを作成する必要があります。

私が説明したように、すべてが完璧に機能していました。しかし、私は他のいくつかの注釈 (私が作成した) をインターフェースに入れて、実装クラスによって「継承」されたいと思っていました (@Transactional のように)。SpringのAOPの組み込みサポートではそれができないことが判明しました(少なくとも、いくつかの調査の後、それを行う方法を理解できませんでした。インターフェースの注釈は実装クラスでは表示されません。したがって、そのクラスはアドバイスを受けません)。

そこで、独自のポイントカットインターセプターを実装して、他のメソッド アノテーションをインターフェイスに追加できるようにすることにしました。基本的に、私のポイントカットはメソッドのアノテーションを探し、見つからないまで、クラスまたはそのスーパークラスが実装するインターフェースの同じメソッド (同じ名前とパラメーターの型) を探します。

問題は、このポイントカット/インターセプターを適切に適用する DefaultAdvisorAutoProxyCreator Bean を宣言すると、インターフェイスを持たないアドバイス クラスの動作が壊れてしまうことです。どうやら何かがうまくいかず、Spring は私のクラスを 2 回 (CGLIB で 1 回、次に 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();
    }
}

ありがとう!

4

2 に答える 2

13

Bozho が提案した「scoped-proxy」を使用して解決策を見つけました。

ほとんど注釈のみを使用しているため、ServiceExecutor クラスは次のようになります。

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

これまでのところ、すべてが正常に機能しているように見えます。このクラスはインターフェイスを実装していないため、CGLIB を使用してプロキシする必要があることを Spring に明示的に伝える必要がある理由がわかりません。多分それはバグです、私にはわかりません。

どうもありがとう、ボジョ。

于 2011-10-11T17:52:11.807 に答える
3

ここで何かが一致しません - があれば$ProxyXX、インターフェースがあることを意味します。インターフェイスがないことを確認します。その他の注意事項:

  • あなたのポイントカットでは、ターゲットオブジェクトがすでに を使用してプロキシであるかどうかを確認できます(x instanceof Advised)。次に、にキャストできますAdvised

  • <aop:scoped-proxy />Beanごとのプロキシ戦略を定義するために使用できます

于 2011-10-03T18:56:54.367 に答える