SpringBoot之集成Spring AOP

轉自: https://blog.csdn.net/zknxx/article/details/53240959

在開始之前,我們先把需要的jar包添加到工程裏。新增Maven依賴如下:

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>

接下來,我們進入正題。這裏的涉及的通知類型有:前置通知、後置最終通知、後置返回通知、後置異常通知、環繞通知,下面我們就具體的來看一下怎麼在SpringBoot中添加這些通知。

首先我們先創建一個Aspect切面類:

//匹配com.zkn.learnspringboot.web.controller包及其子包下的所有類的所有方法
@Pointcut("execution(* com.zkn.learnspringboot.web.controller..*.*(..))")
public void executeService(){
 
}

指定切點:

        //匹配com.zkn.learnspringboot.web.controller包及其子包下的所有類的所有方法
        @Pointcut("execution(* com.zkn.learnspringboot.web.controller..*.*(..))")
        public void executeService(){
     
        }

接着我們再創建一個Controller請求處理類:

    package com.zkn.learnspringboot.web.controller;
     
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
     
    /**
     * Created by zkn on 2016/11/19.
     */
    @RestController
    @RequestMapping("/aop")
    public class AopTestController {
     
    }

前置通知
配置前置通知:

  

      /**
         * 前置通知,方法調用前被調用
         * @param joinPoint
         */
        @Before("executeService()")
        public void doBeforeAdvice(JoinPoint joinPoint){
            System.out.println("我是前置通知!!!");
            //獲取目標方法的參數信息
            Object[] obj = joinPoint.getArgs();
            //AOP代理類的信息
            joinPoint.getThis();
            //代理的目標對象
            joinPoint.getTarget();
            //用的最多 通知的簽名
            Signature signature = joinPoint.getSignature();
            //代理的是哪一個方法
            System.out.println(signature.getName());
            //AOP代理類的名字
            System.out.println(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) {
                System.out.println("請求的參數信息爲:"+str);
            }
        }

注意:這裏用到了JoinPoint和RequestContextHolder。通過JoinPoint可以獲得通知的簽名信息,如目標方法名、目標方法參數信息等。通過RequestContextHolder來獲取請求信息,Session信息。
接下來我們在Controller類裏添加一個請求處理方法來測試一下前置通知:

       @RequestMapping("/testBeforeService.do")
        public String testBeforeService(String key,String value){
     
            return "key="+key+"  value="+value;
        }

前置通知攔截結果如下所示:

後置返回通知
配置後置返回通知的代碼如下:

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

Controller裏添加響應的請求處理信息來測試後置返回通知:

        @RequestMapping("/testAfterReturning.do")
        public String testAfterReturning(String key){
     
            return "key=: "+key;
        }
        @RequestMapping("/testAfterReturning01.do")
        public Integer testAfterReturning01(Integer key){
     
            return key;
        }

當發送請求爲:http://localhost:8001/aop/testAfterReturning.do?key=testsss&value=855sss時,處理結果如圖所示:
當發送請求爲:http://localhost:8001/aop/testAfterReturning01.do?key=55553&value=855sss時,處理結果如圖所示:
後置異常通知
後置異常通知的配置方式如下:

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

Controller裏配置響應的請求處理類:

        @RequestMapping("/testAfterThrowing.do")
        public String testAfterThrowing(String key){
     
            throw new NullPointerException();
        }

後置異常通知方法的處理結果如下所示:

後置最終通知
後置最終通知的配置方式如下:

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

Controller類配置相應的請求處理類:

        @RequestMapping("/testAfter.do")
        public String testAfter(String key){
     
            throw new NullPointerException();
        }
        @RequestMapping("/testAfter02.do")
        public String testAfter02(String key){
     
            return key;
        }

當發送請求爲:http://localhost:8001/aop/testAfter.do?key=55553&value=855sss

當發送請求爲:http://localhost:8001/aop/testAfter02.do?key=55553&value=855sss


環繞通知
環繞通知的配置方式如下:

       /**
         * 環繞通知:
         *   環繞通知非常強大,可以決定目標方法是否執行,什麼時候執行,執行時是否需要替換方法參數,執行完畢是否需要替換返回值。
         *   環繞通知第一個參數必須是org.aspectj.lang.ProceedingJoinPoint類型
         */
        @Around("execution(* com.zkn.learnspringboot.web.controller..*.testAround*(..))")
        public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
            System.out.println("環繞通知的目標方法名:"+proceedingJoinPoint.getSignature().getName());
            try {
                Object obj = proceedingJoinPoint.proceed();
                return obj;
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            return null;
        }

Controller對應的請求處理類如下:

        @RequestMapping("/testAroundService.do")
        public String testAroundService(String key){
     
            return "環繞通知:"+key;
        }

當發送請求爲:http://localhost:8001/aop/testAroundService.do?key=55553

