proceedingjoinpoint 获取方法名_利用 springbootAOP + 注解实现方法执行时间监控

本文介绍如何利用 SpringBoot AOP 和自定义注解实现方法执行时间的监控。通过创建 WatchTimeAnnotation 注解,定义切入点,以及配置统一切入点管理,可以方便地监控并记录方法的执行时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 最近在优化一些项目接口,发现有一些执行时间超过 1S,就想着搞一个注解监控每个方法的执行时间。

前言

  • 以下内容摘自 https://www.tianmaying.com/tutorial/spring-aop

软件工程有一个基本原则叫做“关注点分离”(Concern Separation),通俗的理解就是不同的问题交给不同的部分去解决,每部分专注于解决自己的问题。这年头互联网也天天强调要专注嘛!

这其实也是一种“分治”或者“分类”的思想,人解决复杂问题的能力是有限的,所以为了控制复杂性,我们解决问题时通常都要对问题进行拆解,拆解的同时建立各部分之间的关系,各个击破之后整个问题也迎刃而解了。

面向切面编程(Aspect Oriented Programming,AOP)其实就是一种关注点分离的技术。我们软件开发时经常提一个词叫做“业务逻辑”或者“业务功能”,我们的代码主要就是实现某种特定的业务逻辑。但是我们往往不能专注于业务逻辑,比如我们写业务逻辑代码的同时,还要写事务管理、缓存、日志等等通用化的功能,而且每个业务功能都要和这些业务功能混在一起,痛苦!所以,为了将业务功能的关注点和通用化功能的关注点分离开来,就出现了 AOP 技术。这些通用化功能的代码实现,对应的就是我们说的切面(Aspect)。

业务功能代码和切面代码分开之后,责任明确,开发者就能各自专注解决问题了,代码可以优雅的组织了,设计更加高内聚低耦合了(终极目标啊!)。但是请注意,代码分开的同时,我们如何保证功能的完整性呢?你的业务功能依然需要有事务和日志等特性,即切面最终需要合并(专业术语叫做织入, Weave)到业务功能中。怎么做到呢?这里就涉及 AOP 的底层技术啦,有三种方式:

  • 编译时织入:在代码编译时,把切面代码融合进来,生成完整功能的 Java 字节码,这就需要特殊的 Java 编译器了,AspectJ 属于这一类
  • 类加载时织入:在 Java 字节码加载时,把切面的字节码融合进来,这就需要特殊的类加载器,AspectJ 和 AspectWerkz 实现了类加载时织入
  • 运行时织入:在运行时,通过动态代理的方式,调用切面代码增强业务功能,Spring 采用的正是这种方式。动态代理会有性能上的开销,但是好处就是不需要神马特殊的编译器和类加载器啦,按照写普通 Java 程序的方式来就行了!

更多的细节,不在这里展开了,网上资料一大堆。下面我们就直接上干货。

环境和依赖

环境

  • SpringBoot 2.3.2
  • IDEA
  • MAVEN
  • jdk1.8+

依赖


<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>

其中,starter-aop 中依赖了以下两个包:

<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.2.2.RELEASEversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.5version>
<scope>compilescope>
dependency>

aspectjweaver 中又包含了 aspectjrt.jar 的相关功能,实现编译时织入。

实现

自定义注解 WatchTimeAnnotation

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 可以根据自己的情况,进行增减属性
*
* @author hager
* @since 2020-8-25 22:59:18
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public@interface WatchTimeAnnotation {

/**
* 方法名
*/
String name() default "";

/**
* 描述内容
*/
String desc() default "";

/**
* 日志等级,数字越大,打印日志级别越高
* 最好,这里换成枚举
*/
int level() default 0;
}

切入点监控日志打印

import com.example.mybatisplusdemo.annoations.WatchTimeAnnotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

/**
* 这里可以根据自己的诉求,进行改造,比如什么时候打印什么样的日志等
*
* @author hager
* @since 2020-8-25 23:00:30
*/
@Aspect
@Component
publicclass WatchTimeAspect {
finalstatic Logger LOGGER = LoggerFactory.getLogger(WatchTimeAspect.class);

@Around("com.example.mybatisplusdemo.aspects.CommonJoinPointConfig.watchTimeAspectPointCutWithAnnos()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature methodSignature = (MethodSignature) point.getSignature();

// Get intercepted method details
String className = methodSignature.getDeclaringType().getSimpleName();
String methodName = methodSignature.getName();

String value = "";
String desc = "";
int level = 0;
WatchTimeAnnotation timeAnnotation = methodSignature.getMethod().getAnnotation(WatchTimeAnnotation.class);
if (null != timeAnnotation) {
value = timeAnnotation.name();
desc = timeAnnotation.desc();
level = timeAnnotation.level();
}

final StopWatch stopWatch = new StopWatch();

//Measure method execution time
stopWatch.start();
Object result = point.proceed();
stopWatch.stop();

//Log method execution time
if (level >= 5) {
LOGGER.warn("【执行时间-warning】 {}.{}(value={},desc={})::{} ms",
className, methodName,
value, desc,
stopWatch.getTotalTimeMillis());
} elseif (level > 0 && level < 5) {
LOGGER.info("【执行时间】 {}.{}(value={},desc={})::{} ms",
className, methodName,
value, desc,
stopWatch.getTotalTimeMillis());
} else {
LOGGER.info("不打印日志");
}

return result;
}
}

