CodeGym /Java Blog /ランダム /AOPとは何ですか? アスペクト指向プログラミングの原則
John Squirrels
レベル 41
San Francisco

AOPとは何ですか? アスペクト指向プログラミングの原則

ランダム グループに公開済み
こんにちは、みんな!基本的な概念を理解していなければ、機能を構築するためのフレームワークやアプローチを掘り下げることは非常に困難です。そこで今日は、そのような概念の 1 つである AOP、別名アスペクト指向プログラミングについて説明します。AOPとは何ですか?  アスペクト指向プログラミングの原則 - 1このトピックは簡単ではなく、直接使用されることはほとんどありませんが、多くのフレームワークやテクノロジーが内部でこれを使用しています。そしてもちろん、面接中に、これがどのような種類の獣であり、それがどこに適用できるかを一般的な言葉で説明するよう求められることもあります。それでは、 Java における AOPの基本概念といくつかの簡単な例を見てみましょう。さて、AOPとアスペクト指向プログラミングの略ですこれは、横断的な関心事を分離することで、アプリケーションのさまざまな部分のモジュール性を高めることを目的としたパラダイムです。これを実現するには、元のコードを変更せずに、既存のコードに動作を追加します。言い換えれば、変更されたコードを変更せずに、メソッドやクラスの上に追加の機能をぶら下げていると考えることができます。なぜこれが必要なのでしょうか? 遅かれ早かれ、典型的なオブジェクト指向アプローチでは特定の問題を常に効果的に解決できるとは限らないという結論に達します。その瞬間が来ると、AOP が助けとなって、アプリケーションを構築するための追加ツールを提供します。また、ツールの追加はソフトウェア開発の柔軟性の向上を意味し、特定の問題を解決するための選択肢が増えることを意味します。

AOPの適用

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

  2. ロギング用のすべてのソース コードは 1 か所に保存されるため、コードが使用されているすべての場所を手動で探す必要はありません。

  3. ロギング コードは、すでに記述されたメソッドやクラスであっても、新しい機能であっても、どこにでも追加できます。これにより、コーディングエラーの数が減少します。

    また、デザイン構成からアスペクトを削除すると、すべてのトレース コードが削除され、何も欠落していないことを確認できます。

  4. アスペクトは、改善して繰り返し使用できる個別のコードです。
AOPとは何ですか?  アスペクト指向プログラミングの原則 - 2AOP は、例外処理、キャッシュ、および再利用可能にするための特定の機能の抽出にも使用されます。

AOPの基本原則

このトピックをさらに進めるために、まず AOP の主な概念を理解しましょう。 アドバイス— 結合ポイントから呼び出される追加のロジックまたはコード。アドバイスは、結合ポイントの前、後、またはその代わりに実行できます (詳細は以下を参照)。考えられるアドバイスの種類:
  1. Before — このタイプのアドバイスは、ターゲット メソッド、つまりジョイン ポイントが実行される前に起動されます。アスペクトをクラスとして使用する場合、@Beforeアノテーションを使用して、アドバイスが前に来るものとしてマークします。アスペクトを.ajファイルとして使用する場合、これはbefore()メソッドになります。

  2. After — 通常の実行時と例外スロー時の両方で、メソッド (結合ポイント) の実行が完了した後に実行されるアドバイス。

    アスペクトをクラスとして使用する場合、@Afterアノテーションを使用して、これが後に続くアドバイスであることを示すことができます。

    アスペクトを.ajファイルとして使用する場合、これはafter()メソッドです。

  3. 復帰後— このアドバイスは、ターゲット メソッドがエラーなく正常に終了した場合にのみ実行されます。

    アスペクトがクラスとして表される場合、@AfterReturningアノテーションを使用して、アドバイスが正常に完了した後に実行されるとマークすることができます。

    アスペクトを .ajファイルとして使用する場合、これは(Object obj) メソッドを返す after()になります。

  4. スロー後- このアドバイスは、メソッド、つまりジョインポイントが例外をスローした場合を対象としています。このアドバイスを使用して、特定の種類の失敗した実行を処理できます (たとえば、トランザクション全体をロールバックしたり、必要なトレース レベルでログを作成したりする場合など)。

    クラスの側面では、@AfterThrowingアノテーションを使用して、このアドバイスが例外をスローした後に使用されることを示します。

    アスペクトを.ajファイルとして使用する場合、これはafter() スロー (例外 e)メソッドになります。

  5. おそらく最も重要な種類のアドバイスの 1 つです。これはメソッド、つまり、たとえば、特定のジョイン ポイント メソッドを実行するかどうかを選択するために使用できるジョイン ポイントを囲みます。

    ジョインポイントメソッドの実行前後に実行されるアドバイスコードを作成できます。

    アラウンド アドバイスは、ジョイン ポイント メソッドの呼び出しと、メソッドが何かを返す場合の戻り値を担当します。つまり、このアドバイスでは、ターゲット メソッドを呼び出すことなく、そのメソッドの動作を単純にシミュレートし、必要なものを戻り結果として返すことができます。

    アスペクトをクラスとして指定すると、@Aroundアノテーションを使用して、結合ポイントをラップするアドバイスを作成します。.ajファイルの形式でアスペクトを使用する場合、このメソッドはaround()メソッドになります。

