zoukankan      html  css  js  c++  java
  • SpringAop应用

    1. 引言

      为什么要使用Aop?贴一下较为官方的术语:

      在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方 式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个 热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑 的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高 了开发的效率。

      笼统总结一下,AOP 在不修改源代码的情况下给程序动态统一添加功能。  这样就能够在一个项目及时要在中途需要这么一个功能,那也就只需修改配置文件和加一个类,而没有该已经写好的类的代码。aop明显增加了代码的复用性,也省去了重新测试的时间。

    2.   Aop相关概念

      照例,先给出一个官方标准解释

    • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
    • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
    • Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
    • Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
    • Target(目标对象):织入 Advice 的目标对象.。
    • Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
      最近有个需求,需要统计用户调用接口的持续时间,分析时间较长的接口然后对其进行优化。代码逻辑就是在每个接口前后记录一下时间,入库。此时,但又不可能在每个接口前后都写这样的代码,这是,想到了代理,在每次调用接口时对其前后记录时间。静态代理肯定不行,这和在每个接口前后写没有区别。那就用动态代理时间实现,动态代理不就是AOP嘛。这样就只用写一个统计类了。这个类就相当于一个切面(Aspect)。代码里我到底要做什么?比如说在接口执行前需记录时间(前置增强),接口执行后需执行时间(后置增强)。那我就写几个方法,这几个方法就是Advice(增强)。要做什么写好了,那到底在哪运行?就在Pointcut中指定要在哪运行,这就是Aop中的切点。
      

      如图,3条竖立的红线框代表切面,与业务线相交的地方代表切点,红线框中的a(),b()...这些方法代表通知。----------

    3. Aop统计各个接口运行时长

      理清思路,代码编写其实很简单。下面贴一下我自用的统计接口时长的代码。

    package com.boot.beanConfig;
    
    
    import com.boot.mapper.UserMessageLogMapper;
    import com.boot.pojo.UserMessageLog;
    import com.boot.service.LoggerService;
    import com.boot.service.UserService;
    import com.boot.util.JwtParseUtil;
    import com.boot.util.MessageException;
    import org.apache.log4j.Logger;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * @Author  xiabing5
     * @Create  2019/8/13 10:31
     * @Desc    日志记录切面类
     **/
    @Component
    @Aspect
    public class AopLoggerAspect {
    
        private static final Logger logger = Logger.getLogger(AopLoggerAspect.class);
    
        @Autowired
        private LoggerService loggerService;
    
        // 定义切面的匹配规则
        @Pointcut("execution(* com.boot.service.impl.*(..)) 
                
        public void pointCut() {
    
        }
    
        @Before("pointCut()")
        public void doBefore(JoinPoint joinPoint) {
    
        }
    
        @After("pointCut()")
        public void doAfter(JoinPoint joinPoint) {
    
        }
    
        @AfterReturning(pointcut = "pointCut()",returning = "result")
        public void afterReturn(JoinPoint joinPoint, Object result) {
    
        }
    
        @AfterThrowing(pointcut = "pointCut()",throwing = "throwable")
        public void afterThrowing(JoinPoint joinPoint, Throwable throwable) {
    
        }
    
        // 环绕增强
        @Around("pointCut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            HttpServletRequest httpServletRequest = getHttpServletRequest();
            String token = httpServletRequest.getHeader("token");
            if(token == null) {
                Object result = joinPoint.proceed();
                return result;
            }
            String methodName = joinPoint.getSignature().getName();
           
            Long startTimeMills = System.currentTimeMillis();
            Object result = joinPoint.proceed(); // 调用proceed()方法会真正的执行实际被代理的方法
            Long endTimeMills = System.currentTimeMillis();
            Long runTime = endTimeMills-startTimeMills;
            
            // 插入操作记录
            try{
               // loggerService.insertUserMessageLog(userMessageLog);
            }
            catch (Exception e) {
                e.printStackTrace();
                logger.error("Method Name : [" + methodName + "] ---> ERROR");
            }
            return result;
        }
    
    }
    Aop统计接口调用时长

      在实现Aop时,有个小坑。我的接口都添加了事物,对于查找的接口添加的是只读事物,但使用Aop统计接口时长时,代理执行接口后,需要将记录写入到数据库。这样就会报错,因为只读事物不能写呀!!!所以,我在记录日志的接口上修改了事物的传播特性。因为我事物传播特性默认是PROPAGATION_REQUIRED,即有事物就加入此事物。所以我的写操作加入到了只读事物---- 修改内容见下。不明白我说的也没关系---忽略我即可----

    @Transactional(propagation= Propagation.NOT_SUPPORTED)
        public void insertUserMessageLog(UserMessageLog userMessageLog) {
            userMessageLogMapper.insert(userMessageLog);
        }
    修改插入操作的事物传播属性

    4. 总结

      Aop能很大程度降低代码耦合度!在实现记录日志时,发现了一个问题,即aop不能记录方法内的方法。因为Aop本质上就相当于是个代理----------好了,秒懂。

      




      

  • 相关阅读:
    JAVA NIO之文件通道
    Java NIO之缓冲区
    LinkedList 源码分析(JDK 1.8)
    ArrayList 源码详细分析
    自己动手实现一个简单的JSON解析器
    科普:String hashCode 方法为什么选择数字31作为乘子
    LinkedHashMap 源码详细分析(JDK1.8)
    HashMap 源码详细分析(JDK1.8)
    python创建目录
    python3 内置方法 字符串转换为字典
  • 原文地址:https://www.cnblogs.com/xiaobingblog/p/11424675.html
Copyright © 2011-2022 走看看