ホームページ  >  記事  >  Java  >  SpringbootプロジェクトにAop関数を素早く実装する方法

SpringbootプロジェクトにAop関数を素早く実装する方法

WBOY
WBOY転載
2023-05-16 23:04:151445ブラウズ

依存関係の導入

Springboot が AOP 依存関係パッケージを導入した後は、一般的に、他の構成を行う必要はありません。以前のバージョンまたは他の構成では、AOP の関連機能に影響を及ぼし、その結果、 aop 関数。有効にするには、スタートアップ クラスに @EnableAspectJAutoProxy を追加して有効にしてください。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

コード実装

1。カスタム アノテーション @TestAop

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAop {
}

2 .ExampleAop .java

@Component
@Aspect
@Slf4j
public class ExampleAop {
 
    //切入点:增强标有@TestAop注解的方法
    @Pointcut(value = "@annotation(TestAop)")
    //切入点签名
    public void pointcut() {
        System.out.println("pointCut签名。。。");
    }
 
    //前置通知
    @Before("pointcut()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        log.info("前置通知被执行");
        //可以joinpoint中得到命中方法的相关信息,利用这些信息可以做一些额外的业务操作;
    }
 
    //返回通知
    @AfterReturning(returning = "ret", pointcut = "pointcut()")
    public void doAfterReturning(Object ret) throws Throwable {
        log.info("返回通知被执行");
        //可以joinpoint中得到命中方法的相关信息,利用这些信息可以做一些额外的业务操作;
    }
 
    //异常通知
    @AfterThrowing(throwing = "ex", pointcut = "pointcut()")
    public void throwss(JoinPoint jp, Exception ex) {
        log.info("异常通知被执行");
        //可以joinpoint中得到命中方法的相关信息,利用这些信息可以做一些额外的业务操作;
        //可以从ex中获取到具体的异常信息
    }
 
    //后置通知
    @After("pointcut()")
    public void after(JoinPoint jp) {
        log.info("后置通知被执行");
        //可以joinpoint中得到命中方法的相关信息,利用这些信息可以做一些额外的业务操作;
    }
 
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("------环绕通知 start");
        String methodName = proceedingJoinPoint.getSignature().getName();
        String className = proceedingJoinPoint.getTarget().getClass().getName();
        Object[] args = proceedingJoinPoint.getArgs();
        String argsName = null;
        StringBuilder sb = new StringBuilder();
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                if (args[i] != null) {
                    sb.append(";").append(args[i].getClass().getName());
                }
            }
            if (sb.toString().length() > 0) {
                argsName = sb.toString().substring(1);
            }
        }
        log.info("命中类:{},方法{},参数{};", className, methodName, argsName);
        Object proceed = proceedingJoinPoint.proceed();
        log.info("------环绕通知 end");
        return proceed;
    }
 
}

コア アノテーションとクラス

1. アスペクトは、現在のクラスがアスペクト クラスであることを示します。簡単に理解すると、エントリ ポイントと通知の抽象的なカプセル化が表現されます。エントリポイントと通知メソッドの関係 対応;

@Before アノテーションでマークされたメソッドは、メソッドの実行が開始される前に実行されます (事前通知

3 と呼ばれます)。 @After (通知後) はメソッドに使用されます。 @After アノテーションでマークされたメソッドは、cut メソッドが実行された後に実行されます。

4. @AfterReturning (通知を返す) はメソッドに使用されます。 @AfterReturning アノテーションでマークされたメソッドは、cut メソッドが結果を返した後に実行されます。 ;

6. @AfterThrowing: 例外通知、メソッドで使用されます。 @AfterThrowing アノテーションでマークされたメソッドは、カット メソッドが結果を返した後に実行されます。例外はカットインメソッドによってスローされます。通常、目的の例外情報を取得するために使用されます。

7、@Aroud: サラウンド通知、メソッドに使用され、@Around アノテーションでマークされたメソッドが実行されますカットイン メソッドの実行前と実行後;

8, @Pointcut, カットイン ポイント、メソッド内でマークされ、エントリ ポイントの定義に使用されます。いわゆるエントリ ポイントは、どの接続ポイントを拡張するかの定義です。Spring では、特にどのメソッドを拡張するかの定義を指します。@Pointcut で注釈が付けられた式がエントリ ポイントを表します。多数ありますが、最も一般的に使用される 2 つは実行式ですおよびアノテーション;

9. ジョイントポイント、いわゆる接続ポイントは、AOP アスペクトによってカットされた位置ポイントを指し、特に Spring ではカットインされるメソッドを指します;

10、 PointCut、

11、アドバイス、通知。いわゆる通知は、定義されたエントリ ポイントをインターセプトした後に実行される特定の操作の定義を指します。Spring では、@Before、@After でマークされたメソッドを指します。 、@AfterReturning、@AfterThrowing、および @Around アノテーション;

