您好,登錄后才能下訂單哦!
這篇文章主要講解了“Springboot項目如何快速實現Aop功能”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Springboot項目如何快速實現Aop功能”吧!
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,表示當前類是一個切面類,簡單理解就是切入點和通知的抽象封裝,表述的是切入點和通知方法之間的對應關系;
2、@Before,前置通知,用于方法上,被@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)")
每一個@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; } }
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()); } }
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功能這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。