zoukankan      html  css  js  c++  java
  • 小白的springboot之路(十四)、AOP

    0、前言

    1、什么是AOP

      AOP(面向切面编程),是一种横切技术,是对OOP的补充和完善;

      使用AOP的横切,可以对系统进行无侵入性的日志监听、事务、权限管理等;

      思想上跟拦截器其实类似;拦截器是对action进行拦截处理,AOP是对切面进行拦截处理,其实切面也属于一种action集合;

      AOP可以很好解耦;

    2、AOP的组成

      Aspect:切面;

      Join point:连接点;

      Advice:通知,在切入点上执行的操作;

      Poincut:带有通知的连接点;

      target:被通知的对象;

      AOP proxy;AOP代理;

    其中,Advice(通知)分为以下几种:

    • before(前置通知): 在方法开始执行前执行
    • after(后置通知): 在方法执行后执行
    • afterReturning(返回后通知): 在方法返回后执行
    • afterThrowing(异常通知): 在抛出异常时执行
    • around(环绕通知): 在方法执行前和执行后都会执行
    通知的执行顺序:
    around > before > around > after > afterReturning

    一、实现示例

      光看理论和定义,很多人可能都觉得很难理解,其实用法比较简单,不难的,

      我们先来个简单的例子,看完例子你可能就豁然开朗了,

      所谓程序员,好看书不如多动手:

      实现:

    1、添加依赖

            <!-- 8、集成AOP  -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>

    2、添加切面类LogAspect

    package com.anson.common.aspect;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    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;
    
    /**
     * @description: AOP切面
     * @author: anson
     * @Date: 2019/12/20 10:11
     */
    
    @Aspect //1、添加AOP相关注解
    @Component
    public class LogAspect
    {
        private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
    
        //2、定义切入点(可以匹配、注解的方式,可混用)
    //    @Pointcut("execution(public * com.anson.controller.*.*(..))")
    @Pointcut("execution(public * com.anson.controller.*.*(..)) && @annotation(com.anson.common.annotation.LogAnnotation)")
      // @Pointcut("execution(public * com.anson.controller.TestController.get*()) && @annotation(com.anson.common.annotation.LogAnnotation)")
        public void pointcut(){}
    
        //===========通知 多中通知可根据需要灵活选用,一般Before 、AfterReturning即可=======================
        /**
         * 前置通知:在连接点之前执行的通知
         * @param joinPoint
         * @throws Throwable
         */
        @Before("pointcut()")
        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 = "pointcut()")
        public void doAfterReturning(Object ret) throws Throwable {
            // 处理完请求,返回内容
            logger.info("RESPONSE : " + ret);
        }
    //==============================================
        @After("pointcut()")
        public void commit() {
            logger.info("after commit");
        }
    
        @AfterThrowing("pointcut()")
        public void afterThrowing() {
            logger.info("afterThrowing afterThrowing  rollback");
        }
    
        @Around("pointcut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            try {
                System.out.println("around");
                return joinPoint.proceed();
            } catch (Throwable e) {
                e.printStackTrace();
                throw e;
            } finally {
                logger.info("around");
            }
        }
    
    }

    需要注意的是:上面代码注释2的地方【2、定义切入点(可以匹配、注解的方式,可混用)】;

    简单点说就是通过匹配或者注解(也可两种同时使用)匹配哪些类的哪些方法;

    再直白点说,就是我们要对哪些类的哪些方法执行处理,要处理的范围是哪些;

    类用*作为通配符,方法用(..)表示,括号中的两点表示匹配任何参数,包括没有参数;

    上面这样其实就可以完成一个切面了,

    比如将切面定义那里改为:@Pointcut("execution(public * com.anson.controller.*.*(..))")
    那么运行程序后,所有com.anson.controller包下的所有类的所有方法,都会执行这个切面定义的方法进行相关写日志处理了,结果如下:
    around
    [2019-12-20 11:19:02,205][INFO ][http-nio-8095-exec-1] URL : http://localhost:8095/user/userall (LogAspect.java:45)
    [2019-12-20 11:19:02,205][INFO ][http-nio-8095-exec-1] HTTP_METHOD : GET (LogAspect.java:46)
    [2019-12-20 11:19:02,206][INFO ][http-nio-8095-exec-1] IP : 0:0:0:0:0:0:0:1 (LogAspect.java:47)
    [2019-12-20 11:19:02,207][INFO ][http-nio-8095-exec-1] CLASS_METHOD : com.anson.controller.UserController.getUserAll (LogAspect.java:48)
    [2019-12-20 11:19:02,208][INFO ][http-nio-8095-exec-1] ARGS : [] (LogAspect.java:49)
    [2019-12-20 11:19:02,510][DEBUG][http-nio-8095-exec-1] ==>  Preparing: select id, userName, passWord, realName from user  (BaseJdbcLogger.java:143)
    [2019-12-20 11:19:02,606][DEBUG][http-nio-8095-exec-1] ==> Parameters:  (BaseJdbcLogger.java:143)
    [2019-12-20 11:19:02,631][DEBUG][http-nio-8095-exec-1] <==      Total: 4 (BaseJdbcLogger.java:143)
    [2019-12-20 11:19:02,634][INFO ][http-nio-8095-exec-1] around (LogAspect.java:77)
    [2019-12-20 11:19:02,635][INFO ][http-nio-8095-exec-1] after commit (LogAspect.java:60)
    [2019-12-20 11:19:02,635][INFO ][http-nio-8095-exec-1] RESPONSE : com.anson.common.result.ResultBody@6d9947d (LogAspect.java:55)
    如果不用注解的话,上面就已经完成一个切面了,如果用注解来定义切面范围呢,好,也简单,我们来定义一个注解

    --------------华丽丽的分割线-------------------------------

    --------------增加自定义注解的方式----------------------------

    3、添加一个LogAnnotation注解

    
    
    package com.anson.common.annotation;
    
    import java.lang.annotation.*;
    
    /**
     * @description: 自定义注解
     * @author: anson
     * @Date: 2019/12/20 10:32
     */
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface LogAnnotation 
    { }

    这样就可以了,然后,在需要的地方,加入这个自定义注解:

        //2、获取所有用户
        @ApiOperation(value = "获取所有用户", notes = "获取所有用户")
        @RequestMapping(value="/userall",method= RequestMethod.GET)
        @LogAnnotation //自定义注解
        public ResultBody getUserAll()
        {
            List<User> users = userservice.getAll();
            return  ResultBody.success(users,"获取所有用户信息成功");
        }

    同时,修改切面范围的定义即可:

    //2、定义切入点(可以匹配、注解的方式,可混用)--以下表示范围为:controller包下所有包含@LogAnnotation注解的方法
     @Pointcut("execution(public * com.anson.controller.*.*(..)) && @annotation(com.anson.common.annotation.LogAnnotation)") 

    public void pointcut(){}

    完了,就这么简单;

    至于什么地方该使用AOP,以及AOP和拦截器用哪个比较好,这个就要根据业务场景灵活取舍了,掌握了思想,具体使用那就可以灵活发挥了



  • 相关阅读:
    (五)Redis在项目中应用
    股票收益最大问题
    (四)redigo
    (三)go-sql-driver
    为什么TCP要3次握手?4次挥手?
    分支预测
    事务隔离级别说明
    剑指offer解题思路锦集11-20题
    C++中的二义性问题
    memcached Vs redis
  • 原文地址:https://www.cnblogs.com/yanghj/p/12072516.html
Copyright © 2011-2022 走看看