エントリ ポイントをマークする一般的な方法

1、実行式

式メソッド: 修飾子の戻り値のパッケージ名にアクセスします。パッケージ名...クラス名.メソッド (パラメータリスト)

例 1: すべての com.fanfu パッケージとサブパッケージの一致を示します。すべてのクラスの add で始まるメソッド、戻り値とパラメータは制限されません。

@Pointcut("execution(* com.fanfu..*.*.add*(..))")

例 2: すべての com.fanfu パッケージおよびサブパッケージ内の add で始まるすべてのクラスとの一致を示し、パラメーターの型は String メソッドで、戻り値は制限されません。

@Pointcut("execution(* com.fanfu..*.*.add*(String))")

例 3: com.fanfu パッケージ内の任意のクラス、任意のメソッド、および任意のパラメータとの一致を示します。

@Pointcut("execution(* com.fanfu..*.*.*(String))")

execution() は式本体です。
最初の * は、戻り値の型は任意です。つまり、戻り値の型は制限されません。
パッケージの後の * は現在のパッケージを示し、パッケージの後の 2 つの連続する .. は現在のパッケージとサブパッケージを示します。
(..) は任意のパラメータを意味します;
最後の *.*(..) は任意のクラス、任意のメソッド、任意のパラメータと一致することを意味します;

2、アノテーション

アノテーション構文: @annotation (カスタム アノテーション)

例: すべてのタグに一致するメソッドを示します @TestAop アノテーション;

@Pointcut("@annotation(com.fanfu.config.TestAop)")

Spring Aop のヒント

各 @Pointcut で使用できるエントリ ポイントを定義するための実行または注釈。&&、||、! 演算などの論理演算子を複数のカット ポイント間で使用することもできます。

1、point1()&&point2() はヒットを示します。 point1 と point2 のエントリ ポイント。例: com.fanfu パッケージのすべてのクラスとすべての下位サブパッケージでは、メソッド名は add で始まり、パラメータの型は String であり、com.fanfu.service パッケージのすべてのメソッドすべての下位サブパッケージのすべてのクラスのすべてのメソッドの共通部分。メソッド名とパラメータのタイプに関係なく、つまり、com.fanfu.service パッケージのすべてのクラスとすべての下位サブパッケージで、メソッドは次のいずれかになります。 add1 または add2 メソッド (呼び出しの前後) 周囲の通知 around() メソッド内のロジックが実行されます;

@Component
@Aspect
@Slf4j
public class ExampleAop {
    @Pointcut("execution(* com.fanfu..*.*.add*(String))")
    public void point1() {
    }
    @Pointcut("execution(* com.fanfu.service..*.*(..))")
    public void point2() {
    }
    @Pointcut("point1()&&point2()")
    public void point() {
    }
    @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("------around start");
        Object proceed = proceedingJoinPoint.proceed();
        log.info("------around end");
        return proceed;
    }
}

2, point1()&&point2() は、point1 と point2 にヒットするすべてのポイントカットの結合を表します; 例: com.fanfu.service パッケージとすべてのサブパッケージのすべてのクラスのうち、メソッド名は add1 で、パラメーターの型は String です。com.fanfu.controller パッケージとすべてのサブパッケージのすべてのクラスでは、パッケージの場合、メソッド名は add2 で、パラメータのタイプは String です。メソッドは共用体です。つまり、com.fanfu.service または com.fanfu.controller パッケージのすべてのクラスとすべての下位サブパッケージでは、メソッドまたはadd1 または add2 メソッドは、around() メソッドの呼び出しの前後に周囲の通知を実行します。ロジック;

@Component
@Aspect
@Slf4j
public class ExampleAop {
    @Pointcut("execution(* com.fanfu.service..*.add*(String))")
    public void point1() {
    }
    @Pointcut("execution(* com.fanfu.controller..*.*.add*(String))")
    public void point2() {
    }
    @Pointcut("point1()||point2()")
    public void point() {
    }
    @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("------around start");
        Object proceed = proceedingJoinPoint.proceed();
        log.info("------around end");
        return proceed;
    }
}

3。!point() は、point() にヒットするすべてのエントリ ポイントの反転を示します。たとえば、 : com.fanfu.service パッケージのすべてのクラスとすべての下位サブパッケージでは、先頭のメソッドは呼び出しの前後に around() メソッドのロジックを実行します。 ##Spring Aop Notes

1. メソッドが定義されたポイントカットに一致する場合、それが現在の呼び出しチェーン内にある場合、メソッドが現在のクラスで初めて一致した場合、ヒットします。 , 関連する通知が実行されます。現在の呼び出しチェーンが終了しておらず、現在のクラスの他のエントリ ポイントと一致するメソッドが現在のメソッドで呼び出された場合、再度ヒットすることはありません。つまり、一致する他のメソッドは、エントリ ポイントは、実行時に関連する通知をトリガーしなくなりました;

