Spring-boot集成AOP及AOP相關學習

1、Spring AOP簡單介紹

一、AOP簡單介紹前端

  • AOP(Aspect Oriented Programming),即面向切面編程,能夠說是OOP(Object Oriented Programming,面向對象編程)的補充和完善。OOP引入封裝、繼承、多態等概念來創建一種對象層次結構,用於模擬公共行爲的一個集合。不過OOP容許開發者定義縱向的關係,但並不適合定義橫向的關係,例如日誌功能。日誌代碼每每橫向地散佈在全部對象層次中,而與它對應的對象的核心功能毫無關係對於其餘類型的代碼,如安全性、異常處理和透明的持續性也都是如此,這種散佈在各處的無關的代碼被稱爲橫切(cross cutting),在OOP設計中,它致使了大量代碼的重複,而不利於各個模塊的重用。
  • AOP技術偏偏相反,它利用一種稱爲"橫切"的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其命名爲"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊之間的耦合度,並有利於將來的可操做性和可維護性。
  • 使用"橫切"技術,AOP把軟件系統分爲兩個部分:核心關注點橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特色是,他們常常發生在覈心關注點的多處,而各處基本類似,好比權限認證、日誌、事物。AOP的做用在於分離系統中的各類關注點,將核心關注點和橫切關注點分離開來。
  • Spring中的AOP代理仍是離不開Spring的IOC容器,代理的生成,管理及其依賴關係都是由IOC容器負責,Spring默認使用JDK動態代理,在須要代理類而不是代理接口的時候,Spring會自動切換爲使用CGLIB代理,不過如今的項目都是面向接口編程,因此JDK動態代理相對來講用的仍是多一些。

二、AOP的概念java

  • 橫切關注點:web

    對哪些方法進行攔截,攔截後怎麼處理,這些關注點稱之爲橫切關注點(概念)
  • 切面(aspect):類是對物體特徵的抽象,切面就是對橫切關注點的抽象
  • 鏈接點(joinpoint):被攔截到的點,由於Spring只支持方法類型的鏈接點,因此在Spring中鏈接點指的就是被攔截到的方法,實際上鍊接點還能夠是字段或者構造器
  • 切入點(pointcut):就是帶有通知的鏈接點,在程序中主要體現爲書寫切入點表達式
  • 通知(advice):所謂通知指的就是指攔截到鏈接點以後要執行的代碼,通知分爲前置(before)、後置(afterReturning)、異常(afterThrowing)、最終(after)、環繞通知(around)五類
  • 目標對象:代理的目標對象
  • 織入(weave):將切面應用到目標對象並致使代理對象建立的過程
  • 引入(introduction):在不修改代碼的前提下,引入能夠在運行期爲類動態地添加一些方法或字段

三、AOP開發步驟spring

  1. 定義普通業務組件
  2. 定義切入點,一個切入點可能橫切多個業務組件
  3. 定義加強處理,加強處理就是在AOP框架爲普通業務組件織入的處理動做

因此進行AOP編程的關鍵就是定義切入點和定義加強處理,一旦定義了合適的切入點和加強處理,AOP框架將自動生成AOP代理,即:代理對象的方法=加強處理+被代理對象的方法。apache

