1. 序章

インターセプターパターンは通常、アプリケーションに新しい分野横断的な機能やロジックを追加するために使用され、多数のライブラリで確実にサポートされています。

この記事では、これらの主要なライブラリの2つであるCDIインターセプターとSpringAspectJについて説明します。

2. CDIインターセプタープロジェクトのセットアップ

CDIはJakartaEEで公式にサポートされていますが、一部の実装では、JavaSE環境でCDIを使用するためのサポートが提供されています。 Weld は、JavaSEでサポートされているCDI実装の一例と見なすことができます。

CDIを使用するには、POMにWeldライブラリをインポートする必要があります。

<dependency>
    <groupId>org.jboss.weld.se</groupId>
    <artifactId>weld-se-core</artifactId>
    <version>3.0.5.Final</version>
</dependency>

最新のWeldライブラリは、Mavenリポジトリにあります。

簡単なインターセプターを作成しましょう。

3. CDIインターセプターの紹介

インターセプトする必要のあるクラスを指定するために、インターセプターバインディングを作成しましょう。

@InterceptorBinding
@Target( { METHOD, TYPE } )
@Retention( RUNTIME )
public @interface Audited {
}

インターセプターバインディングを定義した後、実際のインターセプター実装を定義する必要があります。

@Audited
@Interceptor
public class AuditedInterceptor {
    public static boolean calledBefore = false;
    public static boolean calledAfter = false;

    @AroundInvoke
    public Object auditMethod(InvocationContext ctx) throws Exception {
        calledBefore = true;
        Object result = ctx.proceed();
        calledAfter = true;
        return result;
    }
}

すべての@AroundInvokeメソッドは、 javax.interceptor.InvocationContext 引数を取り、 java.lang.Object を返し、Exceptionをスローできます。 。

したがって、新しい @Audit インターフェイスでメソッドにアノテーションを付けると、 auditMethod が最初に呼び出され、次にターゲットメソッドも続行されます。

4. CDIインターセプターを適用する

作成したインターセプターをいくつかのビジネスロジックに適用してみましょう。

public class SuperService {
    @Audited
    public String deliverService(String uid) {
        return uid;
    }
}

この単純なサービスを作成し、インターセプトしたいメソッドに@Auditedアノテーションを付けました。

CDIインターセプターを有効にするには、META-INFディレクトリにあるbeans.xmlファイルで完全なクラス名を指定する必要があります。

<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
      http://java.sun.com/xml/ns/javaee/beans_1_2.xsd">
    <interceptors>
        <class>com.baeldung.interceptor.AuditedInterceptor</class>
    </interceptors>
</beans>

インターセプターが実際に機能したことを検証するために次のテストを実行してみましょう

public class TestInterceptor {
    Weld weld;
    WeldContainer container;

    @Before
    public void init() {
        weld = new Weld();
        container = weld.initialize();
    }

    @After
    public void shutdown() {
        weld.shutdown();
    }

    @Test
    public void givenTheService_whenMethodAndInterceptorExecuted_thenOK() {
        SuperService superService = container.select(SuperService.class).get();
        String code = "123456";
        superService.deliverService(code);
        
        Assert.assertTrue(AuditedInterceptor.calledBefore);
        Assert.assertTrue(AuditedInterceptor.calledAfter);
    }
}

このクイックテストでは、最初にコンテナーからBean SuperService を取得し、次にそのコンテナーでビジネスメソッド DeliverService を呼び出し、検証によってインターセプターAuditedInterceptorが実際に呼び出されたことを確認します。その状態変数。

また、@Beforeおよび@Afterの注釈付きメソッドがあり、それぞれWeldコンテナを初期化およびシャットダウンします。

5. CDIに関する考慮事項

CDIインターセプターの次の利点を指摘できます。

  • JakartaEE仕様の標準機能です
  • 一部のCDI実装ライブラリはJavaSEで使用できます
  • プロジェクトにサードパーティライブラリの厳しい制限がある場合に使用できます

