美文网首页
阿里毕玄-测试Java编程能力-我的回答(二)

阿里毕玄-测试Java编程能力-我的回答(二)

作者: 零壹视界 | 来源:发表于2019-04-22 12:18 被阅读0次

    毕玄老师发表了一篇公众号文章:来测试下你的Java编程能力,本系列文章为其中问题的个人解答。

    第四个问题:

    CGLib和Java的动态代理相比,具体有什么不同?

    还是从简单的开始。

    性能优化的场景

    假设我们的代码写完后,发现性能很差,现在需要进行优化,优化之前需要得到代码中的方法执行耗时,用于辅助分析性能瓶颈。当然我们可以用Jprofiler等工具来可视化分析,这里我们暂且用最原始的方法,就是在代码中打印方法的执行耗时,相信很多人都这样干过。

    简单粗暴直接法

    package com.xetlab.javatest.question2;
    
    import java.util.Random;
    
    public class HelloWorldServiceImpl implements HelloWorldService {
        public void sayhi() {
            long start = System.currentTimeMillis();
            
            try {
                Thread.sleep(new Random().nextInt(5000));
            } catch (InterruptedException e) {
            }
            System.out.println("hello world");
    
            long end = System.currentTimeMillis();
    
            System.out.println("consume:" + (end - start) + "ms");
        }
    }
    
    

    干净一点的方法

    简单粗暴直接法,虽然快,但添加的代码在性能优化完成后,如果想删除时得手动删除,哪天又出现问题可能又得加回来。下面是干净点的方法。

    package com.xetlab.javatest.question2;
    
    import java.util.Random;
    
    public class HelloWorldServiceImpl implements HelloWorldService {
        public void sayhi() {
            try {
                Thread.sleep(new Random().nextInt(5000));
            } catch (InterruptedException e) {
            }
            System.out.println("hello world");
        }
    }
    
    
    package com.xetlab.javatest.question2;
    
    public class JavaStaticProxy {
    
        static class ProxyHelloWorldServiceImpl implements HelloWorldService {
    
            private HelloWorldService helloWorldService;
    
            public ProxyHelloWorldServiceImpl(HelloWorldService helloWorldService) {
                this.helloWorldService = helloWorldService;
            }
    
            public void sayhi() {
                long start = System.currentTimeMillis();
    
                helloWorldService.sayhi();
    
                long end = System.currentTimeMillis();
                System.out.println("consume:" + (end - start) + "ms");
            }
        }
    
        public static void main(String[] args) {
            HelloWorldService helloWorldService = new ProxyHelloWorldServiceImpl(new HelloWorldServiceImpl());
            helloWorldService.sayhi();
        }
    }
    
    

    这里更干净的方法,HelloWorldServiceImpl只包含了和自己业务相关的代码,不用担心代码被不必要的逻辑污染,ProxyHelloWorldServiceImpl实现了和HelloWorldServiceImpl一样的接口,但是ProxyHelloWorldServiceImpl的sayhi方法执行的时候并没执行实际的sayhi逻辑,而是把sayhi逻辑委托给HelloWorldServiceImpl去执行,同时在方法执行前后加上了方法耗时统计的代码。这个就是代理了,具体来说上面的方法实现了代理模式,是静态代理。

    使用代理的优点是:

    可以在不改变原有类的情况下,对类的功能进行扩展,如果原有类是第三方库中的,不能直接修改,就可以通过这种方式来扩展功能。

    更进一步

    目前我们的静态代理,只能分析HelloWorldServiceImpl中的sayhi方法的执行耗时,能不能更通用一点可以应用到别的类呢,Java自带的动态代理就可以达到目的。

    package com.xetlab.javatest.question2;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class JavaDynamicProxy {
    
        static class ProfilerInvocation<T> implements InvocationHandler {
            private T target;
    
            public ProfilerInvocation(T target) {
                this.target = target;
            }
    
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long start = System.currentTimeMillis();
    
                Object result;
                try {
                    result = method.invoke(target, args);
                } finally {
                    long end = System.currentTimeMillis();
                    System.out.println("consume:" + (end - start) + "ms");
                }
                return result;
            }
        }
    
        public static void main(String[] args) {
            HelloWorldService helloWorldService = (HelloWorldService) Proxy.newProxyInstance(HelloWorldServiceImpl.class.getClassLoader(),
                    new Class[]{HelloWorldService.class},
                    new ProfilerInvocation(new HelloWorldServiceImpl()));
            helloWorldService.sayhi();
        }
    }
    
    

    这次并没有直接编写一个代理类,而是编写了一个实现了InvocationHandler的ProfilerInvocation类(ProfilerInvocation除了可应用于实现了HelloWorldService的类中,也可以用在别的实现了接口的类中),然后利用Java中的动态代理Proxy直接生成了代理对象。其基本原理还是和静态代理一样的:

    1. Proxy在生成代理对象之前会先动态创建一个实现HelloWorldService接口的代理类(使用反射直接在内存中创建代理类的class文件并加载到虚拟机中)。
    2. 动态创建的代理类中的sayhi方法通过调用ProfilerInvocation的invode方法,也是委托给真正的HelloWorldServiceImpl去执行。
    3. 和静态代理的区别是代理类是动态生成的,所以叫动态代理。

    Java的动态代理是用实现接口的方式,只适用于有实现接口的类的代理,如下图所示:

    {% asset_img javaproxy.png Java动态代理 %}

    CGLib

    如果是普通类的情况,就需要用CGLib了,CGLib是使用继承的方式实现动态代理,如下图所示:

    {% asset_img cglibproxy.png CGLib动态代理 %}

    使用CGLib实现的动态代理代码:

    package com.xetlab.javatest.question2;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    public class CglibProxy {
    
        static class ProfilerInterceptor<T> implements MethodInterceptor {
            private T target;
    
            public ProfilerInterceptor(T target) {
                this.target = target;
            }
    
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                long start = System.currentTimeMillis();
    
                Object result;
                try {
                    result = method.invoke(target, objects);
                } finally {
                    long end = System.currentTimeMillis();
                    System.out.println("consume:" + (end - start) + "ms");
                }
                return result;
            }
        }
    
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(HelloWorldServiceImpl.class);
            enhancer.setCallback(new ProfilerInterceptor(new HelloWorldServiceImpl()));
            HelloWorldService helloWorldService = (HelloWorldServiceImpl) enhancer.create();
            helloWorldService.sayhi();
        }
    }
    
    

    可以看出,MethodInterceptor和InvocationHandler很类似,区别只是在生成代理对象的写法不一样,另外CGLib动态生成的代理类是直接继承被代理类,然后重写其中的方法(也是在内存中动态生成代理类的class文件,并加载到jvm中)。

    动态代理在spring中可以说应用非常广泛,如:

    1. Transaction事务注解
    2. Cache缓解注解
    3. 其它aop

    所以针对性能优化的场景,还可以添加一个叫Profiler的注解,然后在有需要统计执行耗时的方法上加上注解,如果想灵活控制开关,可以再添加一个配置项,按需全局开启关闭profiler,这样是最方便的。

    源代码

    https://github.com/huangyemin/javatest
    https://gitee.com/huangyemin/javatest
    

    相关文章

      网友评论

          本文标题:阿里毕玄-测试Java编程能力-我的回答(二)

          本文链接:https://www.haomeiwen.com/subject/bqlygqtx.html