前言
把文章在簡書同步了之后,有幾篇文章被收入專題,還有一篇文章被收入首頁,盡管不是什么了不起的事,但是這樣也讓自己挺開心的,慕課網約我錄了一次視頻課程,沒有通過,讓修改下錄第二次,想想還是算了,目前的自己水平還是有限,另一方面時間也比較有限,在博客這方面能夠持續下去就已經是件很難的事情,其他的以后再說看緣分吧。接下來繼續技術的分析。
前兩篇主要記錄了AOP所用的核心設計模式-代理模式,包含動態代理和靜態代理,以及JDK和CGLIB的兩種實現方式,接下來開始重點分析Spring-AOP源碼相關操作,當然開始也是從概念理解。這個不太好明白,但是第一遍閱讀也主要是為了對于框架有個大局觀,不需要過度的拘泥于細節。AOP這段計劃在9月中之前結束,而下半月主要是SpringMVC相關的源碼研究,可能也會結合一點事務和mybatis源碼的研究。
參考了很多程序員DD的東西
Spring-Aop相關概念如下:
我把這個圖片放在網絡上了,歡迎大家前往下載,建議大家先從地址一下載,地址二要耗流量
下載地址
備用地址
相關的概念描述在腦圖中有詳細解釋,當然方便理解,這里我也會記錄下來。
- Target(目標對象):需要被代理增強的對象
- Proxy(代理對象):目標對象被AOP 織入 增強/通知后,產生的對象.
- Joinpoint(連接點):指那些被攔截到的點.在Spring中,這些點指方法(因為Spring只支持方法類型的連接點).
- Pointcut(切入點):指需要(配置)被增強的Joinpoint.
- Advice(通知/增強):指攔截到Joinpoint后要做的操作.通知分為前置通知/后置通知/異常通知/最終通知/環繞通知等.
- Aspect(切面):切入點和通知的結合。
- Weaving(織入):指把增強/通知應用到目標對象來創建代理對象的過程(Spring采用動態代理織入,AspectJ采用編譯期織入和類裝載期織入).
- Introduction(引入增強):一種特殊通知,在不修改類代碼的前提下,可以在運行期為類動態地添加一些Method/Field(不常用).
項目部署
我們先在springboot中將aop整合進來
在pom.xml引入pom依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
starter中默認添加了@EnableAspectJAutoProxy
設計一個簡單的controller入門
@RestController
public class HomeController {
private Logger logger = Logger.getLogger(getClass());
@GetMapping("/index")
public String index(@RequestParam String name){
logger.info("-----------{name}:{}"+name);
return "【welcome to aop】:" +name;
}
}
實現Web層的日志切面
- 使用@Aspect注解將一個java類定義為切面類
- 使用@Pointcut定義一個切入點,可以是一個規則表達式,比如下例中某個package下的所有函數,也可以是一個注解等。根據需要在切入點不同位置的切入內容
- 使用@Before在切入點開始處切入內容
- 使用@After在切入點結尾處切入內容
- 使用@AfterReturning在切入點return內容之后切入內容(可以用來對處理返回值做一些加工處理)
- 使用@Around在切入點前后切入內容,并自己控制何時執行切入點自身的內容
- 使用@AfterThrowing用來處理當切入內容部分拋出異常之后的處理邏輯
代碼如下:
@Aspect
@Component
public class WebLogAspect {
private Logger logger = Logger.getLogger(getClass());
@Pointcut("execution(public * com.sunliangliang.springsource.controller..*(..))")
public void webLog(){}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 接收到請求,記錄請求內容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 記錄下請求內容
logger.info("URL : " + request.getRequestURL().toString());
logger.info("HTTP_METHOD : " + request.getMethod());
logger.info("IP : " + request.getRemoteAddr());
logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 處理完請求,返回內容
logger.info("RESPONSE : " + ret);
}
}
- 通過@Pointcut定義切入點為
com.sunliangliang.springsource.controller.
包下面的所有函數,然后通過@Before
實現,對請求內容的日志記錄(本文只是說明過程,可以根據需要調整內容),最后通過@AfterReturning
記錄請求返回的對象。
運行程序
輸出如下日志:
2017-09-01 18:20:34.953 INFO 14696 --- [nio-8888-exec-3] c.s.springsource.aop.WebLogAspect : URL : http://localhost:8888/index
2017-09-01 18:20:34.953 INFO 14696 --- [nio-8888-exec-3] c.s.springsource.aop.WebLogAspect : HTTP_METHOD : GET
2017-09-01 18:20:34.953 INFO 14696 --- [nio-8888-exec-3] c.s.springsource.aop.WebLogAspect : IP : 0:0:0:0:0:0:0:1
2017-09-01 18:20:34.953 INFO 14696 --- [nio-8888-exec-3] c.s.springsource.aop.WebLogAspect : CLASS_METHOD : com.sunliangliang.springsource.controller.HomeController.index
2017-09-01 18:20:34.953 INFO 14696 --- [nio-8888-exec-3] c.s.springsource.aop.WebLogAspect : ARGS : [liangliang]
2017-09-01 18:20:34.953 INFO 14696 --- [nio-8888-exec-3] c.s.s.controller.HomeController : -----------{name}:{}liangliang
2017-09-01 18:20:34.953 INFO 14696 --- [nio-8888-exec-3] c.s.springsource.aop.WebLogAspect : RESPONSE : 【welcome to aop】:liangliang
優化
由于通過AOP實現,程序得到了很好的解耦,但是也會帶來一些問題,比如:我們可能會對Web層做多個切面,校驗用戶,校驗頭信息等等,這個時候經常會碰到切面的處理順序問題。
所以要定義每個切面的優先級,我們需要@Order(i)注解來標識切面的優先級。i值越小,優先級越高。假設我們還有一個切面是CheckNameAspect用來校驗name必須為didi,我們為其設置@Order(10),而上文中WebLogAspect設置為@Order(5),所以WebLogAspect有更高的優先級,這個時候執行順序是這樣的:
在@Before中優先執行@Order(5)的內容,再執行@Order(10)的內容
在@After和@AfterReturning中優先執行@Order(10)的內容,再執行@Order(5)的內容
所以我們可以這樣子總結:在切入點前的操作,按order的值由小到大執行
在切入點后的操作,按order的值由大到小執行
代碼完整示例