
AOPの適用
アスペクト指向プログラミングは、横断的なタスクを実行するように設計されています。横断的なタスクは、別のモジュールに完全に構造化することができない、さまざまな方法で何度も繰り返すことができる任意のコードにすることができます。したがって、AOP ではこれをメイン コードの外側に保持し、垂直方向に宣言できます。例としては、アプリケーションでのセキュリティ ポリシーの使用があります。通常、セキュリティはアプリケーションの多くの要素を通じて実行されます。さらに、アプリケーションのセキュリティ ポリシーは、アプリケーションのすべての既存部分と新しい部分に等しく適用される必要があります。同時に、使用中のセキュリティ ポリシー自体が進化する可能性があります。これはAOP を使用するのに最適な場所です。また、別の例としてはログ記録があります。。ロギング機能を手動で追加するのではなく、AOP アプローチをロギングに使用することには、いくつかの利点があります。-
ロギング用のコードは簡単に追加および削除できます。必要なのは、いくつかの側面の構成を追加または削除することだけです。
-
ロギング用のすべてのソース コードは 1 か所に保存されるため、コードが使用されているすべての場所を手動で探す必要はありません。
-
ロギング コードは、すでに記述されたメソッドやクラスであっても、新しい機能であっても、どこにでも追加できます。これにより、コーディングエラーの数が減少します。
また、デザイン構成からアスペクトを削除すると、すべてのトレース コードが削除され、何も欠落していないことを確認できます。
- アスペクトは、改善して繰り返し使用できる個別のコードです。

