SpringBoot AOP介紹

提及spring,咱們知道其最核心的兩個功能就是AOP(面向切面)和IOC(控制反轉),這邊文章來總結一下SpringBoot如何整合使用AOP。java

1、示例應用場景:對全部的web請求作切面來記錄日誌。web

一、pom中引入SpringBoot的web模塊和使用AOP相關的依賴:
============================================== spring

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.11</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1</version>
</dependency>
============================================================


其中:
cglib包是用來動態代理用的,基於類的代理;
aspectjrt和aspectjweaver是與aspectj相關的包,用來支持切面編程的;
aspectjrt包是aspectj的runtime包;
aspectjweaver是aspectj的織入包;編程

二、實現一個簡單的web請求入口(實現傳入name參數,返回「hello xxx」的功能): 數組


注意:在完成了引入AOP依賴包後,通常來講並不須要去作其餘配置。使用過Spring註解配置方式的人會問是否須要在程序主類中增長@EnableAspectJAutoProxy來啓用,實際並不須要。session

由於在AOP的默認配置屬性中,spring.aop.auto屬性默認是開啓的,也就是說只要引入了AOP依賴後,默認已經增長了@EnableAspectJAutoProxy。ide

三、定義切面類,實現web層的日誌切面函數

要想把一個類變成切面類,須要兩步,
① 在類上使用 @Component 註解 把切面類加入到IOC容器中
② 在類上使用 @Aspect 註解 使之成爲切面類spring-boot

package com.example.aop;this

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.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
* Created by lmb on 2018/9/5.
*/
@Aspect
@Component
public class WebLogAcpect {

private Logger logger = LoggerFactory.getLogger(WebLogAcpect.class);

/**
* 定義切入點,切入點爲com.example.aop下的全部函數
*/
@Pointcut("execution(public * com.example.aop..*.*(..))")
public void webLog(){}

/**
* 前置通知:在鏈接點以前執行的通知
* @param joinPoint
* @throws Throwable
*/
@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.example.aop包下的全部函數作切人,經過 @Before實現切入點的前置通知,經過 @AfterReturning記錄請求返回的對象。

 

2、AOP支持的通知
一、前置通知@Before:在某鏈接點以前執行的通知,除非拋出一個異常,不然這個通知不能阻止鏈接點以前的執行流程。

/**
* 前置通知,方法調用前被調用
* @param joinPoint/null
*/
@Before(value = POINT_CUT)
public void before(JoinPoint joinPoint){
logger.info("前置通知");
//獲取目標方法的參數信息
Object[] obj = joinPoint.getArgs();
//AOP代理類的信息
joinPoint.getThis();
//代理的目標對象
joinPoint.getTarget();
//用的最多 通知的簽名
Signature signature = joinPoint.getSignature();
//代理的是哪個方法
logger.info("代理的是哪個方法"+signature.getName());
//AOP代理類的名字
logger.info("AOP代理類的名字"+signature.getDeclaringTypeName());
//AOP代理類的類(class)信息
signature.getDeclaringType();
//獲取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//從獲取RequestAttributes中獲取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
//若是要獲取Session信息的話,能夠這樣寫:
//HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
//獲取請求參數
Enumeration<String> enumeration = request.getParameterNames();
Map<String,String> parameterMap = Maps.newHashMap();
while (enumeration.hasMoreElements()){
String parameter = enumeration.nextElement();
parameterMap.put(parameter,request.getParameter(parameter));
}
String str = JSON.toJSONString(parameterMap);
if(obj.length > 0) {
logger.info("請求的參數信息爲:"+str);
}
}

五、環繞通知@Around:包圍一個鏈接點的通知,如方法調用等。這是最強大的一種通知類型。環繞通知能夠在方法調用先後完成自定義的行爲,它也會選擇是否繼續執行鏈接點或者直接返回它本身的返回值或拋出異常來結束執行。

環繞通知最強大,也最麻煩,是一個對方法的環繞,具體方法會經過代理傳遞到切面中去,切面中可選擇執行方法與否,執行幾回方法等。環繞通知使用一個代理ProceedingJoinPoint類型的對象來管理目標對象,因此此通知的第一個參數必須是ProceedingJoinPoint類型。在通知體內調用ProceedingJoinPoint的proceed()方法會致使後臺的鏈接點方法執行。proceed()方法也可能會被調用而且傳入一個Object[]對象,該數組中的值將被做爲方法執行時的入參。

/**
* 環繞通知:
* 環繞通知很是強大,能夠決定目標方法是否執行,何時執行,執行時是否須要替換方法參數,執行完畢是否須要替換返回值。
* 環繞通知第一個參數必須是org.aspectj.lang.ProceedingJoinPoint類型
*/
@Around(value = POINT_CUT)
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
logger.info("環繞通知的目標方法名:"+proceedingJoinPoint.getSignature().getName());
try {
Object obj = proceedingJoinPoint.proceed();
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}

六、有時候咱們定義切面的時候,切面中須要使用到目標對象的某個參數,如何使切面能獲得目標對象的參數呢?可使用args來綁定。若是在一個args表達式中應該使用類型名字的地方使用一個參數名字,那麼當通知執行的時候對象的參數值將會被傳遞進來。

@Before("execution(* findById*(..)) &&" + "args(id,..)")
public void twiceAsOld1(Long id){
System.err.println ("切面before執行了。。。。id==" + id);

}

注意:任何通知方法均可以將第一個參數定義爲org.aspectj.lang.JoinPoint類型(環繞通知須要定義第一個參數爲ProceedingJoinPoint類型,它是 JoinPoint 的一個子類)。JoinPoint接口提供了一系列有用的方法,好比 getArgs()(返回方法參數)、getThis()(返回代理對象)、getTarget()(返回目標)、getSignature()(返回正在被通知的方法相關信息)和 toString()(打印出正在被通知的方法的有用信息)。

3、切入點表達式
定義切入點的時候須要一個包含名字和任意參數的簽名,還有一個切入點表達式,如execution(public * com.example.aop...(..))

切入點表達式的格式:execution([可見性]返回類型[聲明類型].方法名(參數)[異常])
其中[]內的是可選的,其它的還支持通配符的使用:
1) *:匹配全部字符
2) ..:通常用於匹配多個包,多個參數
3) +:表示類及其子類
4)運算符有:&&,||,!

