在看线程并发的书籍时看到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也不会起作用