AOPの基本原則
このトピックをさらに進めるために、まず AOP の主な概念を理解しましょう。 アドバイス— 結合ポイントから呼び出される追加のロジックまたはコード。アドバイスは、結合ポイントの前、後、またはその代わりに実行できます (詳細は以下を参照)。考えられるアドバイスの種類:-
Before — このタイプのアドバイスは、ターゲット メソッド、つまりジョイン ポイントが実行される前に起動されます。アスペクトをクラスとして使用する場合、@Beforeアノテーションを使用して、アドバイスが前に来るものとしてマークします。アスペクトを.ajファイルとして使用する場合、これはbefore()メソッドになります。
- After — 通常の実行時と例外スロー時の両方で、メソッド (結合ポイント) の実行が完了した後に実行されるアドバイス。
アスペクトをクラスとして使用する場合、@Afterアノテーションを使用して、これが後に続くアドバイスであることを示すことができます。
アスペクトを.ajファイルとして使用する場合、これはafter()メソッドです。
-
復帰後— このアドバイスは、ターゲット メソッドがエラーなく正常に終了した場合にのみ実行されます。
アスペクトがクラスとして表される場合、@AfterReturningアノテーションを使用して、アドバイスが正常に完了した後に実行されるとマークすることができます。
アスペクトを .ajファイルとして使用する場合、これは(Object obj) メソッドを返す after()になります。
-
スロー後- このアドバイスは、メソッド、つまりジョインポイントが例外をスローした場合を対象としています。このアドバイスを使用して、特定の種類の失敗した実行を処理できます (たとえば、トランザクション全体をロールバックしたり、必要なトレース レベルでログを作成したりする場合など)。
クラスの側面では、@AfterThrowingアノテーションを使用して、このアドバイスが例外をスローした後に使用されることを示します。
アスペクトを.ajファイルとして使用する場合、これはafter() スロー (例外 e)メソッドになります。
-
おそらく最も重要な種類のアドバイスの 1 つです。これはメソッド、つまり、たとえば、特定のジョイン ポイント メソッドを実行するかどうかを選択するために使用できるジョイン ポイントを囲みます。
ジョインポイントメソッドの実行前後に実行されるアドバイスコードを作成できます。
アラウンド アドバイスは、ジョイン ポイント メソッドの呼び出しと、メソッドが何かを返す場合の戻り値を担当します。つまり、このアドバイスでは、ターゲット メソッドを呼び出すことなく、そのメソッドの動作を単純にシミュレートし、必要なものを戻り結果として返すことができます。
アスペクトをクラスとして指定すると、@Aroundアノテーションを使用して、結合ポイントをラップするアドバイスを作成します。.ajファイルの形式でアスペクトを使用する場合、このメソッドはaround()メソッドになります。
-
コンパイル時ウィービング— アスペクトのソース コードとアスペクトを使用するコードがある場合は、AspectJ コンパイラーを使用してソース コードとアスペクトを直接コンパイルできます。
-
コンパイル後のウィービング (バイナリ ウィービング) — ソース コード変換を使用してアスペクトをコードに組み込むことができない、または使いたくない場合は、以前にコンパイルしたクラスまたは jar ファイルを取得して、それらにアスペクトを注入できます。
-
ロード時ウィービング— これは、クラスローダーがクラス ファイルをロードして JVM のクラスを定義するまで遅延される単なるバイナリ ウィービングです。
これをサポートするには、1 つ以上のウィービング クラス ローダーが必要です。これらは、ランタイムによって明示的に提供されるか、「ウィービング エージェント」によってアクティブ化されます。
Java での例
次に、 AOPをよりよく理解するために、「Hello World」スタイルの小さな例を見ていきます。右側に、この例ではコンパイル時のウィービングを使用することに注意してください。まず、 pom.xmlファイルに次の依存関係を追加する必要があります。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
原則として、特別なajcコンパイラーを使用してアスペクトを使用します。IntelliJ IDEAにはデフォルトではこれが含まれていないため、アプリケーション コンパイラーとして選択する場合は、5168 75 AspectJディストリビューションへのパスを指定する必要があります。これが最初の方法でした。2 つ目は、私が使用したものですが、次のプラグインをpom.xmlファイルに登録することです。
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<<verbose>true<verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
この後、 Mavenから再インポートしてmvn clean COMPILEを実行する ことをお勧めします。それでは、直接例に進みましょう。
例その1
Mainクラスを作成しましょう。その中には、エントリ ポイントと、渡された名前をコンソールに出力するメソッドがあります。
public class Main {
public static void main(String[] args) {
printName("Tanner");
printName("Victor");
printName("Sasha");
}
public static void printName(String name) {
System.out.println(name);
}
}
ここでは複雑なことは何もありません。名前を渡してコンソールに表示します。ここでプログラムを実行すると、コンソールに次の内容が表示されます。
public aspect GreetingAspect {
pointcut greeting() : execution(* Main.printName(..));
before() : greeting() {
System.out.print("Hi, ");
}
}
このファイルはクラスのようなものです。ここで何が起こっているのか見てみましょう。 ポイントカットは結合ポイントのセットです。 greeting()はこのポイントカットの名前です。 : 実行は、Main.printName(...)メソッドのすべての ( * ) 呼び出しの実行中に適用することを示します。次に、ターゲット メソッドが呼び出される前に実行される特定のアドバイス — before() — が続きます。:greeting() は、このアドバイスが応答するカットポイントです。さて、以下にメソッド自体の本体が表示されます。これは、私たちが理解している Java 言語で書かれています。このアスペクトが存在する状態でmainを実行すると、次のコンソール出力が得られます。
@Aspect
public class GreetingAspect{
@Pointcut("execution(* Main.printName(String))")
public void greeting() {
}
@Before("greeting()")
public void beforeAdvice() {
System.out.print("Hi, ");
}
}
.ajアスペクト ファイル の後では、すべてがより明確になります。
- @Aspect は、このクラスがアスペクトであることを示します。
- @Pointcut("execution(* Main.printName(String))") は、型がString の入力引数を使用したMain.printNameへのすべての呼び出しに対してトリガーされるカットポイントです。
- @Before("greeting()") は、greeting()カットポイントで指定されたコードを呼び出す前に適用されるアドバイスです。
例その2
クライアントに対していくつかの操作を実行するメソッドがあり、このメソッドをmainから呼び出すとします。
public class Main {
public static void main(String[] args) {
performSomeOperation("Tanner");
}
public static void performSomeOperation(String clientName) {
System.out.println("Performing some operations for Client " + clientName);
}
}
@Aroundアノテーションを使用して「疑似トランザクション」を作成し ましょう。
@Aspect
public class TransactionAspect{
@Pointcut("execution(* Main.performSomeOperation(String))")
public void executeOperation() {
}
@Around(value = "executeOperation()")
public void beforeAdvice(ProceedingJoinPoint joinPoint) {
System.out.println("Opening a transaction...");
try {
joinPoint.proceed();
System.out.println("Closing a transaction...");
}
catch (Throwable throwable) {
System.out.println("The operation failed. Rolling back the transaction...");
}
}
}
ProceedingJoinPointオブジェクトのprogressメソッド を使用して、ラッピング メソッドを呼び出して、アドバイス内の位置を決定します。したがって、上記のメソッドのコードjoinPoint.proceed(); はBefore、その下のコードはAfterです。mainを実行すると、コンソールに次の情報が表示されます。
public static void performSomeOperation(String clientName) throws Exception {
System.out.println("Performing some operations for Client " + clientName);
throw new Exception();
}
次に、次のコンソール出力が得られます。
例その3
次の例では、コンソールへのログ記録などを実行してみましょう。まず、疑似ビジネス ロジックを追加した Mainを見てください。
public class Main {
private String value;
public static void main(String[] args) throws Exception {
Main main = new Main();
main.setValue("<some value>");
String valueForCheck = main.getValue();
main.checkValue(valueForCheck);
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
public void checkValue(String value) throws Exception {
if (value.length() > 10) {
throw new Exception();
}
}
}
main では、 setValueを使用してvalueインスタンス変数に値を割り当てます。次に、 getValueを使用して値を取得し、checkValue を呼び出してそれが 10 文字を超えているかどうかを確認します。その場合、例外がスローされます。次に、メソッドの作業をログに記録するために使用する側面を見てみましょう。
@Aspect
public class LogAspect {
@Pointcut("execution(* *(..))")
public void methodExecuting() {
}
@AfterReturning(value = "methodExecuting()", returning = "returningValue")
public void recordSuccessfulExecution(JoinPoint joinPoint, Object returningValue) {
if (returningValue != null) {
System.out.printf("Successful execution: method — %s method, class — %s class, return value — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
returningValue);
}
else {
System.out.printf("Successful execution: method — %s, class — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName());
}
}
@AfterThrowing(value = "methodExecuting()", throwing = "exception")
public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
System.out.printf("Exception thrown: method — %s, class — %s, exception — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
exception);
}
}
何が起きてる? @Pointcut("execution(* *(..))") は、すべてのメソッドのすべての呼び出しに参加します。 @AfterReturning(value = "methodExecuting()",returning = "returningValue") は、ターゲット メソッドの実行が成功した後に実行されるアドバイスです。ここには 2 つのケースがあります。
- メソッドに戻り値がある場合 — if (returningValue! = Null) {
- 戻り値がない場合 — else {

GO TO FULL VERSION