Spring AOP – 注解实现统计service中方法的执行时间

Spring AOP - 注解实现统计service中方法的执行时间

一、概述

Spring 2.0引入了更简单和更强大的方式来实现AOP——基于schema的方式和基于@AspectJ的方式。
本文介绍如何使用Spring AOP提供的@AspectJ注解注入切面,来统计service中方法的执行时间。

二、AOP概念

先简单介绍下AOP的主要概念和术语。

  • Aspect: 切面,即横切多个类的关注点的组合。事务管理即为横切关注点的一个很好的示例。Sprint AOP中,切面由一般类实现(基于schema的方式),或者由带有@Aspect注解的一般类(注解方式)来实现。
  • Join point: 接入点,指程序执行中的某个点,例如一个方法的执行,或者异常的处理。Spring AOP中,一个接入点通常指一个方法的执行。
  • Advice: 切入点在一个特殊接入点上执行的动作。有很多类型的advice,如around, before, after。Spring AOP中将advice实现为拦截器,管理围绕接入点的一串拦截器。
  • Pointcut:匹配接入点的谓词。每个advice都关联到一个pointcut表达式,并且在匹配接入点时(例如,某个特定名字方法的执行时)运行。Spring默认使用AspectJ的pointcut表达式语言。
  • target object: 被一个或多个切面advice的对象。由于Spring AOP通过运行时代理来实现,因此此处的对象总是指被代理对象。
  • AOP proxy:AOP为实现切面而创建的对象。Spring中一个AOP代理可能是JDK动态代理,也可以是CGLIB代理。
  • Weaving: 织入,链接切面和其他应用类型或对象,以创建一个被advice的对象。可以在编译时(例如使用AspectJ编译器)、加载时或者运行时执行。Spring AOP在运行时织入。

advice类型:

  • Before : 在接入点执行,一般不会阻止接入点的运行(除非抛出异常)
  • after returning: 在接入点正常终止时执行,例如在一个方法无任何异常返回后执行。
  • after throwing: 方法抛出异常时执行
  • after(finally): 不论接入点正常返回还是异常返回。
  • around:环绕一个接入点,如一个方法调用。最强大的advice。可以在方法调用之前和之后进行一些定制化行为。也可以控制是否执行接入点,或者干脆返回自己的结果,甚至抛出一个异常。本文即使用around advice实现在方法执行前开始秒表计时,在方法执行后关闭秒表,并统计方法运行时间。

三、@AspectJ注解

使用此注解结合普通java类即可实现声明式切面编程。@AspectJ注解由AspectJ项目在AspectJ 5时引入。Spring中的同名注解使用AspectJ提供的库来实现pointcut的解析和匹配,但是Spring AOP运行时仍然是纯Spring AOP实现,无需依赖AspectJ编译器或者织入器。

四、Junit测试时,使用@AspectJ统计调用的所有service方法执行时间

1. 开启@AspectJ支持

方法1: 纯java配置

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
   

}

方法2:XML配置(也是本文中使用的配置)

<aop:aspectj-autoproxy proxy-target-class="true" />

2. 声明和定义切面

xml中声明bean:

    <beans:bean id="logServiceMethodTimeAspectBean" class="com.test.aop.LogServiceMethodTimeAspect">
    </beans:bean>

定义java类:

@Aspect
public class LogServiceMethodTimeAspect {
...
}

3. 声明一个pointcut

    @Pointcut("execution(* com.test.service.impl.*.*(..))")
    private void serviceMethod() {
    }

4. 声明advice

    @Around("com.test.aop.LogServiceMethodTimeAspect.serviceMethod()")
    public Object logMethodRunningTime(ProceedingJoinPoint pjp)
            throws Throwable {
 ...
    }

5. 在Junit测试类上引入定义bean的文件:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { “classpath*:/spring/webmvc-config.xml”,
“classpath*:/test-config.xml” })

6. 完整的代码:

src/test/resources/test-config.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
    <aop:aspectj-autoproxy proxy-target-class="true" />


    <beans:bean id="logServiceMethodTimeAspectBean" class="com.test.aop.LogServiceMethodTimeAspect">
    </beans:bean>

</beans:beans>

com.test.aop.LogServiceMethodTimeAspect文件:


/** * @Title: LogServiceMethodTimeAspect.java * @Package com.test.aop * @Description: 使用AOP统计service方法运行的时间 * @author LIUYUEFENG559 * @date 2016年9月7日 下午4:22:23 * @version V1.0 */
package com.test.aop;

import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

/** * @ClassName: LogServiceMethodTimeAspect * @Description: 使用AOP统计service方法运行的时间 * @author LIUYUEFENG559 * @date 2016年9月7日 下午4:22:23 */
@Aspect
public class LogServiceMethodTimeAspect {
   

    private static final ConcurrentHashMap<String, Long> methodTimeMap = new ConcurrentHashMap<>();

    /** * @return methodtimemap */
    public static ConcurrentHashMap<String, Long> getMethodtimemap() {
        return methodTimeMap;
    }

    @Pointcut("execution(* com.test.service.impl.*.*(..))")
    private void serviceMethod() {
    }

    @Around("com.test.aop.LogServiceMethodTimeAspect.serviceMethod()")
    public Object logMethodRunningTime(ProceedingJoinPoint pjp)
            throws Throwable {
        // start stopwatch
        StopWatch watch = new StopWatch();
        watch.start();
        Object retVal = pjp.proceed();
        // stop stopwatch
        watch.stop();
        Long time = watch.getTime();
        String methodName = pjp.getSignature().getName();
        methodTimeMap.putIfAbsent(methodName, time);
        // map大小大于1000后清空掉。这么做是担心有人将本类放到生产环境,造成内存泄露
        if (methodTimeMap.size() >= 1000) {
            methodTimeMap.clear();
        }

        return retVal;
    }
}

测试类文件:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:/spring/webmvc-config.xml",
        "classpath*:/test-config.xml" })
public class BaseJunit4Test {
   

    @Resource
    private DataSource dataSource;

    /** * @Title: printServiceMethodsTime * @Description: 此方法在每个测试方法结束后打印其调用的所有service方法的执行时间 * @throws */
    @After
    public void printServiceMethodsTime() {
        ConcurrentHashMap<String, Long> methodTimeMap = LogServiceMethodTimeAspect
                .getMethodtimemap();
        Set<String> methodNameSet = methodTimeMap.keySet();
        System.out
                .println("----------下面列出调用的service方法的执行时间start--------------");
        for (String method : methodNameSet) {
            System.out.println("方法 " + method + "执行时间为:"
                    + methodTimeMap.get(method) + "毫秒。");
        }
        System.out.println("----------下面列出调用的service方法的执行时间end--------------");
    }
}

五、参考链接

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-ataspectj

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
spring
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论