什么是AOP?
AOP英文全稱:Aspect Oriented Programming(面向切面編程、面向方面編程),其實說白了,面向切面編程就是面向特定方法編程。AOP面向方法編程,就可以做到在不改動這些原始方法的基礎(chǔ)上,針對特定的方法進行功能的增強。AOP的作用:在程序運行期間在不修改源代碼的基礎(chǔ)上對已有方法進行增強(無侵入性: 解耦)
中間運行的原始業(yè)務(wù)方法,可能是其中的一個業(yè)務(wù)方法,比如:我們只想通過 部門管理的 list 方法的執(zhí)行耗時,那就只有這一個方法是原始業(yè)務(wù)方法。 而如果,我們是先想統(tǒng)計所有部門管理的業(yè)務(wù)方法執(zhí)行耗時,那此時,所有的部門管理的業(yè)務(wù)方法都是 原始業(yè)務(wù)方法。 那面向這樣的指定的一個或多個方法進行編程,我們就稱之為 面向切面編程。
AOP的優(yōu)勢:減少重復(fù)代碼,提高開發(fā)效率,維護方便
AOP快速入門
需求:統(tǒng)計各個業(yè)務(wù)層方法執(zhí)行耗時。
實現(xiàn)步驟:
1.導(dǎo)入依賴:在pom.xml中導(dǎo)入AOP的依賴
2.編寫AOP程序:針對于特定方法根據(jù)業(yè)務(wù)需要進行編程
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
AOP程序:TimeAspect
@Component
@Aspect //當(dāng)前類為切面類
@Slf4j
public class TimeAspect {
? ? @Around("execution(* com.itheima.service.*.*(..))")
? ? public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
? ? ? ? //記錄方法執(zhí)行開始時間
? ? ? ? long begin = System.currentTimeMillis();
? ? ? ? //執(zhí)行原始方法
? ? ? ? Object result = pjp.proceed();
? ? ? ? //記錄方法執(zhí)行結(jié)束時間
? ? ? ? long end = System.currentTimeMillis();
? ? ? ? //計算方法執(zhí)行耗時
? ? ? ? log.info(pjp.getSignature()+"執(zhí)行耗時: {}毫秒",end-begin);
? ? ? ? return result;
? ? }
}
總結(jié):其實AOP常見的應(yīng)用場景如下:
1.記錄系統(tǒng)的操作日志
2.權(quán)限控制
3.事務(wù)管理:我們前面所講解的Spring事務(wù)管理,底層其實也是通過AOP來實現(xiàn)的,只要添加@Transactional注解之后,AOP程序自動會在原始方法運行前先來開啟事務(wù),在原始方法運行完畢之后提交或回滾事務(wù)
AOP進階
通知類型,通知順序,切入點表達式,連接點
@Around:環(huán)繞通知,此注解標注的通知方法在目標方法前、后都被執(zhí)行
@Before:前置通知,此注解標注的通知方法在目標方法前被執(zhí)行
@After :后置通知,此注解標注的通知方法在目標方法后被執(zhí)行,無論是否有異常都會執(zhí)行
@AfterReturning : 返回后通知,此注解標注的通知方法在目標方法后被執(zhí)行,有異常不會執(zhí)行
@AfterThrowing : 異常后通知,此注解標注的通知方法發(fā)生異常后執(zhí)行
@Around環(huán)繞通知需要自己調(diào)用 ProceedingJoinPoint.proceed() 來讓原始方法執(zhí)行,其他通知不需要考慮目標方法執(zhí)行
@Around環(huán)繞通知方法的返回值,必須指定為Object,來接收原始方法的返回值,否則原始方法執(zhí)行完畢,是獲取不到返回值的。
Spring提供了@PointCut注解,該注解的作用是將公共的切入點表達式抽取出來,需要用到時引用該切入點表達式即可。
@Slf4j
@Component
@Aspect
public class MyAspect1 {
? ? //切入點方法(公共的切入點表達式)
? ? @Pointcut("execution(* com.itheima.service.*.*(..))")
? ? private void pt(){
? ? }
? ? //前置通知(引用切入點)
? ? @Before("pt()")
? ? public void before(JoinPoint joinPoint){
? ? ? ? log.info("before ...");
? ? }
? ? //環(huán)繞通知
? ? @Around("pt()")
? ? public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
? ? ? ? log.info("around before ...");
? ? ? ? //調(diào)用目標對象的原始方法執(zhí)行
? ? ? ? Object result = proceedingJoinPoint.proceed();
? ? ? ? //原始方法在執(zhí)行時:發(fā)生異常
? ? ? ? //后續(xù)代碼不在執(zhí)行
? ? ? ? log.info("around after ...");
? ? ? ? return result;
? ? }
? ? //后置通知
? ? @After("pt()")
? ? public void after(JoinPoint joinPoint){
? ? ? ? log.info("after ...");
? ? }
? ? //返回后通知(程序在正常執(zhí)行的情況下,會執(zhí)行的后置通知)
? ? @AfterReturning("pt()")
? ? public void afterReturning(JoinPoint joinPoint){
? ? ? ? log.info("afterReturning ...");
? ? }
? ? //異常通知(程序在出現(xiàn)異常的情況下,執(zhí)行的后置通知)
? ? @AfterThrowing("pt()")
? ? public void afterThrowing(JoinPoint joinPoint){
? ? ? ? log.info("afterThrowing ...");
? ? }
}
execution主要根據(jù)方法的返回值、包名、類名、方法名、方法參數(shù)等信息來匹配,語法為:
execution(訪問修飾符? 返回值 包名.類名.?方法名(方法參數(shù)) throws 異常?)
1.其中帶?的表示可以省略的部分
2.訪問修飾符:可省略(比如: public、protected)
3.包名.類名: 可省略
4.throws 異常:可省略(注意是方法上聲明拋出的異常,不是實際拋出的異常)
示例:@Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
如果只想給查詢的方法加上,那方法取名的時候最好遵循一個規(guī)范,比如查詢的方法都是 get...,
@Before("execution(* com.itheima.service.impl.DeptServiceImpl.get*(java.lang.Integer))")
切入點表達式示例
省略方法的修飾符號
execution(voidcom.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
使用*代替返回值類型
execution(*com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
使用*代替包名(一層包使用一個*)
execution(*com.itheima.*.*.DeptServiceImpl.delete(java.lang.Integer))
使用..省略包名
execution(*com..DeptServiceImpl.delete(java.lang.Integer))
使用*代替類名
execution(*com..*.delete(java.lang.Integer))
使用*代替方法名
execution(*com..*.*(java.lang.Integer))
使用 * 代替參數(shù)
execution(*com.itheima.service.impl.DeptServiceImpl.delete(*))
使用..省略參數(shù)
execution(*com..*.*(..))