AOP切面之打印方法时间

在看线程并发的书籍时看到ThreadLocal,利用线程变量打印方法执行时间,联想到可以用aop实现全局方法打印

下面先看单独使用ThreadLocal打印的方法

public class profiler {

    //第一次get()方法调用时会进行初始化(如果set方法没有调用),每个线程会调用一次
    private static final ThreadLocal<Long> TimeThreadLocal = new ThreadLocal<Long>() {
        protected Long initialValue() {
            return System.currentTimeMillis();
        }
    };

    public static final void begin() {
        TimeThreadLocal.set(System.currentTimeMillis());
    }

    public static final Long end() {
        return System.currentTimeMillis() - TimeThreadLocal.get();
    }

    public static void main(String[] args) throws InterruptedException {
        profiler.begin();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("cost:" + profiler.end() + "mills");
    }
}

结果:cost:1001mills

profile方法可以复用在调用耗时统计的功能上,方法入口前执行begin(),执行后调用end,但是这样需要给每个方法都加就显得比较笨拙了

我们基于aop的思想,可以在方法调用前后的切入点调用beign和end

下面上方法

@Aspect
@Component
public class AopProfiler {
    private static final ThreadLocal<Long> TimeThreadLocal = new ThreadLocal<Long>() {
        protected Long initialValue() {
            return System.currentTimeMillis();
        }
    };

    public static final void begin() {
        TimeThreadLocal.set(System.currentTimeMillis());
    }

    public static final Long end() {
        return System.currentTimeMillis() - TimeThreadLocal.get();
    }

    @Pointcut("execution(public * com.tuhu.threadlocaltest.BussinessTest.*(..))")
    public void  verify(){
    }
    @Before("verify()")
    public void doBefore(JoinPoint joinPoint){
        System.out.println("方法:"+joinPoint.getSignature().getName()+",参数:"+joinPoint.getArgs());
        AopProfiler.begin();
    }
    @After("verify()")
    public  void  doAfter(JoinPoint joinPoint){
        System.out.println("方法:"+joinPoint.getSignature().getName()+"--cost:" + AopProfiler.end() + "mills");
    }
}

这里简单带一下aop在springboot中是实现方法

首先加入依赖

1 <dependency>
2             <groupId>org.springframework.boot</groupId>
3             <artifactId>spring-boot-starter-aop</artifactId>
4  </dependency>

之后看下几个注解:@Aspect,@Before,@After,@Around,@Pointcut,@JoinPoint

 @Aspect:表示这是一个切面类

@Before:方法执行之前

@After:方法执行之后

@Around:方法执行中

@Pointcut;切点范围,表示哪些方法可以执行这个切面

@JoinPoint:执行方法的元数据

上面简单介绍了一下aop的实现,可以看到之前的例子的切点是BussinessTest中的所有方法,下面看下业务实现

 1 @RestController
 2 public class BussinessTest {
 3     @RequestMapping("test")
 4     public void test() throws InterruptedException {
 9         test1();
10         test2();
11         test3();
12         TimeUnit.SECONDS.sleep(1);
13     }
14 
15     public void test1() throws InterruptedException {
16         TimeUnit.SECONDS.sleep(1);
17     }
18 
19     public void test2() throws InterruptedException {
20         TimeUnit.SECONDS.sleep(2);
21     }
22 
23     public void test3() throws InterruptedException {
24         TimeUnit.SECONDS.sleep(3);
25     }
26 }

这个例子test方法中调用了test1,test2,test3,下面我们看下结果

方法:test,参数:[Ljava.lang.Object;@513a380
方法:test--cost:7009mills

嗯? 貌似和我们想的不一样, 为啥只打印了test,其他3个方法被吃了么

检查了切面类也没问题,test也可以打印,那就是那3个方法的问题了,这时候想下aop的原理:拦截器,动态代理

Spring 的代理实现有两种:一是基于 JDK Dynamic Proxy 技术而实现的;二是基于 CGLIB 技术而实现的。如果目标对象实现了接口,在默认情况下Spring会采用JDK的动态代理实现AOP

如果是当前类中调用,是this调用而不是代理调用,所以不会被切面拦截

知道了这个问题后就好办了,第一个最简单的就是把这三个方法放到一个新的类中去,这样就可以了

第二个:在当前类中获取代理类并调用

//第一步
//在启动类上加上该注解
@EnableAspectJAutoProxy(exposeProxy=true)

//第二步:获取代理类并使用代理类调用,这样就可以使用代理类的增强方法了
BussinessTest test = AopContext.currentProxy() != null ? (BussinessTest) AopContext.currentProxy() : this;
test.test1();
test.test2();
test.test3();

下面看下结果:

方法:test,参数:[Ljava.lang.Object;@23426d27
方法:test1,参数:[Ljava.lang.Object;@35eeb8f3
方法:test1--cost:1001mills
方法:test2,参数:[Ljava.lang.Object;@66c87655
方法:test2--cost:2000mills
方法:test3,参数:[Ljava.lang.Object;@179074d5
方法:test3--cost:3001mills
方法:test--cost:4001mills

ok,完美,四个方法都打印出来了!

最后再做一个有意思的例子,我把三个内部方法都写成private,这时候再运行发现打印不出来了,这就是aop的另一个需要注意的地方就是非public方法会跳过代理方法,下面看下源码

if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
   // We can skip creating a MethodInvocation: just invoke the target directly.
   // Note that the final invoker must be an InvokerInterceptor, so we know
   // it does nothing but a reflective operation on the target, and no hot
   // swapping or fancy proxying.
   Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
   retVal = methodProxy.invoke(target, argsToUse);
}
else {
   // We need to create a method invocation...
   retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}

这里可以发现判断了ispublic,如果不是就会直接创建一个方法,所以这时候AOP也不会起作用

原文地址:https://www.cnblogs.com/xwx20160804/p/11847040.html