统一切入点管理配置

import org.aspectj.lang.annotation.Pointcut;

/**
* 统一管理注解切入点
*
* @author hager
* @since 2020-8-25 23:02:46
*/
publicclass CommonJoinPointConfig {

/**
* 切入点,注解形式
*/
@Pointcut("@annotation(com.example.mybatisplusdemo.annoations.WatchTimeAnnotation)")
public void watchTimeAspectPointCutWithAnnos() {
}

/**
* 切入点,执行类方法
*/
@Pointcut("execution(* com.example.mybatisplusdemo.service.*.*(..))")
public void watchTimeAspectPointCutWithExec() {
}
}

使用

在 service 实现方法上增加对应注解即可

@Override
@WatchTimeAnnotation(name = "selectListBySql", desc = "描述内容", level = 5)
public ListselectListBySql() {
return baseMapper.selectListBySql();
}

输出

2020-08-25 23:08:19.895  WARN 18388 --- [nio-8082-exec-2]
c.e.m.aspects.WatchTimeAspect
: 【执行时间-warning】 DemoUserServiceImpl.selectListBySql(value=selectListBySql,
desc=描述内容)::48 ms

总结

该篇笔记,通过使用 SpringBoot AOP 与注解的结合实现了执行方法的时间监控。通过灵活控制注解以及属性,进而控制日志的输出。了解相关技术点后,可以举一反三的应用于其他功能上。比如日志、参数拼接、鉴权等等场景。

扩展

关于 AOP 这块还是有很多重要的内容的,在这里作为扩展内容梳理一下。

0c8ad7d5b627513d16b91b7342c5cf36.png

  • 连接点(Joint Point):广义上来讲,方法、异常处理块、字段这些程序调用过程中可以抽像成一个执行步骤(或者说执行点)的单元。从 Spring AOP 来讲,就是指 java 的方法和异常处理代码块。

  • 切点(Pointcut):是连接点的描述定义,Spring AOP 通过切点来定位到哪些连接点。切点表达式语言就是切点用来定义连接点的语法。有以下 9 种切点指示符:execution、within、this、target、args、@target、@args、@within、@annotation。注意:private 修饰的无法拦截。

  • @Aspect,作用是把当前类标识为一个切面供容器读取

  • @Before 前置通知,在调用目标方法之前执行通知定义的任务。相当于 BeforeAdvice 的功能。

  • @After 后置通知,在目标方法执行结束后,无论执行结果如何都执行通知定义的任务,final 增强,不管是抛出异常或者正常退出都会执行。

  • @Around 环绕通知,和前两个写法不同,实现的效果包含了前置和后置通知。相当于 MethodInterceptor

    • 当使用环绕通知时,proceed 方法必须调用,否则拦截到的方法就不会再执行了
    • 环绕通知=前置+目标方法执行+后置通知,proceed 方法就是用于启动目标方法执行的
    • 常用于 环绕通知可以项目做全局异常处理、日志记录、用来做数据全局缓存、全局的事物处理 等
  • @AfterReturning,切入点返回结果之后执行,也就是都前置后置环绕都执行完了,这个就执行了。相当于 AfterReturningAdvice,方法退出时执行。

    • 应用场景可以用来在订单支付完成之后就行二次的结果验证,重要参数的二次校验,防止在方法执行中的时候参数被修改等等
  • @AfterThrowing,这个是在切入执行报错的时候执行的。相当于 ThrowsAdvice

资料

  • https://howtodoinjava.com/spring-boot2/aop-aspectj/
  • https://docs.spring.io/spring/docs/2.0.x/reference/aop.html
  • https://www.baeldung.com/spring-aop-pointcut-tutorial
  • 扩展资料 https://www.jianshu.com/p/d32b17d079ef

文中部分图片来自网上,如有侵权,请联系删除。

ea5f44294bd6aeb427df7475eb5d24b0.png

7cc4eae1f71f531dc8df7765a6103834.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值