當發送請求爲:http://localhost:8001/aop/testAfter02.do?key=55553&value=855sss時,不符合環繞通知的切入規則,所以環繞通知不會 執行。
完整的AOP配置代碼如下:

    package com.zkn.learnspringboot.aop;
     
    import com.alibaba.fastjson.JSON;
    import com.google.common.collect.Maps;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.RequestContextHolder;
     
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    import java.util.Enumeration;
    import java.util.Map;
     
    /**
     * Created by zkn on 2016/11/18.
     */
    @Component
    @Aspect
    public class WebControllerAop {
     
        //匹配com.zkn.learnspringboot.web.controller包及其子包下的所有類的所有方法
        @Pointcut("execution(* com.zkn.learnspringboot.web.controller..*.*(..))")
        public void executeService(){
     
        }
     
        /**
         * 前置通知,方法調用前被調用
         * @param joinPoint
         */
        @Before("executeService()")
        public void doBeforeAdvice(JoinPoint joinPoint){
            System.out.println("我是前置通知!!!");
            //獲取目標方法的參數信息
            Object[] obj = joinPoint.getArgs();
            //AOP代理類的信息
            joinPoint.getThis();
            //代理的目標對象
            joinPoint.getTarget();
            //用的最多 通知的簽名
            Signature signature = joinPoint.getSignature();
            //代理的是哪一個方法
            System.out.println(signature.getName());
            //AOP代理類的名字
            System.out.println(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) {
                System.out.println("請求的參數信息爲:"+str);
            }
        }
     
        /**
         * 後置返回通知
         * 這裏需要注意的是:
         *      如果參數中的第一個參數爲JoinPoint,則第二個參數爲返回值的信息
         *      如果參數中的第一個參數不爲JoinPoint,則第一個參數爲returning中對應的參數
         * returning 限定了只有目標方法返回值與通知方法相應參數類型時才能執行後置返回通知,否則不執行,對於returning對應的通知方法參數爲Object類型將匹配任何目標返回值
         * @param joinPoint
         * @param keys
         */
        @AfterReturning(value = "execution(* com.zkn.learnspringboot.web.controller..*.*(..))",returning = "keys")
        public void doAfterReturningAdvice1(JoinPoint joinPoint,Object keys){
     
            System.out.println("第一個後置返回通知的返回值:"+keys);
        }
     
        @AfterReturning(value = "execution(* com.zkn.learnspringboot.web.controller..*.*(..))",returning = "keys",argNames = "keys")
        public void doAfterReturningAdvice2(String keys){
     
            System.out.println("第二個後置返回通知的返回值:"+keys);
        }
     
        /**
         * 後置異常通知
         *  定義一個名字,該名字用於匹配通知實現方法的一個參數名,當目標方法拋出異常返回後,將把目標方法拋出的異常傳給通知方法;
         *  throwing 限定了只有目標方法拋出的異常與通知方法相應參數異常類型時才能執行後置異常通知,否則不執行,
         *      對於throwing對應的通知方法參數爲Throwable類型將匹配任何異常。
         * @param joinPoint
         * @param exception
         */
        @AfterThrowing(value = "executeService()",throwing = "exception")
        public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){
            //目標方法名:
            System.out.println(joinPoint.getSignature().getName());
            if(exception instanceof NullPointerException){
                System.out.println("發生了空指針異常!!!!!");
            }
        }
     
        /**
         * 後置最終通知(目標方法只要執行完了就會執行後置通知方法)
         * @param joinPoint
         */
        @After("executeService()")
        public void doAfterAdvice(JoinPoint joinPoint){
     
            System.out.println("後置通知執行了!!!!");
        }
     
        /**
         * 環繞通知:
         *   環繞通知非常強大,可以決定目標方法是否執行,什麼時候執行,執行時是否需要替換方法參數,執行完畢是否需要替換返回值。
         *   環繞通知第一個參數必須是org.aspectj.lang.ProceedingJoinPoint類型
         */
        @Around("execution(* com.zkn.learnspringboot.web.controller..*.testAround*(..))")
        public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
            System.out.println("環繞通知的目標方法名:"+proceedingJoinPoint.getSignature().getName());
            try {//obj之前可以寫目標方法執行前的邏輯
                Object obj = proceedingJoinPoint.proceed();//調用執行目標方法
                return obj;
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            return null;
        }
    }

完整的Controller類代碼如下:

 

   package com.zkn.learnspringboot.web.controller;
     
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
     
    /**
     * Created by zkn on 2016/11/19.
     */
    @RestController
    @RequestMapping("/aop")
    public class AopTestController {
     
        @RequestMapping("/testBeforeService.do")
        public String testBeforeService(String key,String value){
     
            return "key="+key+"  value="+value;
        }
        @RequestMapping("/testAfterReturning.do")
        public String testAfterReturning(String key){
     
            return "key=: "+key;
        }
        @RequestMapping("/testAfterReturning01.do")
        public Integer testAfterReturning01(Integer key){
     
            return key;
        }
        @RequestMapping("/testAfterThrowing.do")
        public String testAfterThrowing(String key){
     
            throw new NullPointerException();
        }
        @RequestMapping("/testAfter.do")
        public String testAfter(String key){
     
            throw new NullPointerException();
        }
        @RequestMapping("/testAfter02.do")
        public String testAfter02(String key){
     
            return key;
        }
        @RequestMapping("/testAroundService.do")
        public String testAroundService(String key){
     
            return "環繞通知:"+key;
        }
    }

備註:

     1.  @Around 和 @Before,@After 不能同時使用, 同時使用時,前置監聽會失效 .

     2.  @Order(0) 註釋類的加載優先級, value(這裏寫的0)越小,優先級越高 .

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