次の例:

当请求http://localhost:8080/example时,ExampleController中的example方法被触发,ExampleController#example()又调用了ExampleService#test(),在ExampleService#test()内部,又顺序调用了ExampleService#test1()和ExampleService#test2();在ExampleAop中,按照execution中的配置,是可以匹配到test()、test1()、test2(),实际是命中的方法只有test();

@Component
@Aspect
@Slf4j
public class ExampleAop {
    @Pointcut("execution(* com.fanfu.service.impl.ExampleServiceImpl.test*(..))")
    public void point2() {
        log.info("切入点匹配");
    }
    @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("------around start");
        String methodName = proceedingJoinPoint.getSignature().getName();
        String className = proceedingJoinPoint.getTarget().getClass().getName();
        Object[] args = proceedingJoinPoint.getArgs();
        String argsName=null;
        StringBuilder sb=new StringBuilder();
        if (args!=null&&args.length>0) {
            for (int i = 0; i < args.length; i++) {
                if (args[i] != null) {
                    sb.append(";").append(args[i].getClass().getName());
                }
            }
            if (sb.toString().length()>0) {
                argsName=sb.toString().substring(1);
            }
        }
        log.info("命中类:{},方法{},参数{};",className,methodName,argsName);
        Object proceed = proceedingJoinPoint.proceed();
        log.info("------around end");
        return proceed;
    }
}
@Service
@Slf4j
public class ExampleServiceImpl implements IExampleService {
 
    @Override
    public String test(String msg) {
        log.info("test方法被执行");
        this.test1(msg);
        this.test2(msg);
        return msg;
    }
 
    public String test1(String msg) {
        log.info("test1方法被执行");
        return "msg1";
    }
 
    public String test2(String msg) {
        log.info("test2方法被执行");
        return "msg2";
    }
}
public interface IExampleService {
    public String test(String msg);
    public String test1(String msg);
    public String test2(String msg);
}
@RestController
@Slf4j
public class ExampleController {
    @Autowired
    private IExampleService exampleService;
    @GetMapping("/example")
    public String example() {
        log.info("example start");
        exampleService.test(null);
        log.info("example end");
        return String.valueOf(System.currentTimeMillis());
    }
}

SpringbootプロジェクトにAop関数を素早く実装する方法

2、对于上面的问题,如果把execution表达换成注解,会不会结果不一样?再把ExampleAop中的@Pointcut改成注解形式,再在ExampleService#test1()、ExampleService#test2()、ExampleService#test()添加注解@TestAop,验证结果依然是一样的,只有test()会命中,其他不会!所以要注意呀。

@Component
@Aspect
@Slf4j
public class ExampleAop {
    @Pointcut("@annotation(TestAop)")
    public void point() {
    }
    @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("------around start");
        String methodName = proceedingJoinPoint.getSignature().getName();
        String className = proceedingJoinPoint.getTarget().getClass().getName();
        Object[] args = proceedingJoinPoint.getArgs();
        String argsName = null;
        StringBuilder sb = new StringBuilder();
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                if (args[i] != null) {
                    sb.append(";").append(args[i].getClass().getName());
                }
            }
            if (sb.toString().length() > 0) {
                argsName = sb.toString().substring(1);
            }
        }
        log.info("命中类:{},方法{},参数{};", className, methodName, argsName);
        Object proceed = proceedingJoinPoint.proceed();
        log.info("------around end");
        return proceed;
    }
}
@Service
@Slf4j
public class ExampleServiceImpl implements IExampleService {
 
    @Override
    @TestAop
    public String test(String msg) {
        log.info("test方法被执行");
        this.test1(msg);
        this.test2(msg);
        return msg;
    }
    @Override
    @TestAop
    public String test1(String msg) {
        log.info("test1方法被执行");
        return "msg1";
    }
    @Override
    @TestAop
    public String test2(String msg) {
        log.info("test2方法被执行");
        return "msg2";
    }
   
}

3、那什么情况下,ExampleService#test1()、ExampleService#test2()、ExampleService#test()会同时命中呢?让从ExampleController#example()到ExampleService#test1()、ExampleService#test2()、ExampleService#test()分别在不同的调用链上,那么就可以同时命中了;

@RestController
@Slf4j
public class ExampleController {
    @Autowired
    private IExampleService exampleService;
    @GetMapping("/example")
    public String example() {
        log.info("example start");
        exampleService.test(null);
        exampleService.test1(null);
        exampleService.test2(null);
        log.info("example end");
        return String.valueOf(System.currentTimeMillis());
    }
}

SpringbootプロジェクトにAop関数を素早く実装する方法

以上がSpringbootプロジェクトにAop関数を素早く実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。