首頁 > Java > java教程 > 主體

Springboot專案如何快速實現Aop功能

WBOY
發布: 2023-05-16 23:04:15
轉載
1453 人瀏覽過

依賴引入

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、Aspect,表示目前類別是一個切面類,簡單理解就是切入點和通知的抽象封裝,表述的是切入點和通知方法之間的對應關係;

@Before註解標記的方法將在方法被切入執行前執行,稱為前置通知

3、@After,後置通知,用於方法上,被@After註解標記的方法會在被切入方法執行之後執行;

4、@AfterReturning,返回通知,用於方法上,被@AfterReturning註解標記的方法會在被切入方法返回結果之後執行;

6、@AfterThrowing:異常通知,用於方法上,被@AfterThrowing註解標記的方法會在被切入方法拋出異常之後執行,一般用於有目的的獲取異常信息;

7、@Aroud:環繞通知,用於方法上,被@Around註解標記的方法會在被切入方法執行前後執行;

8、@Pointcut,切入點,標記在方法上,用來定義切入點,所謂的切入點是指對哪些連接點處進行切入增強的定義,在Spring中具體就是指對哪些方法進行切入增強的定義;被@Pointcut註解表示切入點的表達式有多種,最常用的是兩種,execution表達式和註解;

9、Jointpoint,連接點,所謂的連接點是指被aop切面切入的位置點,在Spring中具體就是指被切入的方法;

10、PointCut,

11、Advice,通知,所謂的通知是指對定義好的切入點進行攔截後,要具體做哪些操作的定義;在Spring中就是指被@Before、@After、@AfterReturning、@AfterThrowing、@Around註解標記的方法;

標記切入點的常用方式

1、execution表達式

表達式請法:訪問修飾符返回值包名.包名...類名.方法(參數列表)

#範例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、註解

#註解語法:@annotation(自訂的註解)

範例:表示符合所有標記@TestAop註解的方法;

@Pointcut("@annotation(com.fanfu.config.TestAop)")
登入後複製

Spring Aop的小技巧

#每一個@Pointcut可以使用execution或註解來定義切入點,多個切點之間也可以使用邏輯運算符,如&&、||、!運算;

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套件及下屬所有子套件的所有類別中,不是以add開頭的方法,在呼叫前後都會執行環繞通知around()方法內的邏

@Component
@Aspect
@Slf4j
public class ExampleAop {
    @Pointcut("execution(* com.fanfu.service..*.add*(String))")
    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;
    }
}
登入後複製

Spring Aop注意事項

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中文網其他相關文章!

相關標籤:
來源:yisu.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板