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也不会起作用