2、Spring集成AOP開發

  1. pom文件添加依賴

    <!--aop-->
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>複製代碼

  2. 切面類、切入點、通知、鏈接點代碼

    package com.wxx.demo.aop;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.google.common.collect.Maps;
    import com.wxx.demo.model.HelloModel;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.validation.BindingResult;
    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Map;
    
    @Component
    @Aspect//切面類
    public class HelloAspect {
    
        private static final Logger logger = LoggerFactory.getLogger(HelloAspect.class);
    
        //凡是註解了RequestMapping的方法都被攔截
        @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
        private void webPointcut() {
        }
    
        //指定切入點
        @Pointcut("execution(* com.wxx.demo.controller.HelloController.*(..)))")
        private void validate() {
        }
    
    
        //通知advice
        @Before("validate()")
        public void doBefore(JoinPoint joinPoint) {//經過joinpoint獲取通知的簽名信息如目標名,參數信息
            System.out.println("========================前置通知========================");
            Object[] args = joinPoint.getArgs();
            joinPoint.getThis();//aop代理信息
            System.out.println("========================aop代理信息:" + joinPoint.getThis() + "========================");
            joinPoint.getTarget();//代理對象
            System.out.println("========================aop代理對象:" + joinPoint.getTarget() + "========================");
            Signature signature = joinPoint.getSignature();
            System.out.println("========================aop通知簽名:" + signature + "========================");
            String methodName = signature.getName();//代理方法名
            System.out.println("========================aop代理方法名:" + methodName + "========================");
    
            // AOP 代理的名字
            System.out.println("========================aop代理的名字:" + signature.getDeclaringTypeName() + "========================");
            signature.getDeclaringType();//  AOP代理類的類(class)信息
    
            /**
             * 經過RequestContextHolder獲取請求信息,如session 信息 ;
             * 注:
                 關於調用 JoinPoint 和 RequestContextHolder。
                 經過JoinPoint能夠得到通知的簽名信息,如目標方法名、目標方法參數信息等。
                 經過RequestContextHolder來獲取請求信息,Session信息。
             */
            //  獲取RequestAttributes
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            //  從requestAttributes中獲取HttpServletRequest信息
            HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
            //  獲取session信息
            HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
    
            System.out.println("請求 : " + request + " , HttpSession : " + session);
            Enumeration<String> enumerations = request.getParameterNames();
            //        Map<String,String> parameterMaps=new HashMap<>();
            Map<String, String> parameterMaps = Maps.newHashMap();
            while (enumerations.hasMoreElements()) {
                String parameter = enumerations.nextElement();
                parameterMaps.put(parameter, request.getParameter(parameter));
            }
    
            // String str=JSON.toJSONString(parameterMaps);
            String str = JSON.toJSONString(parameterMaps);//   alibaba.fastjson
            if (args.length > 0) {
                System.out.println("請求參數信息爲 : " + str);
            }
    
        }
    
        /**
         * 後置返回通知
         * 須要注意:
         *     若是第一個參數是JoinPoint,則第二個參數是返回值的信息
         *     若是參數中的第一個不是JoinPoint,則第一個參數是returning中對應的參數,
         *     returning 限定了只有目標方法返回值與通知方法相應參數類型時才能
         *     執行後置返回通知,不然不執行;
         *     對於returning對應的通知方法參數爲Object類型將匹配任何目標返回值
         * @param joinPoint
         * @param keys
         * value = "execution(* com.wxx.demo.controller..*.*(..))"
         */
        @AfterReturning(pointcut = "validate()",returning = "keys")
        public void doAfterReturn(JoinPoint joinPoint,Object keys){
            System.out.println("========================後置返回通知執行========================");
    
            if (keys instanceof HelloModel){
                HelloModel hello = (HelloModel) keys;
                hello.setHello("hello aop i am @AfterReturning!");
                System.out.println("========================後置返回通知修改後的參數:" + keys.toString() + "========================");
            }
        }
    
    
        /**
         * 後置異常通知
         * @param e
         */
        @AfterThrowing(pointcut = "validate()",throwing = "e")
        public void doAfterThrowing(Exception e){
            //if (e instanceof FieldError)
            Map<String,Object> map = new HashMap<>();
            map.put("resCode",500);
            map.put("resMsg","Illegal parameters");
            writeContent(JSONObject.toJSONString(map));
        }
    
    
        /**
         * 後置最終通知
         *
         */
        @After(value = "validate()")
        public void doAfter(){
            System.out.println("========================後置通知最終執行了========================");
        }
    
    
    
        /**
         * 攔截web層異常,
         * 記錄異常日誌,並返回友好信息到前端
         * 目前只攔截Exception,是否要攔截Error需再作考慮
         *
         * @param e 異常對象
         */
        @AfterThrowing(pointcut = "webPointcut()", throwing = "e")
        public void handleThrowing(Exception e) {
    
            e.printStackTrace();
            logger.error("發現異常!" + e.getMessage());
            logger.error(JSON.toJSONString(e.getStackTrace()));
            //這裏輸入友好性信息
            Map<String, Object> map = new HashMap<>();
            map.put("resCode", "500");
            map.put("resMsg", "laoma is dead");
            writeContent(JSONObject.toJSONString(map));
    
        }
    
    
        /**
         * 將內容輸入瀏覽器
         *
         * @param content
         */
        private void writeContent(String content) {
            HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
            response.reset();
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Type", "text/plain;charset=UTF-8");
            response.setHeader("icop-content-type", "exception");
            PrintWriter writer = null;
            try {
                writer = response.getWriter();
            } catch (IOException e) {
                e.printStackTrace();
            }
            writer.print(content);
            writer.flush();
            writer.close();
        }
    
    }
    
    複製代碼

  3. Controller方法代碼

    /**
     * 參數校驗
     * @param helloModel
     * @param bindingResult
     */
    @GetMapping("/validate")
    public HelloModel validate(@Valid HelloModel helloModel, BindingResult bindingResult) {
        HelloValidate.validate(bindingResult);
        return helloModel;
    }複製代碼

  4. 代碼運行實例,瀏覽器訪問http://localhost:8086/api/validate?hello= &phone="123456789"執行正常的日誌信息及頁面返回


    ========================前置通知========================
    ========================aop代理信息:com.wxx.demo.controller.HelloController@6cdae95d========================
    ========================aop代理對象:com.wxx.demo.controller.HelloController@6cdae95d========================
    ========================aop通知簽名:HelloModel com.wxx.demo.controller.HelloController.validate(HelloModel,BindingResult)========================
    ========================aop代理方法名:validate========================
    ========================aop代理的名字:com.wxx.demo.controller.HelloController========================
    請求 : org.apache.catalina.connector.RequestFacade@5288ea44 ,  HttpSession : org.apache.catalina.session.StandardSessionFacade@89f7377
    請求參數信息爲 : {"phone":"「123456789」","hello":" "}
    ========================後置通知最終執行了========================
    ========================後置返回通知執行========================
    ========================後置返回通知修改後的參數:HelloModel{hello='hello aop i am @AfterReturning!', phone='「123456789」', email='null'}========================複製代碼

  5. 執行異常的日誌及頁面返回

    ========================前置通知========================
    ========================aop代理信息:com.wxx.demo.controller.HelloController@6cdae95d========================
    ========================aop代理對象:com.wxx.demo.controller.HelloController@6cdae95d========================
    ========================aop通知簽名:HelloModel com.wxx.demo.controller.HelloController.validate(HelloModel,BindingResult)========================
    ========================aop代理方法名:validate========================
    ========================aop代理的名字:com.wxx.demo.controller.HelloController========================
    請求 : org.apache.catalina.connector.RequestFacade@5288ea44 ,  HttpSession : org.apache.catalina.session.StandardSessionFacade@89f7377
    請求參數信息爲 : {"phone":"","hello":" "}
    ========================後置通知最終執行了========================
    2018-12-28 17:53:19.570 ERROR 10632 --- [nio-8086-exec-2] o.a.c.c.C.[.[.[.[dispatcherServlet]      : Servlet.service() for servlet [dispatcherServlet] in context with path [/api] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: 手機號不能爲空!] with root cause
    
    java.lang.IllegalArgumentException: 手機號不能爲空!
    	at org.springframework.util.Assert.isTrue(Assert.java:92) ~[spring-core-4.3.21.RELEASE.jar:4.3.21.RELEASE]
    複製代碼



採坑總結:該實例爲springmvc的參數校驗和異常處理用aop統一處理,學習中遇到的坑編程

啓動報錯json

Caused by: java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut 
複製代碼

緣由是配置不一樣通知的時候參數是否配置好比:api

相關文章
相關標籤/搜索