SpringBoot☞パーミッション検証を達成するためのアスペクトAOP:デモンストレーションの例と注釈の完全な説明

目次

1AOPを理解する

1.1AOPとは

1.2AOPシステムとコンセプト

2AOPの例

2.1最初の例

2.22番目の例

3つのAOP関連ソリューション

3.1 @ポイントカット

3.2 @Around

3.3 @Before

3.4@After

3.5 @AfterReturning

3.6 @AfterThrowing


1AOPを理解する

1.1AOPとは

アスペクト指向思考であるAOP(アスペクト指向プログラミング)は、Springの3つのコアアイデアの1つです(2つのうち2つは、IOC制御の反転、DI依存性注入)。

では、なぜAOPがそれほど重要なのですか?私たちのプログラムでは、許可の検証、ロギング、統計など、いくつかの体系的な要件がしばしばあります。これらのコードは、非常に冗長でメンテナンスに役立たないさまざまなビジネスロジックに散在して散在します。たとえば、次の図:
画像
事業運営の数、チェックサムロギングコードの繰り返しの数を書き込む必要がありますが、これは明らかに受け入れられません。もちろん、オブジェクト指向の考え方を使用して、これらの反復コードを抽出し、次のようにパブリックメソッドとして記述することができます。

画像
このようにして、コードの冗長性と保守性の問題は解決されましたが、各ビジネスメソッドでは、これらのパブリックメソッドを順番に手動で呼び出す必要があり、これも少し面倒です。もっと良い方法はありますか?はい、それはAOPです。AOPは、権限検証やログレコードなどの非ビジネスコードを完全に抽出し、それらをビジネスコードから分離し、ビジネスコードに分割するノードを見つけます。

画像

 

1.2AOPシステムとコンセプト

簡単に理解すると、AOPは実際には次の3種類のことを行う必要があります。

  • どこに割り込むか、つまり、どのビジネスコードで、権限検証などの非ビジネス操作が実行されるか。

  • ビジネスコードの実行前または実行後に、いつ切り込むか。

  • 権限の確認、ログ記録など、スイッチイン後に行うこと。

したがって、AOPシステムは次の図のように分類できます。
画像
いくつかの概念が詳細に説明されています。

  • Pointcut:カットポイント、権限検証、ログレコードなどの処理がビジネスコードに切り込まれる場所を決定します(つまり、アスペクトに織り込みます)カットポイントはexecution方法とannotation方法に分けられます。前者はパス式を使用してアスペクトに組み込まれるクラスを指定でき、後者はアスペクトに組み込まれるコードを変更するアノテーションを指定できます。

  • Advice:処理タイミングと処理内容を含む処理。コンテンツの処理は、権限の確認やログの記録など、何をするかです。処理タイミングは、処理内容が実行されるタイミングであり、前処理(ビジネスコード実行前)、後処理(ビジネスコード実行後)などに分けられます。

  • Aspect:セクション、すなわちPointcutAdvice

  • 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リクエストが呼び出される前に、「リクエストをトリガーするようにアドバイス」という文がコンソールに出力されます。

具体的な実装は次のとおりです。

  1. 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触发了");
    }
}
  1. インターフェイスクラスを作成し、内部で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番目の例

問題を少し複雑にしましょう。この例のシナリオは次のとおりです。

  1. 注釈をカスタマイズするPermissionsAnnotation

  2. アスペクトクラスを作成し、マークされたすべてのPermissionsAnnotationメソッドをインターセプトするようにカットポイントを設定し、インターフェイスのパラメーターをインターセプトし、簡単なアクセス許可の検証を実行します

  3. PermissionsAnnotationマークテストインタフェーステストインタフェースクラスtest

特定の実装手順:

  1. @Target、@Retention、@Documentedカスタムアノテーションを使用します。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PermissionAnnotation{
}
  1. 最初の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();
    }
}
  1. インターフェイスクラスを作成し、ターゲットメソッドにカスタムアノテーションをマークします 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拡張処理は非常に強力であり、次のように表されます。

  1. @Around拡張アクションとターゲットメソッドの実行順序は自由に選択できます。つまり、ターゲットメソッドは拡張アクションの前後、またはプロセス中にも実行できます。この機能の実現は、ProceedingJoinPointパラメーターを呼び出すprocedd()メソッドターゲットメソッドを実行することです。

  2. @Around実行対象メソッドのパラメータ値を変更したり、対象メソッドの実行後の戻り値を変更したりすることもできます。

Around拡張処理には、次の特徴があります。

  1. Around拡張処理メソッドを定義する場合、メソッドの最初のパラメーターはProceedingJoinPoint 型(少なくとも1つのパラメーター)である必要があり ます。インビボ強調処理では、呼び出し方法は、ターゲットメソッドを実行する:これは完全にキーを実行する方法を対象実行タイミングを制御することができる強化処理方法、プログラムが呼び出さない場合の方法を、ターゲットメソッドがありません。ProceedingJoinPointproceed@AroundProceedingJoinPointproceed

  2. 呼び出すときの方法を、あなたも渡すことができ、オブジェクト、および実際のパラメータ-これはの鍵であるとして、配列の値は、ターゲットメソッドに渡されるターゲット・メソッドのパラメータ値を変更することができます強化処理方法。これは、渡された配列長さターゲットメソッドに必要なパラメーターの数と等しくない場合、または配列要素がターゲットメソッドに必要なパラメーターのタイプと一致しない場合、プログラムは異常になります。ProceedingJoinPointproceedObject[ ]AroundObject[ ]Object[ ]

@Around強力ですが、通常はスレッドセーフな環境で使用する必要があります。そのため、あなたは一般的に使用している場合BeforeAfterReturning問題を解決することができます、使用する必要はありません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);
    }
}

おすすめ

転載: blog.csdn.net/baidu_39322753/article/details/111274598