SpringBoot集成AOP切面以及切入點表達式的簡單理解

看完正文之後再來理解這個表達式 艘EZ

先看完下面的文章再來看這一部分

首先我說一下切入點表達式,然後你在去看下面的文章

@Pointcut("execution(public * com.skxx.vrxmty.modules.*.*.controller.*.*(..))")
public void before(){}

匹配modules下的所有controller下的所有方法,不包含controlelr中方法調用其他的方法

 

先看此處----------------------正文---------------------------------------------------

以下轉載自:https://blog.csdn.net/lmb55/article/details/82470388

一、示例應用場景:對所有的web請求做切面來記錄日誌。

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

 <!--10.springboot aop-->
        <!-- start of aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- end of aop-->

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

2、實現一個簡單的web請求入口(實現傳入name參數,返回“hello xxx”的功能): 
這裏寫圖片描述

注意:在完成了引入AOP依賴包後,一般來說並不需要去做其他配置。使用過Spring註解配置方式的人會問是否需要在程序主類中增加@EnableAspectJAutoProxy來啓用,實際並不需要。

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

3、定義切面類,實現web層的日誌切面

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

package com.example.aop;

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記錄請求返回的對象。

訪問http://localhost:8004/hello?name=lmb得到控制檯輸出如下: 
這裏寫圖片描述

詳細代碼參見本人的Github:SpringBoot整合AOP

二、AOP支持的通知 
1、前置通知@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);
    }  
}

注意:這裏用到了JoinPoint和RequestContextHolder。 
1)、通過JoinPoint可以獲得通知的簽名信息,如目標方法名、目標方法參數信息等; 
2)、通過RequestContextHolder來獲取請求信息,Session信息;

2、後置通知@AfterReturning:在某連接點之後執行的通知,通常在一個匹配的方法返回的時候執行(可以在後置通知中綁定返回值)。

/** 
 * 後置返回通知 
 * 這裏需要注意的是: 
 *      如果參數中的第一個參數爲JoinPoint,則第二個參數爲返回值的信息 
 *      如果參數中的第一個參數不爲JoinPoint,則第一個參數爲returning中對應的參數 
 *       returning:限定了只有目標方法返回值與通知方法相應參數類型時才能執行後置返回通知,否則不執行,
 *       對於returning對應的通知方法參數爲Object類型將匹配任何目標返回值 
 * @param joinPoint 
 * @param keys 
 */  
@AfterReturning(value = POINT_CUT,returning = "keys")  
public void doAfterReturningAdvice1(JoinPoint joinPoint,Object keys){  
    logger.info("第一個後置返回通知的返回值:"+keys);  
}  

@AfterReturning(value = POINT_CUT,returning = "keys",argNames = "keys")  
public void doAfterReturningAdvice2(String keys){  
    logger.info("第二個後置返回通知的返回值:"+keys);  
}

3、後置異常通知@AfterThrowing:在方法拋出異常退出時執行的通知。

/** 
 * 後置異常通知 
 *  定義一個名字,該名字用於匹配通知實現方法的一個參數名,當目標方法拋出異常返回後,將把目標方法拋出的異常傳給通知方法; 
 *  throwing:限定了只有目標方法拋出的異常與通知方法相應參數異常類型時才能執行後置異常通知,否則不執行, 
 *           對於throwing對應的通知方法參數爲Throwable類型將匹配任何異常。 
 * @param joinPoint 
 * @param exception 
 */  
@AfterThrowing(value = POINT_CUT,throwing = "exception")  
public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){  
    //目標方法名:  
    logger.info(joinPoint.getSignature().getName());  
    if(exception instanceof NullPointerException){  
        logger.info("發生了空指針異常!!!!!");  
    }  
}  

4、後置最終通知@After:當某連接點退出時執行的通知(不論是正常返回還是異常退出)。

/** 
 * 後置最終通知(目標方法只要執行完了就會執行後置通知方法) 
 * @param joinPoint 
 */  
@After(value = POINT_CUT)  
public void doAfterAdvice(JoinPoint joinPoint){ 
    logger.info("後置最終通知執行了!!!!");  
}  

5、環繞通知@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;  
}  

6、有時候我們定義切面的時候,切面中需要使用到目標對象的某個參數,如何使切面能得到目標對象的參數呢?可以使用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()(打印出正在被通知的方法的有用信息)。

三、切入點表達式 
定義切入點的時候需要一個包含名字和任意參數的簽名,還有一個切入點表達式,如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(){}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章