こんにちは皆さん!基本的な概念を理解していなければ、機能を構築するためのフレームワークやアプローチを掘り下げることは非常に困難です。そこで今日は、これらの概念の 1 つである AOP (アスペクト指向プログラミング)について説明します。これは簡単なトピックではなく、直接使用されることはあまりありませんが、多くのフレームワークやテクノロジーが内部でこれを使用しています。そしてもちろん、面接中に、これがどのような種類の動物で、どこで使用できるかについて一般的な言葉で説明するように求められる場合もあります。それでは、 Java における AOP の基本概念といくつかの簡単な例を見てみましょう。つまり、AOP (アスペクト指向プログラミング) は、横断的な関心事を分離することでアプリケーションのさまざまな部分のモジュール性を高めることを目的としたパラダイムです。これを行うには、元のコードを変更せずに、既存のコードに追加の動作を追加します。言い換えれば、変更されたコードを修正することなく、メソッドやクラスの上に追加の機能を追加しているようです。なぜこれが必要なのでしょうか? 遅かれ早かれ、通常のオブジェクト指向アプローチでは特定の問題を常に効果的に解決できるわけではないという結論に達します。このような瞬間に、 AOP が役に立ち、アプリケーションを構築するための追加ツールを提供します。また、ツールを追加すると、特定の問題を解決するためのオプションが増えるため、開発の柔軟性が向上します。
AOPの適用
アスペクト指向プログラミングは、横断的な問題を解決するように設計されています。横断的な問題とは、別のモジュールに完全に構造化できない、さまざまな方法で何度も繰り返されるコードのことです。したがって、AOPを使用すると、これをメイン コードの外に残し、垂直に定義できます。例としては、アプリケーションへのセキュリティ ポリシーの適用があります。通常、セキュリティはアプリケーションの多くの要素にまたがります。さらに、アプリケーション セキュリティ ポリシーは、アプリケーションのすべての既存部分と新しい部分に均等に適用される必要があります。同時に、使用されるセキュリティ ポリシー自体も進化する可能性があります。このような場合に、 AOPの使用が役立ちます。別の例としては、ロギングがあります。手動でログを挿入する場合と比較して、 AOPアプローチを使用してログを記録することには、いくつかの利点があります。- ロギング コードの実装と削除は簡単です。必要なのは、ある側面のいくつかの構成を追加または削除するだけです。
- ロギング用のすべてのソース コードは 1 か所に保存されるため、すべての使用場所を手動で見つける必要はありません。
- ロギングを目的としたコードは、すでに記述されたメソッドやクラスであっても、新しい機能であっても、どこにでも追加できます。これにより、開発者のエラーの数が減少します。
また、デザイン コンフィギュレーションからアスペクトを削除すると、すべてのトレース コードが削除され、何も欠けていないことを確実に確認できます。 - アスペクトは、何度も再利用および改善できるスタンドアロン コードです。
AOP の基本概念
このトピックの分析をさらに進めるために、まず AOP の主な概念について理解しましょう。 アドバイスは、接続ポイントから呼び出される追加のロジック、コードです。このアドバイスは、接続ポイントの前、後、または接続ポイントの代わりに実行できます (詳細は後述します)。考えられるアドバイスの種類:- Before (Before) - このタイプのアドバイスは、ターゲット メソッド - 接続ポイントの実行前に起動されます。アスペクトをクラスとして使用する場合、@Beforeアノテーションを使用して、アドバイス タイプを前に来るものとしてマークします。アスペクトを.ajファイルとして使用する場合、これはbefore()メソッドになります。
- After (After) - 通常の場合と例外がスローされた場合の両方で、メソッド - 接続ポイントの実行完了後に実行されるアドバイス。
アスペクトをクラスとして使用する場合、@Afterアノテーションを使用して、これが後に来るヒントであることを示すことができます。アスペクトを.aj
ファイル として使用する場合、これはafter()メソッドになります。 - 復帰後- これらのヒントは、ターゲット メソッドがエラーなく正常に動作する場合にのみ実行されます。
アスペクトがクラスとして表される場合、@AfterReturningアノテーションを使用して、アドバイスが正常に完了したときに実行されるものとしてマークできます。
アスペクトを .aj ファイルとして使用する場合、これは (Object obj) を返すafter()メソッドになります。 - スロー後- このタイプのアドバイスは、メソッド、つまり接続ポイントが例外をスローする場合を対象としています。このアドバイスは、失敗した実行の処理 (たとえば、トランザクション全体のロールバックや、必要なトレース レベルでのログ記録など) に使用できます。
アスペクト クラスの場合、@AfterThrowingアノテーションは、例外がスローされた後にこのアドバイスが使用されることを示すために使用されます。.aj
ファイル の形式でアスペクトを使用する場合、これはメソッド - after() throw (Exception e)になります。 - Around はおそらく、メソッド、つまり接続ポイントを囲む最も重要なタイプのアドバイスの 1 つであり、これにより、たとえば、指定された接続ポイント メソッドを実行するかどうかを選択できます。
ジョインポイントメソッドの実行前後に実行されるアドバイスコードを作成できます。アドバイスの
責任には、ジョイン ポイント メソッドの呼び出しと、メソッドが何かを返した場合に値を返すことが含まれます。つまり、このヒントでは、ターゲット メソッドを呼び出さずにその操作を単純に模倣し、結果として独自の何かを返すことができます。 クラス形式のアスペクトの場合、@Aroundアノテーションを使用して、接続ポイントをラップするヒントを作成します。アスペクトを.ajファイルとして使用する場合、これはaround()メソッドになります。
- コンパイル時ウィービング- アスペクトのソース コードとアスペクトを使用するコードがある場合は、AspectJ コンパイラーを使用してソース コードとアスペクトを直接コンパイルできます。
- コンパイル後のウィービング(バイナリ ウィービング) - ソース コード変換を使用してアスペクトをコードに組み込むことができない、または使用したくない場合は、コンパイル済みのクラスまたは jar を取得してアスペクトを注入できます。
- ロード時ウィービングは、クラス ローダーがクラス ファイルをロードして JVM のクラスを定義するまで延期される単純なバイナリ ウィービングです。
これをサポートするには、1 つ以上の「weave クラス ローダー」が必要です。これらは、ランタイムによって明示的に提供されるか、「ウィービング エージェント」によってアクティブ化されます。
Java での例
次に、AOP をよりよく理解するために、 Hello World レベルの小さな例を見ていきます。この例では、コンパイル時のウィービングを使用することにすぐに注意してください。まず、次の依存関係をpom.xmlに追加する必要があります。<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
アスペクトを使用するには、原則として、特別なAjs コンパイラが使用されます。IntelliJ IDEAにはデフォルトでこれが含まれていないため、アプリケーション コンパイラーとして選択する場合は、 AspectJディストリビューションへのパスを指定する必要があります。Ajs をコンパイラーとして選択する方法の詳細については、このページを参照してください。これが最初の方法であり、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("Толя");
printName("Вова");
printName("Sasha");
}
public static void printName(String name) {
System.out.println(name);
}
}
何も複雑なことはありません。名前を渡してコンソールに表示しました。ここで実行すると、コンソールに次のように表示されます。
トーリヤ・ヴォヴァ・サーシャ
さて、AOP のパワーを活用するときが来ました。次に、ファイルアスペクトを作成する必要があります。これらには 2 つのタイプがあります。1 つ目は.aj拡張子を持つファイルで、2 つ目はアノテーションを使用してAOP機能を実装する通常のクラスです。まず拡張子が.ajのファイルを見てみましょう。
public aspect GreetingAspect {
pointcut greeting() : execution(* Main.printName(..));
before() : greeting() {
System.out.print("Привет ");
}
}
このファイルはクラスに似ています。ここで何が起こっているのか理解してみましょう。 ポイントカット- カットまたは接続ポイントのセット。 greeting() — このスライスの名前。 : 実行- *を実行するとき- すべて、 - Main.printName(..) - このメソッドを呼び出します。次に、特定のアドバイス - before() - が来ます。これはターゲット メソッドが呼び出される前に実行されます。greeting() - このアドバイスが反応するスライスです。その下に、Java で書かれたメソッド自体の本体が表示されます。私たちが理解できる言語。このアスペクトが存在する状態でmainを実行すると、コンソールに次の出力が表示されます。
こんにちは、トーリヤ、こんにちは、ヴォヴァ、こんにちは、サーシャ
printNameメソッドへのすべての呼び出しがアスペクトによって変更されていること がわかります。次に、アスペクトがどのようになるかを見てみましょう。ただし、注釈付きの Java クラスとして表示されます。
@Aspect
public class GreetingAspect{
@Pointcut("execution(* Main.printName(String))")
public void greeting() {
}
@Before("greeting()")
public void beforeAdvice() {
System.out.print("Привет ");
}
}
.ajアスペクト ファイル の後では、すべてがより明白になります。
- @Aspect は、指定されたクラスがアスペクトであることを示します。 @Pointcut("execution(* Main.printName(String))") は、String型の受信引数を持つMain.printName へのすべての呼び出しで起動されるカット ポイントです。
- @Before("greeting()") -greeting()のカットポイントに記述されているコードを呼び出す前に適用されるアドバイス。
こんにちは、トーリヤ、こんにちは、ヴォヴァ、こんにちは、サーシャ
例その2
クライアントに対していくつかの操作を実行するメソッドがあり、このメソッドをmainから呼び出すとします。public class Main {
public static void main(String[] args) {
makeSomeOperation("Толя");
}
public static void makeSomeOperation(String clientName) {
System.out.println("Выполнение некоторых операций для клиента - " + clientName);
}
}
@Aroundアノテーション を使用して、「疑似トランザクション」のようなものを作成します。
@Aspect
public class TransactionAspect{
@Pointcut("execution(* Main.makeSomeOperation(String))")
public void executeOperation() {
}
@Around(value = "executeOperation()")
public void beforeAdvice(ProceedingJoinPoint joinPoint) {
System.out.println("Открытие транзакции...");
try {
joinPoint.proceed();
System.out.println("Закрытие транзакции....");
}
catch (Throwable throwable) {
System.out.println("Операция не удалась, откат транзакции...");
}
}
}
ProceedingJoinPointオブジェクトのprogressメソッド を使用して、ラッパーのメソッドを呼び出してボード内の位置を決定し、それに応じて、上のメソッドのコードjoinPoint.proceed();を決定します。- これはBeforeで、その下は - Afterです。mainを実行すると、コンソールが表示されます。
トランザクションを開始しています... クライアントに対していくつかの操作を実行しています - Tolya トランザクションを終了しています....
メソッドに例外スローを追加すると (突然操作が失敗します):
public static void makeSomeOperation(String clientName)throws Exception {
System.out.println("Выполнение некоторых операций для клиента - " + clientName);
throw new Exception();
}
次に、コンソールに出力が表示されます。
トランザクションをオープンしています... クライアントに対していくつかの操作を実行しています - Tolya 操作は失敗しました。トランザクションはロールバックされました...
失敗の擬似処理であることが判明しました。
例その3
次の例として、コンソールにログインするようなことをしてみましょう。まず、疑似ビジネス ロジックが実行される Mainを見てみましょう。public class Main {
private String value;
public static void main(String[] args) throws Exception {
Main main = new Main();
main.setValue("<некоторое meaning>");
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("Успешно выполнен метод - %s, класса- %s, с результатом выполнения - %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
returningValue);
}
else {
System.out.printf("Успешно выполнен метод - %s, класса- %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName());
}
}
@AfterThrowing(value = "methodExecuting()", throwing = "exception")
public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
System.out.printf("Метод - %s, класса- %s, был аварийно завершен с исключением - %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
exception);
}
}
何が起きてる? @Pointcut("execution(* *(..))") - すべてのメソッドのすべての呼び出しに接続します。 @AfterReturning(value = "methodExecuting()",returning = "returningValue") - ターゲット メソッドが正常に完了した後に実行されるアドバイス。ここには 2 つのケースがあります。
- メソッドに戻り値がある場合if (returningValue != null) {
- 戻り値がない場合else {
クラス - Main のメソッド - setValue は正常に実行されました。 クラス - Main のメソッド - getValue は正常に実行され、実行結果 - <何らかの値> クラス - Main のメソッド - checkValue例外で異常終了しました - java.lang.Exception メソッド - main、class-Main、例外でクラッシュしました - java.lang.Exception
例外を処理しなかったので、そのスタックトレースも取得します。例外とその処理については、 「Java の例外」および「例外とその処理」の記事を参照してください。今日はここまでです。今日、私たちはAOPについて知りました。そして、この獣が描かれているほど怖くないことがわかりました。 さようなら皆さん!
GO TO FULL VERSION