結合ポイント— 実行中のプログラム (つまり、メソッド呼び出し、オブジェクト作成、変数アクセス) 内でアドバイスを適用する必要があるポイント。つまり、コードインジェクションの場所(アドバイスを適用すべき場所)を見つけるために使用される一種の正規表現です。 ポイントカット—結合点のセット。ポイントカットは、指定されたアドバイスが指定された結合ポイントに適用できるかどうかを決定します。 アスペクト— 横断的な機能を実装するモジュールまたはクラス。アスペクトは、ポイントカットによって定義された結合ポイントアドバイスを適用することで、残りのコードの動作を変更します。言い換えれば、アドバイスと参加ポイントの組み合わせです。 序章— クラスの構造を変更したり、継承階層を変更して、アスペクトの機能を外部コードに追加します。 ターゲット— アドバイスが適用されるオブジェクト。 ウィービング— アスペクトを他のオブジェクトにリンクして、推奨されるプロキシ オブジェクトを作成するプロセス。これは、コンパイル時、ロード時、または実行時に行うことができます。織り方には次の3種類があります。
  • コンパイル時ウィービング— アスペクトのソース コードとアスペクトを使用するコードがある場合は、AspectJ コンパイラーを使用してソース コードとアスペクトを直接コンパイルできます。

  • コンパイル後のウィービング (バイナリ ウィービング) — ソース コード変換を使用してアスペクトをコードに組み込むことができない、または使いたくない場合は、以前にコンパイルしたクラスまたは jar ファイルを取得して、それらにアスペクトを注入できます。

  • ロード時ウィービング— これは、クラスローダーがクラス ファイルをロードして JVM のクラスを定義するまで遅延される単なるバイナリ ウィービングです。

    これをサポートするには、1 つ以上のウィービング クラス ローダーが必要です。これらは、ランタイムによって明示的に提供されるか、「ウィービング エージェント」によってアクティブ化されます。

AspectJ —横断的なタスクを実行する機能を実装するAOPパラダイムの特定の実装。ドキュメントはここにあります。

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);
  }
}
ここでは複雑なことは何もありません。名前を渡してコンソールに表示します。ここでプログラムを実行すると、コンソールに次の内容が表示されます。
タナー・ヴィクター・サーシャ
さあ、今度は AOP のパワーを活用しましょう。次に、アスペクトファイルを作成する必要があります。これらには 2 種類あり、1 つはファイル拡張子が.ajです。2 つ目は、アノテーションを使用してAOP機能を実装する通常のクラスです。まず、 .aj拡張子を持つファイルを見てみましょう。

public aspect GreetingAspect {
 
  pointcut greeting() : execution(* Main.printName(..));
 
  before() : greeting() {
     System.out.print("Hi, ");
  }
}
このファイルはクラスのようなものです。ここで何が起こっているのか見てみましょう。 ポイントカットは結合ポイントのセットです。 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("Hi, ");
  }
}
.ajアスペクト ファイル の後では、すべてがより明確になります。
  • @Aspect は、このクラスがアスペクトであることを示します。
  • @Pointcut("execution(* Main.printName(String))") は、型がString の入力引数を使用したMain.printNameへのすべての呼び出しに対してトリガーされるカットポイントです。
  • @Before("greeting()") は、greeting()カットポイントで指定されたコードを呼び出す前に適用されるアドバイスです。
このアスペクトでmainを実行しても、コンソール出力は変わりません。
こんにちは、タナー、こんにちは、ビクター、こんにちは、サーシャ

例その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を実行すると、コンソールに次の情報が表示されます。
トランザクションを開始しています... Client Tanner に対していくつかの操作を実行しています トランザクションを終了しています...
ただし、(失敗した操作をシミュレートするために) メソッドで例外をスローすると、次のようになります。

public static void performSomeOperation(String clientName) throws Exception {
  System.out.println("Performing some operations for Client " + clientName);
  throw new Exception();
}
次に、次のコンソール出力が得られます。
トランザクションを開いています... Client Tanner に対していくつかの操作を実行しています 操作は失敗しました。トランザクションをロールバックしています...
したがって、ここで最終的に完成したのは、一種のエラー処理機能です。

例その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 つのケースがあります。
  1. メソッドに戻り値がある場合 — if (returningValue! = Null) {
  2. 戻り値がない場合 — else {
@AfterThrowing(value = "methodExecuting()", throwing = "Exception") は、エラーの場合、つまりメソッドが例外をスローした場合にトリガーされるアドバイスです。したがって、mainを実行すると、一種のコンソールベースのログが取得されます。
実行成功: メソッド — setValue、クラス — Main 実行成功: メソッド — getValue、クラス — Main、戻り値 — <何らかの値> スローされた例外: メソッド — checkValue、クラス — メイン例外 — java.lang.Exception スローされた例外: メソッド —メイン、クラス — メイン、例外 — java.lang.Exception
例外を処理しなかったので、スタック トレースは引き続き取得されます。例外と例外処理については、 「Java の例外」および「例外:キャッチと処理」AOPとは何ですか?  アスペクト指向プログラミングの原則 - 3の記事を参照してください。今日はここまでです。今日、私たちはAOPについて知りました。そして、この獣が一部の人々が考えているほど怖くないことがわかりました。さようなら、皆さん!
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION