春のアドバイスタイプの紹介

Springのアドバイスタイプの概要

1. 概要

この記事では、Springで作成できるさまざまなタイプのAOPアドバイスについて説明します。

Adviceは、特定のジョインポイントでアスペクトによって実行されるアクションです。 さまざまな種類のアドバイスには、「約」、「前」、「後」のアドバイスが含まれます。 アスペクトの主な目的は、ロギング、プロファイリング、キャッシング、トランザクション管理などの分野横断的な懸念をサポートすることです。

また、ポイントカット式について詳しく知りたい場合は、yesterdays intro to theseを確認してください。

2. アドバイスを有効にする

Springでは、AspectJアノテーションを使用してアドバイスを宣言できますが、最初に@EnableAspectJAutoProxyアノテーションを構成クラスに適用する必要があります。これにより、AspectJの@Aspectアノテーションでマークされたコンポーネントの処理がサポートされます。

@Configuration
@ComponentScan(basePackages = {"org.example.dao", "org.example.aop"})
@EnableAspectJAutoProxy
public class TestConfig {
    ...
}

3. アドバイスの前に

このアドバイスは、名前が示すとおり、結合ポイントの前に実行されます。 例外がスローされない限り、推奨されるメソッドの継続的な実行を妨げません。

呼び出される前にメソッド名を単純に記録する次の側面を考慮してください。

@Component
@Aspect
public class LoggingAspect {

    private Logger logger = Logger.getLogger(LoggingAspect.class.getName());

    @Pointcut("@target(org.springframework.stereotype.Repository)")
    public void repositoryMethods() {};

    @Before("repositoryMethods()")
    public void logMethodCall(JoinPoint jp) {
        String methodName = jp.getSignature().getName();
        logger.info("Before " + methodName);
    }
}

logMethodCallアドバイスは、repositoryMethodsポイントカットで定義されたリポジトリメソッドの前に実行されます。

4. アドバイス後

@Afterアノテーションを使用して宣言されたアドバイスの後、例外がスローされたかどうかに関係なく、一致したメソッドの実行後に__が実行されます。

いくつかの点で、それはfinallyブロックに似ています。 通常の実行後にのみアドバイスをトリガーする必要がある場合は、@AfterReturningアノテーションで宣言されたreturning adviceを使用する必要があります。 ターゲットメソッドが例外をスローしたときにのみアドバイスをトリガーする場合は、@AfterThrowingアノテーションを使用して宣言されたthrowing advice,を使用する必要があります。

Fooの新しいインスタンスが作成されたときに、いくつかのアプリケーションコンポーネントに通知したいとします。 FooDaoからイベントを公開することはできますが、これは単一責任の原則に違反します。 代わりに、次の側面を定義することでこれを実現できます。

@Component
@Aspect
public class PublishingAspect {

    private ApplicationEventPublisher eventPublisher;

    @Autowired
    public void setEventPublisher(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    @Pointcut("@target(org.springframework.stereotype.Repository)")
    public void repositoryMethods() {}

    @Pointcut("execution(* *..create*(Long,..))")
    public void firstLongParamMethods() {}

    @Pointcut("repositoryMethods() && firstLongParamMethods()")
    public void entityCreationMethods() {}

    @AfterReturning(value = "entityCreationMethods()", returning = "entity")
    public void logMethodCall(JoinPoint jp, Object entity) throws Throwable {
        eventPublisher.publishEvent(new FooCreationEvent(entity));
    }
}

まず、@AfterReturningアノテーションを使用することで、ターゲットメソッドの戻り値にアクセスできることに注意してください。 次に、タイプJoinPoint,のパラメーターを宣言することにより、ターゲットメソッドの呼び出しの引数にアクセスできます。

次に、イベントを記録するだけのリスナーを作成します。 this tutorialでイベントの詳細を読むことができます:

@Component
public class FooCreationEventListener implements ApplicationListener {

    private Logger logger = Logger.getLogger(getClass().getName());

    @Override
    public void onApplicationEvent(FooCreationEvent event) {
        logger.info("Created foo instance: " + event.getSource().toString());
    }
}

5. アドバイスの周辺

Around adviceは、メソッド呼び出しなどのジョインポイントを囲みます。

これは最も強力なアドバイスです。 アラウンドアドバイスは、メソッド呼び出しの前後にカスタム動作を実行できます。 また、結合ポイントに進むか、独自の戻り値を提供するか、例外をスローすることにより、推奨されるメソッド実行をショートカットするかを選択する責任もあります。

その使用方法を示すために、メソッドの実行時間を測定するとします。 このために、次のアスペクトを作成できます。

@Aspect
@Component
public class PerformanceAspect {

    private Logger logger = Logger.getLogger(getClass().getName());

    @Pointcut("within(@org.springframework.stereotype.Repository *)")
    public void repositoryClassMethods() {};

    @Around("repositoryClassMethods()")
    public Object measureMethodExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();
        Object retval = pjp.proceed();
        long end = System.nanoTime();
        String methodName = pjp.getSignature().getName();
        logger.info("Execution of " + methodName + " took " +
          TimeUnit.NANOSECONDS.toMillis(end - start) + " ms");
        return retval;
    }
}

このアドバイスは、repositoryClassMethodsポイントカットに一致するジョインポイントのいずれかが実行されたときにトリガーされます。

このアドバイスは、タイプProceedingJointPointの1つのパラメーターを取ります。 このパラメーターは、ターゲットメソッドを呼び出す前にアクションを実行する機会を提供します。 この場合、メソッドの開始時間を保存するだけです。

次に、ターゲットメソッドは任意のタイプの結果を返すことができるため、アドバイスの戻り値の型はObjectです。 ターゲットメソッドがvoid,の場合、nullが返されます。 ターゲットメソッドの呼び出し後、タイミングを測定してログに記録し、メソッドの結果値を呼び出し元に返すことができます。

6. 概要

この記事では、Springでのさまざまな種類のアドバイスと、それらの宣言と実装について学びました。 スキーマベースのアプローチとAspectJアノテーションを使用してアスペクトを定義しました。 また、いくつかの可能なアドバイスアプリケーションも提供しています。

これらすべての例とコードスニペットの実装はmy github projectにあります。これはEclipseベースのプロジェクトであるため、そのままインポートして実行するのは簡単です。