CDIインターセプターの欠点は次のとおりです。

  • クラスとビジネスロジックおよびインターセプター間の緊密な結合
  • プロジェクトでインターセプトされているクラスを確認するのは難しい
  • メソッドのグループにインターセプターを適用するための柔軟なメカニズムの欠如

6. 春のアスペクトJ

Springは、AspectJ構文を使用したインターセプター機能の同様の実装もサポートしています。

まず、次のSpringとAspectJの依存関係をPOMに追加する必要があります。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.2</version>
</dependency>

Springコンテキスト aspectjweaver の最新バージョンは、Mavenリポジトリーにあります。

これで、AspectJアノテーション構文を使用して単純なアスペクトを作成できます。

@Aspect
public class SpringTestAspect {
    @Autowired
    private List accumulator;

    @Around("execution(* com.baeldung.spring.service.SpringSuperService.*(..))")
    public Object auditMethod(ProceedingJoinPoint jp) throws Throwable {
        String methodName = jp.getSignature().getName();
        accumulator.add("Call to " + methodName);
        Object obj = jp.proceed();
        accumulator.add("Method called successfully: " + methodName);
        return obj;
    }
}

SpringSuperService クラスのすべてのメソッドに適用されるアスペクトを作成しました。これは、簡単にするために、次のようになります。

public class SpringSuperService {
    public String getInfoFromService(String code) {
        return code;
    }
}

7. SpringAspectJアスペクト適用

その側面が実際にサービスに適用されることを検証するために、次の単体テストを作成しましょう。

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = { AppConfig.class })
public class TestSpringInterceptor {
    @Autowired
    SpringSuperService springSuperService;

    @Autowired
    private List accumulator;

    @Test
    public void givenService_whenServiceAndAspectExecuted_thenOk() {
        String code = "123456";
        String result = springSuperService.getInfoFromService(code);
        
        Assert.assertThat(accumulator.size(), is(2));
        Assert.assertThat(accumulator.get(0), is("Call to getInfoFromService"));
        Assert.assertThat(accumulator.get(1), is("Method called successfully: getInfoFromService"));
    }
}

このテストでは、サービスを注入し、メソッドを呼び出して結果を確認します。

構成は次のようになります。

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    @Bean
    public SpringSuperService springSuperService() {
        return new SpringSuperService();
    }

    @Bean
    public SpringTestAspect springTestAspect() {
        return new SpringTestAspect();
    }

    @Bean
    public List getAccumulator() {
        return new ArrayList();
    }
}

ここでの@EnableAspectJAutoProxyアノテーションの重要な側面の1つは、SpringのXML要素にある機能と同様に、AspectJの@Aspectアノテーションでマークされたコンポーネントの処理をサポートできるようにします。

8. SpringAspectJの考慮事項

SpringAspectJを使用する利点のいくつかを指摘しましょう。

  • インターセプターはビジネスロジックから切り離されています
  • インターセプターは依存性注入の恩恵を受けることができます
  • インターセプターには、それ自体にすべての構成情報があります
  • 新しいインターセプターを追加するために、既存のコードを拡張する必要はありません
  • インターセプターには、インターセプトする方法を選択するための柔軟なメカニズムがあります
  • JakartaEEなしで使用できます

そしてもちろん、いくつかの欠点:

  • インターセプターを開発するには、AspectJ構文を知っている必要があります
  • AspectJインターセプターの学習曲線はCDIインターセプターよりも高くなっています

9. CDIインターセプターvsSpringAspectJ

現在のプロジェクトでSpringを使用している場合は、SpringAspectJを検討することをお勧めします。

本格的なアプリケーションサーバーを使用している場合、またはプロジェクトでSpring(またはGoogle Guiceなどの他のフレームワーク)を使用せず、厳密にJakarta EEである場合は、CDIインターセプターを選択する以外に何もありません。

10. 結論

この記事では、インターセプターパターンの2つの実装、CDIインターセプターとSpringAspectJについて説明しました。 それぞれの長所と短所について説明しました。

この記事の例のソースコードは、GitHubのリポジトリにあります。