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

  • 相关阅读:
    Apache Tomcat Ajp CVE-2020-1938漏洞复现
    关于JDK高版本下RMI、LDAP+JNDI bypass的一点笔记
    javaweb-codereview 学习记录-5
    java 动态代理机制
    关于<Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)>看后的一些总结-2
    关于<Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上)>看后的一些总结-1
    javaweb-codereview 学习记录-4
    从0到1掌握某Json-TemplatesImpl链与ysoserial-jdk7u21的前因后果
    javaweb-codereview 学习记录-2
    javaweb-codereview 学习记录-1
  • 原文地址:https://www.cnblogs.com/xwx20160804/p/11847040.html
Copyright © 2020-2023  润新知