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

毕玄老师发表了一篇公众号文章:来测试下你的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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • Java设计模式——代理模式 代理模式主要分为接口,委托类,代理类 接口:规定具体方法委托类:实现接口,完成具体的...
    vczyh阅读 651评论 0 0
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,089评论 1 32
  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,413评论 1 14
  • 话说贾芸举起装有冰片与麝香的锦盒递到凤姐跟前。可巧凤姐正是要办端阳的节礼,采买香料药饵的时节,...
    无言的启明星阅读 862评论 3 11
  • 宝贝: 我亲爱的孩子,听你今天电话中伤心的哭,妈妈既心疼也有安慰!心疼你有种种不适妈妈却无能为力,也心疼你不被老师...
    朵宝的姨妈阅读 169评论 0 2