1. 概要

このクイックチュートリアルでは、アスペクト指向プログラミング(AOP)を使用してJavaアプリケーションの動作を変更する便利なアノテーションのコレクションである jcabi-aspectsJavaライブラリについて説明します。

jcabi-aspects ライブラリは、 @Async @Loggable @RetryOnFailure などの注釈を提供します。これらは、特定の操作を効率的に実行するのに役立ちます。 AOPを使用します。 同時に、アプリケーションのボイラープレートコードの量を減らすのに役立ちます。 ライブラリでは、アスペクトをコンパイル済みクラスに織り込むためにAspectJが必要です。

2. 設定

まず、最新の jcabi-aspectsMaven依存関係をpom.xmlに追加します。

<dependency>
    <groupId>com.jcabi</groupId>
    <artifactId>jcabi-aspects</artifactId>
    <version>0.22.6</version>
</dependency>

jcabi-aspects ライブラリが機能するには、AspectJランタイムサポートが必要です。 したがって、 aspectjrtMaven依存関係を追加しましょう。

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.2</version>
    <scope>runtime</scope>
</dependency>

次に、コンパイル時にAspectJアスペクトでバイナリを織り込むjcabi-maven-pluginプラグインを追加しましょう。 プラグインは、自動織りを行うajcゴールを提供します。

<plugin>
    <groupId>com.jcabi</groupId>
    <artifactId>jcabi-maven-plugin</artifactId>
    <version>0.14.1</version>
    <executions>
        <execution>
            <goals>
                <goal>ajc</goal>
            </goals>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>1.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>
    </dependencies>
</plugin>

最後に、Mavenコマンドを使用してクラスをコンパイルしましょう。

mvn clean package

コンパイル時にjcabi-maven-pluginによって生成されるログは次のようになります。

[INFO] --- jcabi-maven-plugin:0.14.1:ajc (default) @ jcabi ---
[INFO] jcabi-aspects 0.18/55a5c13 started new daemon thread jcabi-loggable for watching of 
  @Loggable annotated methods
[INFO] Unwoven classes will be copied to /jcabi/target/unwoven
[INFO] Created temp dir /jcabi/target/jcabi-ajc
[INFO] jcabi-aspects 0.18/55a5c13 started new daemon thread jcabi-cacheable for automated
  cleaning of expired @Cacheable values
[INFO] ajc result: 11 file(s) processed, 0 pointcut(s) woven, 0 error(s), 0 warning(s)

ライブラリをプロジェクトに追加する方法がわかったので、アノテーションが機能しているかどうかを見てみましょう。

3. @Async

@Async アノテーションを使用すると、メソッドを非同期で実行できます。 ただし、voidまたはFutureタイプを返すメソッドとのみ互換性があります。

数値の階乗を非同期的に表示するdisplayFactorialメソッドを作成してみましょう。

@Async
public static void displayFactorial(int number) {
    long result = factorial(number);
    System.out.println(result);
}

次に、クラスを再コンパイルして、Mavenが@Asyncアノテーションのアスペクトを織り込むようにします。 最後に、例を実行できます。

[main] INFO com.jcabi.aspects.aj.NamedThreads - 
jcabi-aspects 0.22.6/3f0a1f7 started new daemon thread jcabi-async for Asynchronous method execution

ログからわかるように、ライブラリは、すべての非同期操作を実行するための個別のデーモンスレッドjcabi-asyncを作成します

次に、 @Async アノテーションを使用して、Futureインスタンスを返します。

@Async
public static Future<Long> getFactorial(int number) {
    Future<Long> factorialFuture = CompletableFuture.supplyAsync(() -> factorial(number));
    return factorialFuture;
}

voidまたはFutureを返さないメソッドで@Asyncを使用すると、実行時に呼び出し時に例外がスローされます。

4. @Cacheable

@Cacheable アノテーションを使用すると、メソッドの結果をキャッシュして、計算の重複を回避できます。