切入點表達式關鍵詞用例:
1)execution:用於匹配子表達式。
//匹配com.cjm.model包及其子包中全部類中的全部方法,返回類型任意,方法參數任意
@Pointcut(「execution(* com.cjm.model...(..))」)
public void before(){}

2)within:用於匹配鏈接點所在的Java類或者包。
//匹配Person類中的全部方法
@Pointcut(「within(com.cjm.model.Person)」)
public void before(){}
//匹配com.cjm包及其子包中全部類中的全部方法
@Pointcut(「within(com.cjm..*)」)
public void before(){}

3) this:用於向通知方法中傳入代理對象的引用。
@Before(「before() && this(proxy)」)
public void beforeAdvide(JoinPoint point, Object proxy){
//處理邏輯
}

4)target:用於向通知方法中傳入目標對象的引用。
@Before(「before() && target(target)
public void beforeAdvide(JoinPoint point, Object proxy){
//處理邏輯
}

5)args:用於將參數傳入到通知方法中。
@Before(「before() && args(age,username)」)
public void beforeAdvide(JoinPoint point, int age, String username){
//處理邏輯
}

6)@within :用於匹配在類一級使用了參數肯定的註解的類,其全部方法都將被匹配。
@Pointcut(「@within(com.cjm.annotation.AdviceAnnotation)」)
- 全部被@AdviceAnnotation標註的類都將匹配
public void before(){}

7)@target :和@within的功能相似,但必需要指定註解接口的保留策略爲RUNTIME。
@Pointcut(「@target(com.cjm.annotation.AdviceAnnotation)」)
public void before(){}

8)@args :傳入鏈接點的對象對應的Java類必須被@args指定的Annotation註解標註。
@Before(「@args(com.cjm.annotation.AdviceAnnotation)」)
public void beforeAdvide(JoinPoint point){
//處理邏輯
}

9)@annotation :匹配鏈接點被它參數指定的Annotation註解的方法。也就是說,全部被指定註解標註的方法都將匹配。
@Pointcut(「@annotation(com.cjm.annotation.AdviceAnnotation)」)
public void before(){}

10)bean:經過受管Bean的名字來限定鏈接點所在的Bean。該關鍵詞是Spring2.5新增的。 @Pointcut(「bean(person)」) public void before(){}

相關文章
相關標籤/搜索