当前位置:   article > 正文

JAVA如何优雅的计算函数执行时间_java 如何优雅的打印执行时间

java 如何优雅的打印执行时间

优雅的实现 PK 传统的实现

优雅实现

加一个注解即可,如@TimeIt 

  1. @TimeIt
  2. public ReturnJson getNavigationConfigEntity() {
  3. ...}

传统实现
  1. public ReturnJson getNavigationConfigEntity() {
  2. long startTime = System.currentTimeMillis();
  3. ...
  4. log.info("getNavigationConfigEntity costs: {}", System.currentTimeMillis() - startTime);
  5. }

优雅的实现,不入侵原有函数实现,且代码量小。

如何做出计时效果的注解?

核心技术:AspectJ 和 AOP

步骤0,定义一个注解TimeIt
  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface TimeIt {
  4. }

步骤1,定义切面
  1. @Aspect
  2. @Component //不要用configuration, 会导致bean启动太晚,出现 Advice must be declared inside an aspect type的错误
  3. @Slf4j
  4. public class AspectAnnotation {
  5. @Around("@annotation(com.pingan.pivot.manage.annotation.TimeIt)")
  6. public Object recordPerformance(ProceedingJoinPoint pjp) throws Throwable {
  7. StopWatch stopWatch = new StopWatch();
  8. stopWatch.start();
  9. Object ret = pjp.proceed(pjp.getArgs());
  10. stopWatch.stop();
  11. log.info("{} costs: {} ms", ((MethodSignature)pjp.getSignature()).getMethod().getName(), stopWatch.getLastTaskInfo().getTimeMillis());
  12. return ret;
  13. }
  14. }

@Around 属于aspect内的advice,能力很强,可以在原函数前面或后面添加其他功能,也可以修改入参和返回结果。

下面这句就是原函数的执行。

Object ret = pjp.proceed(pjp.getArgs())

这里,在原函数之前加入计时,函数之后停止计时,即可起到计算原函数执行时间的效果。

@Around内的描述,就是将这个切面和注解TimeIt进行关联,凡是被TimeIt注解的函数,都会执行这个方法而实现计时功能。

要打印函数运行时间,就必须显示是函数名,如何做到一个通用的功能,注解在哪个函数上面,就显示那个函数的名字?

采用下面的方式可以获取函数签名从而显示函数名。

((MethodSignature)pjp.getSignature()).getMethod().getName()

注意:

这个类的要使用 @Component 而不是 @Configuration (否则会出现错误)

因为spring boot的启动中,会故意延迟Configuration注解的Bean的启动时机。

拓展知识

微服务主函数的使用的注解 @SpringBootApplication的定义里有

@ComponentScan(

excludeFilters = {@Filter(

type = FilterType.CUSTOM,

classes = {TypeExcludeFilter.class}

), @Filter(

type = FilterType.CUSTOM,

classes = {AutoConfigurationExcludeFilter.class}

)}

)

从这个描述可知,配置类的注解会故意不scan,等其他bean完成后,再进行Bean创建

步骤2,引入aspectj-maven-plugin 实现静态代理

通过引入aspectj-maven-plugin,让mvn编译时,加入AspectJ的静态编织能力,从而实现在原函数外面加上静态proxy。

  1. <plugin>
  2. <groupId>org.codehaus.mojo</groupId>
  3. <artifactId>aspectj-maven-plugin</artifactId>
  4. <version>1.14.0</version>
  5. <configuration>
  6. <complianceLevel>1.8</complianceLevel>
  7. <source>1.8</source>
  8. <target>1.8</target>
  9. <showWeaveInfo>true</showWeaveInfo>
  10. <verbose>true</verbose>
  11. <Xlint>ignore</Xlint>
  12. <encoding>UTF-8 </encoding>
  13. <forceAjcCompile>true</forceAjcCompile>
  14. <sources/>
  15. <weaveDirectories>
  16. <weaveDirectory>${project.build.directory}/classes</weaveDirectory>
  17. </weaveDirectories>
  18. </configuration>
  19. <executions>
  20. <execution>
  21. <goals>
  22. <!-- use this goal to weave all your main classes -->
  23. <goal>compile</goal>
  24. </goals>
  25. </execution>
  26. </executions>
  27. </plugin>

这里特别关键的是:

  1. <sources/>
  2. <weaveDirectories>
  3. <weaveDirectory>${project.build.directory}/classes</weaveDirectory>
  4. </weaveDirectories>

如果不添加这个,会出现无法找到各种get和set函数的问题。

为什么会有这个问题?

因为get和set目前基本都由lombok在编译阶段自动生成。

aspectj和lombok都属于编译阶段引入的静态代理。

必须要让aspectj在lombok完成之后再进行静态代理。

因此,将weaveDirectory设置为${project.build.directory}/classes,这样,aspectJ就会去lombok生成后的.class文件里去作用,从而实现先lombok后aspectj的工作顺序。

(${project.build.directory} 是系统变量,直接写即可,无需额外定义)

另外,<sources/> 等价于 <sources></sources>,即不使用任何java代码

如何不使用注解,批量的让函数打印执行时间?

上面注解的方式,非常方便的精确打印具体的函数。

但是,如果需要打印的函数数量很多,且具有聚集效应,如都在一个类或包里等,每个函数写一个注解是低效的,可以采用如下方式实现批量动作。

  1. @Aspect
  2. @Component //不要用configuration, 会导致bean启动太晚,出现 Advice must be declared inside an aspect type的错误
  3. @Slf4j
  4. public class AspectAnnotation {
  5. @Pointcut("execution(* com.pingan.pivot.manage.service.impl.NavigationRobotServiceImpl.*(..))")
  6. public void logRunTime(){
  7. }
  8. //@Around("@annotation(com.pingan.pivot.manage.annotation.TimeIt)")
  9. @Around("logRunTime()")
  10. public Object recordPerformance(ProceedingJoinPoint pjp) throws Throwable {
  11. StopWatch stopWatch = new StopWatch();
  12. stopWatch.start();
  13. Object ret = pjp.proceed(pjp.getArgs());
  14. stopWatch.stop();
  15. log.info("{} costs: {} ms", ((MethodSignature)pjp.getSignature()).getMethod().getName(), stopWatch.getLastTaskInfo().getTimeMillis());
  16. return ret;
  17. }
  18. }

制作一个切点Pointcut,作用范围为:

NavigationRobotServiceImpl 这个类下的任何方法

其中,execution右边的*代表任意返回值的函数,最右边的(..)代表任意入参,2个结合在一起,就是不根据入参和返回值进行过滤。

将这个Pointcut和切面函数关联在一起,即可让这个类的所有方法都具备计时打印的功能。

背景文档:

Introduction to Spring AOP | Baeldung

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/823415
推荐阅读
相关标签
  

闽ICP备14008679号