1、AOP简介:

AOP为开发者提供一种进行横切关注点分离并织入的机制,把横切关注点分离,然后通过某种技术织入到系统中,从而无耦合的完成了我们的功能。

使用楔子,完成方法前后的自定义功能

2、AOP学习目的:

  • SpringBoot使用AOP统一处理:Web请求日志
  • SpringBoot使用AOP统一处理:事务处理
  • SpringBoot使用AOP统一处理:用户登录拦截
  • SpringBoot使用AOP统一处理:请求幂等级(防止重复提交)
  • SpringBoot使用AOP统一处理:统计方法执行时长

3、参考网址:

4、核心代码讲解

4.1 使用AOP进行日志的拦截
  • 进行日志AOP的编写:先定义一个切入点,然后编写这个切入点的前置、后置等方法,进行日志相关的记录,当前的切入点是所有的controller类的所有方法
@Aspect
@Component
public class LogWebAspect {
    /**
     * 指定切点
     * 匹配 com.enzoism.controller包及其子包下的所有类的所有方法
     */
    @Pointcut("execution(public * com.enzoism.controller.*.*(..))")
    public void webLog(){
    }

    /**
     * 前置通知,方法调用前被调用
     * @param joinPoint
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint){
        System.out.println("我是前置通知!!!");
        //获取目标方法的参数信息
        Object[] obj = joinPoint.getArgs();
        System.out.println("请求参数:"+obj);
        // 代理
        Signature signature = joinPoint.getSignature();
        //代理的是哪一个方法
        System.out.println("方法:"+signature.getName());
        //AOP代理类的名字
        System.out.println("方法所在包:"+signature.getDeclaringTypeName());
        //AOP代理类的类(class)信息
        signature.getDeclaringType();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] strings = methodSignature.getParameterNames();
        System.out.println("参数名:"+ Arrays.toString(strings));
        System.out.println("参数值ARGS : " + Arrays.toString(joinPoint.getArgs()));
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest req = attributes.getRequest();
        // 记录下请求内容
        System.out.println("请求URL : " + req.getRequestURL().toString());
        System.out.println("HTTP_METHOD : " + req.getMethod());
        System.out.println("IP : " + req.getRemoteAddr());
        System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());

    }

    /**
     * 处理完请求返回内容
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        System.out.println("方法的返回值 : " + ret);
    }

    /**
     * 后置异常通知
     * @param jp
     */
    @AfterThrowing("webLog()")
    public void throwss(JoinPoint jp){
        System.out.println("方法异常时执行.....");
    }

    /**
     * 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
     * @param jp
     */
    @After("webLog()")
    public void after(JoinPoint jp){
        System.out.println();
    }

    /**
     * 环绕通知,环绕增强,相当于MethodInterceptor
     * @param pjp
     * @return
     */
    @Around("webLog()")
    public Object arround(ProceedingJoinPoint pjp) {
        try {
            Object o =  pjp.proceed();
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}
  • 进行方法请求:任何的请求都会触发日志的拦截,所有我们可以自定义多个AOP,然后针对不对的业务,对不同的类和方法进行拦截,或者【结合annotation进行指定的标记位插入】



4.2 进行annotation的使用
  • 定义annotation:后面可以获取该注解的参数,进行日志相关的打印
@Aspect
@Component
public class LogWebAspect {
    //统计请求的处理时间
    ThreadLocal<Long> startTime = new ThreadLocal<>();
    /**
     * 指定切点
     * 匹配 com.enzoism.controller包及其子包下的所有类的所有方法
     */
    @Pointcut("execution(public * com.enzoism.controller.*.*(..))")
    public void webLog(){
    }

    /**
     * 前置通知,方法调用前被调用
     * @param joinPoint
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint){
        System.out.println("我是前置通知!!!");
        startTime.set(System.currentTimeMillis());
        //获取目标方法的参数信息
        Object[] obj = joinPoint.getArgs();
        System.out.println("请求参数:"+obj);
        // 代理
        Signature signature = joinPoint.getSignature();
        //代理的是哪一个方法
        System.out.println("方法:"+signature.getName());
        //AOP代理类的名字
        System.out.println("方法所在包:"+signature.getDeclaringTypeName());
        //AOP代理类的类(class)信息
        signature.getDeclaringType();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] strings = methodSignature.getParameterNames();
        System.out.println("参数名:"+ Arrays.toString(strings));
        System.out.println("参数值ARGS : " + Arrays.toString(joinPoint.getArgs()));
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest req = attributes.getRequest();
        // 记录下请求内容
        System.out.println("请求URL : " + req.getRequestURL().toString());
        System.out.println("HTTP_METHOD : " + req.getMethod());
        System.out.println("IP : " + req.getRemoteAddr());
        System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());

    }

    /**
     * 处理完请求返回内容
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        System.out.println("方法的返回值 : " + ret);
    }

    /**
     * 后置异常通知
     * @param jp
     */
    @AfterThrowing("webLog()")
    public void throwss(JoinPoint jp){
        System.out.println("方法异常时执行.....");
    }

    /**
     * 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
     * @param jp
     */
    @After("webLog()")
    public void after(JoinPoint jp){
        System.out.println("方法执行时间:"+ (System.currentTimeMillis() - startTime.get()));
        System.out.println();
    }

    /**
     * 环绕通知,环绕增强,相当于MethodInterceptor
     * @param pjp
     * @return
     */
    @Around("webLog()")
    public Object arround(ProceedingJoinPoint pjp) {
        try {
            Object o =  pjp.proceed();
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}

此时可以看出annotation的作用,可以进行自定义标记,然后在切面中获取该标记,进行后续相关操作,通过method.getAnnotation()获取指定的标记位即可,但是这个操作毕竟有点low,能不能使用AOP将Annotation直接对接起来


4.1 进行AOP将Annotation组合使用
@Aspect
@Component
public class LogAnnotationAspect {
    /**
     * 指定切点
     * 匹配 被添加LogAnnotation标注的方法
     */
    @Pointcut("@annotation(logAnnotation)")
    public void pointCut(LogAnnotation logAnnotation) {
    }

    /**
     * 环绕通知,环绕增强,相当于MethodInterceptor
     * @param pjp
     * @return
     */
    @Around("pointCut(logAnnotation)")
    public Object arround(ProceedingJoinPoint pjp, LogAnnotation logAnnotation) {
        try {
            Object o = pjp.proceed();
            System.out.println("---------------直接指定到logAnnotation位置");
            System.out.println("actionType:"+logAnnotation.actionType());
            System.out.println("controllerName:"+logAnnotation.controllerName());
            System.out.println("module:"+logAnnotation.module());
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}

5、由AOP发起的思考

  • 什么是代理?有几种方式,区别是什么?
  • ThreadLocal在多线程中的使用(并发编程)
  • ProceedingJoinPoint进行proceed()进程执行过程中加其他操作(锁判定/幂等级判定)
  • Springboot测试用例CountDownLatch和ExecutorService使用