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

相关文章
相关标签/搜索