AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,經過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是Spring框架中的一個重要內容,它經過對既有程序定義一個切入點,而後在其先後切入不一樣的執行內容,好比常見的有:打開數據庫鏈接/關閉數據庫鏈接、打開事務/關閉事務、記錄日誌等。基於AOP不會破壞原來程序邏輯,所以它能夠很好的對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發的效率。java
由於須要對web請求作切面來記錄日誌,因此先引入web模塊,並建立一個簡單的hello請求的處理。web
<dependency>spring
<groupId>org.springframework.boot</groupId>數據庫
<artifactId>spring-boot-starter-web</artifactId>編程
</dependency>app
實現一個簡單請求處理:框架
@RestController函數
publicclass HelloController {spring-boot
@RequestMapping("/hello")測試
public String hello(String name,int state){
return"name "+name+"---"+state;
}
}
引入AOP依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
在完成了引入AOP依賴包後,通常來講並不須要去作其餘配置。也許在Spring中使用過註解配置方式的人會問是否須要在程序主類中增長@EnableAspectJAutoProxy
來啓用,實際並不須要。
能夠看下面關於AOP的默認配置屬性,其中spring.aop.auto
屬性默認是開啓的,也就是說只要引入了AOP依賴後,默認已經增長了@EnableAspectJAutoProxy
。
# AOP spring.aop.auto=true # Add @EnableAspectJAutoProxy.
spring.aop.proxy-target-class=false # Whether subclass-based (CGLIB) proxies are to be created (true) as opposed to standard Java interface-based proxies (false).
我在作測試的時候,以上兩個配置我都沒有進行使用,請自行進行測試。
而當咱們須要使用CGLIB來實現AOP的時候,須要配置spring.aop.proxy-target-class=true
,否則默認使用的是標準Java的實現。
實現AOP的切面主要有如下幾個要素:
使用@Aspect註解將一個java類定義爲切面類
使用@Pointcut定義一個切入點,能夠是一個規則表達式,好比下例中某個package下的全部函數,也能夠是一個註解等。
根據須要在切入點不一樣位置的切入內容
使用@Before在切入點開始處切入內容
使用@After在切入點結尾處切入內容
使用@AfterReturning在切入點return內容以後切入內容(能夠用來對處理返回值作一些加工處理)
使用@Around在切入點先後切入內容,並本身控制什麼時候執行切入點自身的內容
使用@AfterThrowing用來處理當切入內容部分拋出異常以後的處理邏輯
import java.util.Arrays;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* 實現Web層的日誌切面
* @author
* @version v.0.1
*/
@Aspect
@Component
@Order(-5)
public class WebLogAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 定義一個切入點.
* 解釋下:
*
* ~ 第一個 * 表明任意修飾符及任意返回值.
* ~ 第二個 * 任意包名
* ~ 第三個 * 表明任意方法.
* ~ 第四個 * 定義在web包或者子包
* ~ 第五個 * 任意方法
* ~ .. 匹配任意數量的參數.
*/
@Pointcut("execution(public * com.kfit.*.web..*.*(..))")
publicvoid webLog(){}
@Before("webLog()")
publicvoid doBefore(JoinPoint joinPoint){
// 接收到請求,記錄請求內容
logger.info("WebLogAspect.doBefore()");
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()));
//獲取全部參數方法一:
Enumeration<String> enu=request.getParameterNames();
while(enu.hasMoreElements()){
String paraName=(String)enu.nextElement();
System.out.println(paraName+": "+request.getParameter(paraName));
}
}
@AfterReturning("webLog()")
publicvoid doAfterReturning(JoinPoint joinPoint){
// 處理完請求,返回內容
logger.info("WebLogAspect.doAfterReturning()");
}
}
因爲經過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)的內容
因此咱們能夠這樣子總結: