IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> AOP面向切面编程之全局日志打印/统计接口耗时 -> 正文阅读

[Java知识库]AOP面向切面编程之全局日志打印/统计接口耗时

目录

一、什么是AOP

二、AOP使用场景

三、使用AOP的好处

四、先举个例子理解AOP面向切面编程

五、Spring5.X的AOP切入点表达式有这些种写法

六、实战基于Spring的AOP快速实现通用日志打印

七、实战基于Spring的AOP快速实现统计接口耗时


本篇重点介绍了AOP面向切面编程,同时在实际项目中使用自定义注解后置通知和环绕通知方式分别用来实现日志统一收集和接口耗时统计功能。

一、什么是AOP

Aspect Oriented Program 面向切面编程,再通俗点说就是在不改变原有逻辑上增加额外的功能,比如解决系统层面的问题,或者增加新的功能


二、AOP使用场景

权限控制、缓存、日志处理、事务控制、接口统计耗时
AOP思想把功能分两个部分,分离系统中的各种关注点
?? ?核心关注点
?? ??? ?业务的主要功能。就比如订单模块中的下单操作,属于业务的主要功能
?? ?横切关注点
?? ??? ?非核心、额外增加的功能。就比如订单模块中下单操作之前的权限校验以及事务控制或者防重提交校验等


三、使用AOP的好处

????减少代码侵入,解耦
?? ?可以统一处理横切逻辑
?? ?方便添加和删除横切逻辑


四、先举个例子理解AOP面向切面编程

用户下单逻辑中
?? ?核心关注点:创建订单
?? ?横切关注点:记录日志、控制事务、权限校验

VideoOrderService{
    //新增订单
    addOrder(){ }
    //查询订单
   findOrderById(){}
   //删除订单
   delOrder(){}
   //更新订单
   updateOrder(){}   
}
JointPoint连接点:addOrder/findOrderById/delOrder/updateOrder
Pointcut切入点:过滤出那些JointPoint哪些目标函数进行切入,比如说记录日志,通常增删改
才需要记录日志,查询的日志可以忽略
Advice通知:在切入点中的函数上执行的动作,比如记录日志,权限校验的话,就在对应的方法上加上注解。
Aspect切面:由切入点和通知组合而成,定义通知应用到哪些切入点
Weaving织入:把切面的代码,应用到目标函数的过程

核心概念:

通知 Advice
?? ?在特定的切入点上执行的增强处理,有5种通知
?? ?@Before前置通知
?? ??? ?在执行目标方法之前运行
?? ?@After后置通知
?? ??? ?在目标方法运行结束之后
?? ?@AfterReturning返回通知
?? ??? ?在目标方法正常返回值后运行
?? ?@AfterThrowing异常通知
?? ??? ?在目标方法出现异常后运行
?? ?@Around环绕通知
?? ??? ?在目标方法完成前、后做增强处理 ,环绕通知是最重要的通知类型 ,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint,需要手动执行 joinPoint.procced()
?? ?做啥? 比如你需要记录日志,控制事务 ,提前编写好通用的模块,需要的地方直接调用
连接点 JointPoint
?? ?要用通知的地方,业务流程在运行过程中需要插入切面的具体位置,
?? ?一般是方法的调用前后,全部方法都可以是连接点
?? ?只是概念,没啥特殊
切入点 Pointcut
?? ?不能全部方法都是连接点,通过特定的规则来筛选连接点, 就是Pointcut,选中那几个你想要的方法
?? ?在程序中主要体现为书写切入点表达式(通过通配、正则表达式)过滤出特定的一组 JointPoint连接点
?? ?过滤出相应的 Advice 将要发生的joinpoint地方
切面 Aspect
?? ?通常是一个类,里面定义 切入点+通知 , 定义在什么地方; 什么时间点、做什么事情
?? ?通知 advice指明了时间和做的事情(前置、后置等)
?? ?切入点 pointcut 指定在什么地方干这个事情
?? ?web接口设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面,对象和对象,方法和方法之间都是一个个切面
目标 target
?? ?目标类,真正的业务逻辑,可以在目标类不知情的条件下,增加新的功能到目标类的链路上
织入 Weaving
?? ?把切面(某个类)应用到目标函数的过程称为织入
AOP代理
?? ?AOP框架创建的对象,代理就是目标对象的加强
?? ?Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理

?

下单权限校验/日志业务流程伪代码例子:

//目标类 VideoOrderService; 里面每个方法都是连接点,;切入点是CUD类型的方法,R读取的不作为切入点
//CRDU全称:增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)

VideoOrderService{
    //新增订单
    addOrder(){ }
    //查询订单
   findOrderById(){}
   //删除订单
   delOrder(){}
   //更新订单
   updateOrder(){}   
}


//权限切面类 = 切入点+通知 
PermissionAspects{
  
  //切入点  定义了什么地方
    @Pointcut("execution(public int net.wnn.permission.service.VideoOrderService.*(..))")
  public void pointCut(){}
  
  
  //before 通知 表示在目标方法执行前切入, 并指定在哪个方法前切入
  //什么时候,做什么事情
  @Before("pointCut()")
  public void permissionCheck(){
    
    System.out.println("在 xxx 之前执行权限校验");
  }
  ....
}

//日志切面类 = 切入点+通知 
LogAspect{
  
  //切入点  定义了什么地方
    @Pointcut("execution(public int net.wnn.permission.service.VideoOrderService.*(..))")
  public void pointCut(){}
  
  
  //after 通知 表示在目标方法执行后切入, 并指定在哪个方法前切入
  //什么时候,做什么事情
  @After("pointCut()")
  public void logStart(){
    
    System.out.println("在 xxx 之后记录日志");
  }
  ....
}

五、Spring5.X的AOP切入点表达式有这些种写法

切入点表示式,除了返回类型、方法名和参数外,其它项都是可选的 (修饰符基本都是省略不写)
? ? ? ? ? ? ? ? ?访问修饰符? ? ? ? ? ?返回值类型(必填)? ? ? 包和类? ? ? ? ? ? ? ? ? ? ?方法(必填)
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) ?throws-pattern?)?
比如上面的:
@Pointcut("execution(public int net.wnn.permission.service.VideoOrderService.*(..))")
其中:*:匹配任何数量字符 单个VideoOrderService.*表示VideoOrderService下任意方法
..:?? ?() 匹配一个不接受任何参数的方法
?? ?(..) 匹配一个接受任意数量参数的方法
?? ?(*) 匹配了一个接受一个任何类型的参数的方法
?? ?(*,Integer) 匹配了一个接受两个参数的方法,其中第一个参数是任意类型,第二个参数必须是Integer类型?

常见的例子:
任意公共方法
execution(public * *(..))

任何一个名字以“save”开始的方法
execution(* save*(..))

VideoService接口定义的任意方法(识别)
execution(* net.wnn.service.VideoService.*(..))

在service包中定义的任意方法(识别)
execution(* net.wnn.service.*.*(..))

匹配 service 包,子孙包下所有类的所有方法(识别)
execution(* net.wnn.service..*.*(..))

六、实战基于Spring的AOP快速实现通用日志打印

第一步开启SpringAOP注解配置


@Configuration
@ComponentScan("net.wnn")
@EnableAspectJAutoProxy  //开启了spring对aspect的支持
public class AnnotationAppConfig {

}

第二步

配置切入点和通知。自定义注解类型配置

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OpLog {

    /**
     *
     * 操作模块
     */
    String opModule();

    /**
     *
     * 操作类型
     */
    String operType();

    /**
     *
     * 操作描述
     */
    String operDesc();
}
import lombok.extern.slf4j.Slf4j;
import net.wnn.model.OpLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

//让spring进行扫描 一定要加
@Component
//告诉spring,这个一个切面类,里面可以定义切入点和通知
@Aspect
@Slf4j
public class LogAdvice {

    /**
     * 首先定义一个切点
     */
    @Pointcut("@annotation(net.wnn.model.OpLog)")
    public void printLog() {
    }

    @After("printLog()")
    public void after(JoinPoint joinPoint){
        try {
            //获取方法名称
            String methodName = joinPoint.getSignature().getName();
            //获取类名称
            String className = joinPoint.getSignature().getDeclaringTypeName();
            System.out.println("类名:"+className+" 方法名:"+methodName);
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature =(MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method =signature.getMethod();
            // 获取操作
            OpLog opLog =method.getAnnotation(OpLog.class);
            log.info("模块名称:[{}],类型:[{}],描述信息:[{}]",opLog.opModule(),opLog.operType(),opLog.operDesc());
            //执行连接点的方法
            try {
                ((ProceedingJoinPoint)joinPoint).proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();

        }
    }

}

第三步在请求方法上增加注解?

  /**
     * 用户登录
     * @param request
     * @return
     */
    @PostMapping("login")
    @OpLog(opModule = "用户模块",operType = "登录操作",operDesc = "此方法用户用户登录")
    public JsonData login(@RequestBody AccountLoginRequest request){
        JsonData jsonData = accountService.login(request);
        return jsonData;
    }

postman请求接口验证:

?日常开发工作中,可以根据实际日志存储需求,将统一收集到的需求进行入库等操作。

七、实战基于Spring的AOP快速实现统计接口耗时

第一步开启SpringAOP注解配置


@Configuration
@ComponentScan("net.wnn")
@EnableAspectJAutoProxy  //开启了spring对aspect的支持
public class AnnotationAppConfig {

}

第二步配置切入点

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;


//让spring进行扫描 一定要加
@Component
//告诉spring,这个一个切面类,里面可以定义切入点和通知
@Aspect
@Slf4j
public class LogAdvice {

    //切入点表达式,也可以直接在通知上编写切入点表达式
    @Pointcut("execution(* net.wnn.service.impl.AccountServiceImpl.*(..))")
    public void aspect(){

    }

    @Around("aspect()")
    public void around(JoinPoint joinPoint){
        Object target = joinPoint.getTarget().getClass().getName();
        //通过joinPoint获取参数
        Object [] args = joinPoint.getArgs();
        log.info("调用者:{} 调用方法:{} 调用参数:{}",target,joinPoint.getSignature(),args[0]);

        long start = System.currentTimeMillis();
        log.info("===========环绕通知 环绕前========");
        //执行连接点的方法
        try {
            ((ProceedingJoinPoint)joinPoint).proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        long end = System.currentTimeMillis();
        log.info("===========环绕通知 环绕后========");

        log.info("调用方法总耗时 time = " + (end - start) +" ms");
    }


}

控制台输出:?

实际开发工作中,可根据接收到的方法 以及耗时进行入库统计展示。?

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-01-29 22:56:58  更:2022-01-29 22:57:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:21:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码