たとえば、最新の為替レートを返すcacheExchangeRatesメソッドを作成してみましょう。

@Cacheable(lifetime = 2, unit = TimeUnit.SECONDS)
public static String cacheExchangeRates() {
    String result = null;
    try {
        URL exchangeRateUrl = new URL("https://api.exchangeratesapi.io/latest");
        URLConnection con = exchangeRateUrl.openConnection();
        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        result = in.readLine();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return result;
}

ここで、キャッシュされた結果の有効期間は2秒になります。 同様に、次を使用して結果を永久にキャッシュ可能にすることができます。

@Cacheable(forever = true)

クラスを再コンパイルして再度実行すると、ライブラリは、キャッシュメカニズムを処理する2つのデーモンスレッドの詳細をログに記録します。

[main] INFO com.jcabi.aspects.aj.NamedThreads - 
jcabi-aspects 0.22.6/3f0a1f7 started new daemon thread jcabi-cacheable-clean for automated 
  cleaning of expired @Cacheable values
[main] INFO com.jcabi.aspects.aj.NamedThreads - 
jcabi-aspects 0.22.6/3f0a1f7 started new daemon thread jcabi-cacheable-update for async 
  update of expired @Cacheable values

cacheExchangeRates メソッドを呼び出すと、ライブラリは結果をキャッシュし、実行の詳細をログに記録します。

[main] INFO com.baeldung.jcabi.JcabiAspectJ - #cacheExchangeRates(): 
'{"rates":{"CAD":1.458,"HKD":8.5039,"ISK":137.9,"P..364..:4.5425},"base":"EUR","date":"2020-02-10"}'
  cached in 560ms, valid for 2s

したがって、(2秒以内に)再度呼び出されると、cacheExchangeRatesはキャッシュから結果を返します。

[main] INFO com.baeldung.jcabi.JcabiAspectJ - #cacheExchangeRates(): 
'{"rates":{"CAD":1.458,"HKD":8.5039,"ISK":137.9,"P..364..:4.5425},"base":"EUR","date":"2020-02-10"}'
  from cache (hit #1, 563ms old)

メソッドが例外をスローした場合、結果はキャッシュされません。

5. @Loggable

ライブラリは、SLF4Jロギング機能を使用した単純なロギングのための@Loggableアノテーションを提供します。

@LoggableアノテーションをdisplayFactorialおよびcacheExchangeRatesメソッドに追加しましょう。

@Loggable
@Async
public static void displayFactorial(int number) {
    ...
}

@Loggable
@Cacheable(lifetime = 2, unit = TimeUnit.SECONDS)
public static String cacheExchangeRates() {
    ...
}

次に、再コンパイル後、アノテーションはメソッド名、戻り値、および実行時間をログに記録します。

[main] INFO com.baeldung.jcabi.JcabiAspectJ - #displayFactorial(): in 1.16ms
[main] INFO com.baeldung.jcabi.JcabiAspectJ - #cacheExchangeRates(): 
'{"rates":{"CAD":1.458,"HKD":8.5039,"ISK":137.9,"P..364..:4.5425},"base":"EUR","date":"2020-02-10"}'
  in 556.92ms

6. @LogExceptions

@Loggable と同様に、 @LogExceptions アノテーションを使用して、メソッドによってスローされた例外のみをログに記録できます。

ArithmeticExceptionをスローするメソッドdivideByZero@LogExceptionsを使用してみましょう。

@LogExceptions
public static void divideByZero() {
    int x = 1/0;
}

メソッドを実行すると、例外がログに記録され、例外がスローされます。

[main] WARN com.baeldung.jcabi.JcabiAspectJ - java.lang.ArithmeticException: / by zero
    at com.baeldung.jcabi.JcabiAspectJ.divideByZero_aroundBody12(JcabiAspectJ.java:77)

java.lang.ArithmeticException: / by zero
    at com.baeldung.jcabi.JcabiAspectJ.divideByZero_aroundBody12(JcabiAspectJ.java:77)
    ...

7. @Quietly

@Quietly アノテーションは、がメソッドによってスローされた例外を伝播しないことを除いて、@LogExceptionsに似ています。 代わりに、ログに記録するだけです。

@QuietlyアノテーションをdivideByZeroメソッドに追加しましょう。

@Quietly
public static void divideByZero() {
    int x = 1/0;
}

したがって、アノテーションは例外を飲み込み、そうでなければスローされたはずの例外の詳細のみをログに記録します。

[main] WARN com.baeldung.jcabi.JcabiAspectJ - java.lang.ArithmeticException: / by zero
    at com.baeldung.jcabi.JcabiAspectJ.divideByZero_aroundBody12(JcabiAspectJ.java:77)

@Quietly アノテーションは、voidリターン型を持つメソッドとのみ互換性があります。

8. @RetryOnFailure

@RetryOnFailure アノテーションを使用すると、例外または失敗が発生した場合にメソッドの実行を繰り返すことができます。

たとえば、@RetryOnFailureアノテーションをdivideByZeroメソッドに追加しましょう。

@RetryOnFailure(attempts = 2)
@Quietly
public static void divideByZero() {
    int x = 1/0;
}

したがって、メソッドが例外をスローした場合、AOPアドバイスはそれを2回実行しようとします。

[main] WARN com.baeldung.jcabi.JcabiAspectJ - 
#divideByZero(): attempt #1 of 2 failed in 147µs with java.lang.ArithmeticException: / by zero
[main] WARN com.baeldung.jcabi.JcabiAspectJ - 
#divideByZero(): attempt #2 of 2 failed in 110µs with java.lang.ArithmeticException: / by zero

また、 @RetryOnFailure アノテーションを宣言しながら、 delay unit typesなどの他のパラメーターを定義できます。

@RetryOnFailure(attempts = 3, delay = 5, unit = TimeUnit.SECONDS, 
  types = {java.lang.NumberFormatException.class})

この場合、AOPアドバイスは、メソッドが NumberFormatException をスローした場合にのみ、試行の間に5秒の遅延を伴って、メソッドを3回試行します。

9. @UnitedThrow

@UnitedThrow アノテーションを使用すると、メソッドによってスローされたすべての例外をキャッチし、指定した例外でラップできます。 したがって、メソッドによってスローされた例外を統合します。

たとえば、IOExceptionInterruptedExceptionをスローするメソッドprocessFileを作成しましょう。

@UnitedThrow(IllegalStateException.class)
public static void processFile() throws IOException, InterruptedException {
    BufferedReader reader = new BufferedReader(new FileReader("baeldung.txt"));
    reader.readLine();
    // additional file processing
}

ここでは、すべての例外をIllegalStateExceptionにラップするアノテーションを追加しました。 したがって、メソッドが呼び出されると、例外のスタックトレースは次のようになります。

java.lang.IllegalStateException: java.io.FileNotFoundException: baeldung.txt (No such file or directory)
    at com.baeldung.jcabi.JcabiAspectJ.processFile(JcabiAspectJ.java:92)
    at com.baeldung.jcabi.JcabiAspectJ.main(JcabiAspectJ.java:39)
Caused by: java.io.FileNotFoundException: baeldung.txt (No such file or directory)
    at java.io.FileInputStream.open0(Native Method)
    ...

10. 結論

この記事では、 jcabi-aspectsJavaライブラリについて説明しました。

まず、 jcabi-maven-plugin を使用して、Mavenプロジェクトでライブラリーをセットアップする簡単な方法を見てきました。

次に、 @Async @Loggable @RetryOnFailure など、AOPを使用してJavaアプリケーションの動作を変更するいくつかの便利なアノテーションを調べました。

いつものように、すべてのコード実装はGitHub利用できます。