目次
1AOPを理解する
1.1AOPとは
アスペクト指向思考であるAOP(アスペクト指向プログラミング)は、Springの3つのコアアイデアの1つです(2つのうち2つは、IOC制御の反転、DI依存性注入)。
では、なぜAOPがそれほど重要なのですか?私たちのプログラムでは、許可の検証、ロギング、統計など、いくつかの体系的な要件がしばしばあります。これらのコードは、非常に冗長でメンテナンスに役立たないさまざまなビジネスロジックに散在して散在します。たとえば、次の図:
事業運営の数、チェックサムロギングコードの繰り返しの数を書き込む必要がありますが、これは明らかに受け入れられません。もちろん、オブジェクト指向の考え方を使用して、これらの反復コードを抽出し、次のようにパブリックメソッドとして記述することができます。
このようにして、コードの冗長性と保守性の問題は解決されましたが、各ビジネスメソッドでは、これらのパブリックメソッドを順番に手動で呼び出す必要があり、これも少し面倒です。もっと良い方法はありますか?はい、それはAOPです。AOPは、権限検証やログレコードなどの非ビジネスコードを完全に抽出し、それらをビジネスコードから分離し、ビジネスコードに分割するノードを見つけます。
1.2AOPシステムとコンセプト
簡単に理解すると、AOPは実際には次の3種類のことを行う必要があります。
-
どこに割り込むか、つまり、どのビジネスコードで、権限検証などの非ビジネス操作が実行されるか。
-
ビジネスコードの実行前または実行後に、いつ切り込むか。
-
権限の確認、ログ記録など、スイッチイン後に行うこと。
したがって、AOPシステムは次の図のように分類できます。
いくつかの概念が詳細に説明されています。
-
Pointcut
:カットポイント、権限検証、ログレコードなどの処理がビジネスコードに切り込まれる場所を決定します(つまり、アスペクトに織り込みます)カットポイントはexecution
方法とannotation
方法に分けられます。前者はパス式を使用してアスペクトに組み込まれるクラスを指定でき、後者はアスペクトに組み込まれるコードを変更するアノテーションを指定できます。 -
Advice
:処理タイミングと処理内容を含む処理。コンテンツの処理は、権限の確認やログの記録など、何をするかです。処理タイミングは、処理内容が実行されるタイミングであり、前処理(ビジネスコード実行前)、後処理(ビジネスコード実行後)などに分けられます。 -
Aspect
:セクション、すなわちPointcut
とAdvice
。 -
Joint point
:接続点はプログラム実行点です。たとえば、メソッドの実行や例外の処理。Spring AOPでは、接続ポイントは常にメソッドの実行を表します。 -
Weaving
:ウィービングは、動的エージェントを介してターゲットオブジェクトメソッドでコンテンツ処理を実行するプロセスです。
インターネット上に写真があります。とても鮮やかだと思います。ここに投稿して、みんなに見てもらいましょう。
2AOPの例
実践から実際の知識が得られたら、コードを使用してAOPを実装します。完成したプロジェクトは次の場所にアップロードされています。
https://github.com/ThinkMugz/aopDemo
AOPを使用するには、最初にAOPの依存関係を導入する必要があります。パラメータ検証:このようにパラメータ検証(バリデータ)を書くと、あなたは思いとどまることはありません〜
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.1最初の例
次に、最小限の例を見てみましょう。すべてのget
リクエストが呼び出される前に、「リクエストをトリガーするようにアドバイス」という文がコンソールに出力されます。
具体的な実装は次のとおりです。
-
AOPアスペクトクラスを作成する
@Aspect
には、クラスに注釈を追加するだけ です。@Aspect
アノテーションは、アスペクトクラスを説明するために使用されます。このアノテーションは、アスペクトクラスを定義するときに必要です。@Component
注釈は管理のためにSpringに渡されます。このクラスでアドバイスを実装します。
package com.root.demo.advice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAdvice {
// 定义一个切点:所有被GetMapping注解修饰的方法会织入advice
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
private void logAdvicePointcut() {}
// Before表示logAdvice将在目标方法执行前执行
@Before("logAdvicePointcut()")
public void logAdvice(){
// 这里只是一个示例,你可以写任何处理逻辑
System.out.println("get请求的advice触发了");
}
}
-
インターフェイスクラスを作成し、内部でgetリクエストを作成します。
package com.root.demo.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(value = "/aop")
public class AopController {
@GetMapping(value = "/getTest")
public JSONObject aopTest() {
return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
}
@PostMapping(value = "/postTest")
public JSONObject aopTest2(@RequestParam("id") String id) {
return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
}
}
プロジェクトの開始後、http://localhost:8085/aop/getTest
インターフェースを要求します。
ここに画像の説明を挿入
http://localhost:8085/aop/postTest
インターフェイスが要求され、コンソールからの出力がありません。これは、カットポイントが実際にGetMapping
変更されたメソッド専用であることを証明しています。
2.22番目の例
問題を少し複雑にしましょう。この例のシナリオは次のとおりです。
-
注釈をカスタマイズする
PermissionsAnnotation
-
アスペクトクラスを作成し、マークされたすべての
PermissionsAnnotation
メソッドをインターセプトするようにカットポイントを設定し、インターフェイスのパラメーターをインターセプトし、簡単なアクセス許可の検証を実行します -
PermissionsAnnotation
マークテストインタフェーステストインタフェースクラスtest
に
特定の実装手順:
-
@Target、@Retention、@Documented
カスタムアノテーションを使用します。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionAnnotation{
}
-
最初のAOPアスペクトクラスを作成するには
@Aspect
、クラスに注釈を追加するだけ です。@Aspect
アノテーションは、アスペクトクラスを説明するために使用されます。このアノテーションは、アスペクトクラスを定義するときに必要です。@Component
注釈は管理のためにSpringに渡されます。このクラスに最初のステップの権限検証ロジックを実装します。
package com.root.example.demo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Order(1)
public class PermissionFirstAdvice {
// 定义一个切面,括号内写入第1步中自定义注解的路径
@Pointcut("@annotation(com.mu.demo.annotation.PermissionAnnotation)")
private void permissionCheck() {
}
@Around("permissionCheck()")
public Object permissionCheckFirst(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("===================第一个切面===================:" + System.currentTimeMillis());
//获取请求参数,详见接口类
Object[] objects = joinPoint.getArgs();
Long id = ((JSONObject) objects[0]).getLong("id");
String name = ((JSONObject) objects[0]).getString("name");
System.out.println("id1->>>>>>>>>>>>>>>>>>>>>>" + id);
System.out.println("name1->>>>>>>>>>>>>>>>>>>>>>" + name);
// id小于0则抛出非法id的异常
if (id < 0) {
return JSON.parseObject("{\"message\":\"illegal id\",\"code\":403}");
}
return joinPoint.proceed();
}
}
-
インターフェイスクラスを作成し、ターゲットメソッドにカスタムアノテーションをマークします
PermissionsAnnotation
。
package com.root.example.demo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(value = "/permission")
public class TestController {
@RequestMapping(value = "/check", method = RequestMethod.POST)
// 添加这个注解
@PermissionsAnnotation()
public JSONObject getGroupList(@RequestBody JSONObject request) {
return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200}");
}
}
ここでは、まずテストを行います。まず、リクエストアドレスとヘッダーを入力します。
次に、通常のパラメーターを作成します。
通常の応答結果を得ることができます:
次に、異常なパラメータを作成し、再度要求します。
応答結果は、アスペクトクラスが判断され、対応する結果が返されることを示しています。
1つのインターフェイスで検証するために複数のアスペクトクラスを設定したい場合はどうすればよいですか?これらの側面の実行順序を管理する方法は?
非常にシンプルです。カスタムAOP
アノテーションは複数のアスペクトクラスに対応できます。これらのアスペクトクラスの実行順序は@Order
アノテーションによって管理されます。アノテーションの後の数字が小さいほど、アスペクトクラスが最初に実行されます。
以下を例で示します。
2番目のAOPアスペクトクラスを作成し、このクラスで権限検証の2番目のステップを実装します。
package com.root.example.demo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Order(0)
public class PermissionSecondAdvice {
@Pointcut("@annotation(com.example.demo.PermissionsAnnotation)")
private void permissionCheck() {
}
@Around("permissionCheck()")
public Object permissionCheckSecond(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("===================第二个切面===================:" + System.currentTimeMillis());
//获取请求参数,详见接口类
Object[] objects = joinPoint.getArgs();
Long id = ((JSONObject) objects[0]).getLong("id");
String name = ((JSONObject) objects[0]).getString("name");
System.out.println("id->>>>>>>>>>>>>>>>>>>>>>" + id);
System.out.println("name->>>>>>>>>>>>>>>>>>>>>>" + name);
// name不是管理员则抛出异常
if (!name.equals("admin")) {
return JSON.parseObject("{\"message\":\"not admin\",\"code\":403}");
}
return joinPoint.proceed();
}
}
プロジェクトを再開し、テストを続行して、両方のパラメーターが異常である状況を構築します。
結果に応じて、2番目のアスペクトクラスの実行順序はより高いように見えます。
3つのAOP関連ソリューション
上記の場合、多くのアノテーションが使用されます。以下は、これらのアノテーションの詳細な説明です。
3.1 @ポイントカット
@Pointcut
注釈は、アスペクト、つまり上記の関連するもののエントリポイントを定義するために使用され、エントリポイントはイベントトリガーのタイミングを定義します。
@Aspect
@Component
public class LogAspectHandler {
/**
* 定义一个切面,拦截 com.itcodai.course09.controller 包和子包下的所有方法
*/
@Pointcut("execution(* com.mutest.controller..*.*(..))")
public void pointCut() {}
}
@Pointcutアノテーションは、アスペクトを指定し、インターセプトする必要があるものを定義します。一般的に使用される2つの式は、1つは使用するものexecution()
で、もう1つはを使用 するもの annotation()
です。
詳細参照:SpringBootコンテンツ集約
実行式:
execution(* * com.mutest.controller..*.*(..)))
例として式を取り上げ ます。
-
最初の*:の位置は戻り値のタイプを示し、*はすべてのタイプを示します。
-
パッケージ名:インターセプトする必要のあるパッケージ名を示します。後ろの2つのピリオドは、現在のパッケージと現在のパッケージのすべてのサブパッケージを示します。この例では、com.mutest.controllerパッケージと以下のすべてのクラスのメソッドを参照します。サブパッケージ。
-
2番目の*記号の位置:クラス名を示し、*はすべてのクラスを示します。
-
*(..):このアスタリスクはメソッドの名前を示し、*はすべてのメソッドを示し、括弧はメソッドのパラメーターを示し、2つのピリオドは任意のパラメーターを示します。
アノテーション()式:
annotation()
方法は、特定のアノテーションのアスペクトを定義することです。たとえば、@ PostMappingアノテーションを使用してメソッドのアスペクトを作成する場合、次のようにアスペクトを定義できます。
@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
public void annotationPointcut() {}
次に、このアスペクトを使用@PostMapping
すると、アノテーションのすべてのメソッドがにカットされ ます。この方法は、@GetMapping、@PostMapping、@DeleteMapping
さまざまな注釈にさまざまな特定の処理ロジックがある処理シナリオに非常に適しています 。
上記の場合に示すように、カスタム注釈のアスペクトの定義もあります。
@Pointcut("@annotation(com.example.demo.PermissionsAnnotation)")
private void permissionCheck() {}
3.2 @Around
@Around
注釈はAround
、処理を変更および拡張するために使用されます。Around
拡張処理は非常に強力であり、次のように表されます。
-
@Around
拡張アクションとターゲットメソッドの実行順序は自由に選択できます。つまり、ターゲットメソッドは拡張アクションの前後、またはプロセス中にも実行できます。この機能の実現は、ProceedingJoinPoint
パラメーターを呼び出すprocedd()
メソッドがターゲットメソッドを実行することです。 -
@Around
実行対象メソッドのパラメータ値を変更したり、対象メソッドの実行後の戻り値を変更したりすることもできます。
Around
拡張処理には、次の特徴があります。
-
Around
拡張処理メソッドを定義する場合、メソッドの最初のパラメーターはProceedingJoinPoint
型(少なくとも1つのパラメーター)である必要があり ます。インビボ強調処理では、呼び出し方法は、ターゲットメソッドを実行する:これは完全にキーを実行する方法を対象実行タイミングを制御することができる強化処理方法、プログラムが呼び出さない場合の方法を、ターゲットメソッドがありません。ProceedingJoinPoint
proceed
@Around
ProceedingJoinPoint
proceed
-
呼び出すときの方法を、あなたも渡すことができ、オブジェクト、および実際のパラメータ-これはの鍵であるとして、配列の値は、ターゲットメソッドに渡されるターゲット・メソッドのパラメータ値を変更することができます強化処理方法。これは、渡された配列の長さがターゲットメソッドに必要なパラメーターの数と等しくない場合、または配列要素がターゲットメソッドに必要なパラメーターのタイプと一致しない場合、プログラムは異常になります。
ProceedingJoinPoint
proceed
Object[ ]
Around
Object[ ]
Object[ ]
@Around
強力ですが、通常はスレッドセーフな環境で使用する必要があります。そのため、あなたは一般的に使用している場合Before
、AfterReturning
問題を解決することができます、使用する必要はありませんAround
アップ。ターゲットメソッドの実行の前後にいくつかの状態データを共有する必要がある場合は、それを使用することを検討する必要がありますAround
。特に、ターゲットの実行を防ぐために拡張処理を使用する必要がある場合、またはターゲットメソッドの戻り値を変更する必要がある場合は、Around
拡張処理のみを使用できます。
次に、前の例にいくつかの変更を加えて@Around
、特性を観察します。
カスタムアノテーションクラスは変更されません。まず、インターフェイスクラスを定義します。
package com.example.demo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(value = "/permission")
public class TestController {
@RequestMapping(value = "/check", method = RequestMethod.POST)
@PermissionsAnnotation()
public JSONObject getGroupList(@RequestBody JSONObject request) {
return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200,\"data\":" + request + "}");
}
}
唯一のファセットタイプ(前のケースには2つのファセットタイプがあり、ここに1つだけ保持します):
package com.root.example.demo;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Order(1)
public class PermissionAdvice {
@Pointcut("@annotation(com.example.demo.PermissionsAnnotation)")
private void permissionCheck() {
}
@Around("permissionCheck()")
public Object permissionCheck(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("===================开始增强处理===================");
//获取请求参数,详见接口类
Object[] objects = joinPoint.getArgs();
Long id = ((JSONObject) objects[0]).getLong("id");
String name = ((JSONObject) objects[0]).getString("name");
System.out.println("id1->>>>>>>>>>>>>>>>>>>>>>" + id);
System.out.println("name1->>>>>>>>>>>>>>>>>>>>>>" + name);
// 修改入参
JSONObject object = new JSONObject();
object.put("id", 8);
object.put("name", "lisi");
objects[0] = object;
// 将修改后的参数传入
return joinPoint.proceed(objects);
}
}
また、JMeterを使用してインターフェースを呼び出し、パラメーター:{"id":-5,"name":"admin"}
を渡します。応答結果は@Around
、インターフェースの入力パラメーターがインターセプトされ、インターフェースがアスペクトクラスで結果を返すことを示しています。
3.3 @Before
@Before
アノテーションで指定されたメソッドは、アスペクトがターゲットメソッドに切り替わる前に実行され Log
、ユーザーのリクエストURL
やユーザーの IP
アドレスの取得など、情報の処理や統計を行う ことができます。これは、個人サイトを作成するときに使用できます。これは一般的に使用される方法です。たとえば、次のコード:
@Aspect
@Component
@Slf4j
public class LogAspectHandler {
/**
* 在上面定义的切面方法之前执行该方法
* @param joinPoint jointPoint
*/
@Before("pointCut()")
public void doBefore(JoinPoint joinPoint) {
log.info("====doBefore方法进入了====");
// 获取签名
Signature signature = joinPoint.getSignature();
// 获取切入的包名
String declaringTypeName = signature.getDeclaringTypeName();
// 获取即将执行的方法名
String funcName = signature.getName();
log.info("即将执行方法为: {},属于{}包", funcName, declaringTypeName);
// 也可以用来记录一些信息,比如获取请求的 URL 和 IP
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取请求 URL
String url = request.getRequestURL().toString();
// 获取请求 IP
String ip = request.getRemoteAddr();
log.info("用户请求的url为:{},ip地址为:{}", url, ip);
}
}
JointPoint
このオブジェクトは非常に便利です。これを使用して署名を取得し、署名を使用して、要求されたパッケージ名、パラメーターを含むメソッド名(joinPoint.getArgs()
取得による)などを取得できます 。
3.4@After
@After
アノテーションはアノテーションに @Before
対応します。指定されたメソッドは、アスペクトがターゲットメソッドに切り込まれた後に実行されるか、特定のメソッドが完了した後に何らかのログ処理が実行されます。
@Aspect
@Component
@Slf4j
public class LogAspectHandler {
/**
* 定义一个切面,拦截 com.mutest.controller 包下的所有方法
*/
@Pointcut("execution(* com.mutest.controller..*.*(..))")
public void pointCut() {}
/**
* 在上面定义的切面方法之后执行该方法
* @param joinPoint jointPoint
*/
@After("pointCut()")
public void doAfter(JoinPoint joinPoint) {
log.info("==== doAfter 方法进入了====");
Signature signature = joinPoint.getSignature();
String method = signature.getName();
log.info("方法{}已经执行完", method);
}
}
この時点で、実行結果をテストするコントローラーを作成し、次のように新しいAopControllerを作成しましょう。
@RestController
@RequestMapping("/aop")
public class AopController {
@GetMapping("/{name}")
public String testAop(@PathVariable String name) {
return "Hello " + name;
}
}
プロジェクトを開始し、ブラウザーにlocalhost:8080 / aop / csdnと入力して、コンソールの出力情報を確認します。
====doBefore 方法进入了====
即将执行方法为: testAop,属于com.itcodai.mutest.AopController包
用户请求的 url 为:http://localhost:8080/aop/name,ip地址为:0:0:0:0:0:0:0:1
==== doAfter 方法进入了====
方法 testAop 已经执行完
Log
プログラム実行の論理シーケンスで印刷物を 見ることができるので、制御は非常に直感的@Before
で @After
実用的な 2つの注釈の効果があります。
3.5 @AfterReturning
@AfterReturning
アノテーション @After
は多少似ていますが、違いは @AfterReturning
、カットインメソッドの実行後に戻り値をキャプチャし、戻り値に対してビジネスロジック拡張処理を実行するためにアノテーションを使用できることです。次に例を示します。
@Aspect
@Component
@Slf4j
public class LogAspectHandler {
/**
* 在上面定义的切面方法返回后执行该方法,可以捕获返回对象或者对返回对象进行增强
* @param joinPoint joinPoint
* @param result result
*/
@AfterReturning(pointcut = "pointCut()", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
Signature signature = joinPoint.getSignature();
String classMethod = signature.getName();
log.info("方法{}执行完毕,返回参数为:{}", classMethod, result);
// 实际项目中可以根据业务做具体的返回值增强
log.info("对返回参数进行业务上的增强:{}", result + "增强版");
}
}
であることに注意すべき @AfterReturning
注釈、returning
の値属性は 、それ以外の場合は検出されません、パラメータと一致していなければなりません。このメソッドの2番目の入力パラメーターは、cutメソッドの戻り値です。戻り値doAfterReturning
は、 メソッドで拡張でき、ビジネスニーズに応じてカプセル化できます。サービスを再起動して、もう一度テストしてみましょう。
方法 testAop 执行完毕,返回参数为:Hello CSDN
对返回参数进行业务上的增强:Hello CSDN 增强版
3.6 @AfterThrowing
cutメソッドの実行中に例外がスロー @AfterThrowing
されると、注釈付きメソッドで実行され、このメソッドでいくつかの例外処理ロジックを実行できます。throwing
属性の値はパラメータと一致している必要があることに注意 してください。一致していない場合、エラーが報告されます。このメソッドの2番目のパラメーターは、スローされる例外です。
@Aspect
@Component
@Slf4j
public class LogAspectHandler {
/**
* 在上面定义的切面方法执行抛异常时,执行该方法
* @param joinPoint jointPoint
* @param ex ex
*/
@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
Signature signature = joinPoint.getSignature();
String method = signature.getName();
// 处理异常的逻辑
log.info("执行方法{}出错,异常为:{}", method, ex);
}
}