动态代理

根据代理类的生成时间不同可以将代理分为静态代理动态代理两种。

静态代理

先看下静态代理的例子。以链家中介代理租赁买房的例子。

/**
 * 业务接口
 * Created by jiapeng on 2017/12/26.
 */
public interface Handle {
    //租赁
    public void rent();
    //购买
    public void purchase();
}
/**
 * 业务处理类,哈登登哥
 * Created by jiapeng on 2017/12/26.
 */
public class Harden implements Handle{

    public void rent() {
        System.out.println("哈登租赁了xx房屋");
    }
    public void purchase() {
        System.out.println("哈登购买了xx房屋");
    }
}
/**
 * 代理类链接中介
 * 哈登登哥太忙,没空去跑腿办手续,跟卖家沟通细节
 * Created by jiapeng on 2017/12/26.
 */
public class HomeLinkProxy implements Handle{

    private Harden harden;

    public HomeLinkProxy(Harden harden){
        this.harden = harden;
    }

    public void rent() {
        preAction();
        harden.rent();
        afterAction();
    }

    public void purchase() {
        preAction();
        harden.purchase();
        afterAction();
    }

    /**
     * 链家代理,之前的动作
     */
    private void preAction(){
        System.out.println("链家收定金,跑腿,帮助准备材料and so on");
    }

    /**
     * 链家代理,之后的动作
     */
    private void afterAction(){
        System.out.println("链接收尾款,跑腿,帮助确保交接房屋顺利and so on");
    }
}
/**
 * 客户端调用类
 * Created by jiapeng on 2017/12/26.
 */
public class Client {

    public static void main(String[] args) {
        Handle proxy = new HomeLinkProxy(new Harden());
        proxy.purchase();
    }
}

执行客户端调用类结果:


image.png

以上例子就能说明,代理类的作用,跟现实生活中的场景一样。代理类在实际业务处理(哈登买房)的前后加上了,通用的业务操作。
这其实就是AOP的思想,只不过spring AOP是用动态代理实现的。后边继续体会下。

可以看出,所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

动态代理

动态代理从实现上,可以分为:

  1. JDK动态代理,jdk自带的不需要依赖别的包,但是功能有限制。
  2. 使用第三方字节码工具包实现。
  • ASM:是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。

ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or dynamically generate classes, directly in binary form.

ASM是低级的字节码生成工具,使用ASM已经近乎在于使用Javabytecode编程,对开发人员要求较高,也是性能最好的一种动态代理生成工具。但ASM的使用是在过于繁琐,而且性能也没有数量级的提升。所以有了高级的字节码生成库CGLIB和Javassist。

  • cglib,第三方包,高级的字节码生成库,使用会相对简单些,动态生成字节码文件。底层依赖asm包,所以使用需要依赖引入asm包。spring aop的实现提供jdk动态代理和cglib两种选择。

  • javassist,第三方包,高级的字节码生成库,使用会相对简单些,是由日本东京工业大学的数学和计算机科学系的xxx牛人所创建的。目前它已加入了开放源代码JBoss项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

JDK动态代理

代理类:

/**
 * 使用jdk实现的动态代理
 * 要绑定接口(这是一个限制缺陷,cglib弥补了这一缺陷)
 * Created by jiapeng on 2017/12/27.
 */
public class JdkDynamicProxy implements InvocationHandler {

    private Object target;

    /**
     * 绑定委托对象并返回一个代理类
     * @param target
     * @return
     */
    public Object bind(Object target) {
        this.target = target;
        //取得代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        preAction();
        result = method.invoke(target, args);
        afterAction();
        return result;
    }

    /**
     * 链家(jdk动态)代理,之前的动作
     */
    private void preAction() {
        System.out.println("链家收定金,跑腿,帮助准备材料and so on");
    }

    /**
     * 链家(jdk动态)代理,之后的动作
     */
    private void afterAction() {
        System.out.println("链接收尾款,跑腿,帮助确保交接房屋顺利and so on");
    }

}

客户端调用

/**
 * 客户端调用类
 * Created by jiapeng on 2017/12/26.
 */
public class Client {
    
    public static void main(String[] args) {
        //静态代理
//        Handle proxy = new HomeLinkProxy(new Harden());
//        proxy.purchase();
        //jdk动态代理
        //看源码可以知道,设置这个属性,会把代理类写到磁盘上
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy();
        Handle handle = (Handle) jdkDynamicProxy.bind(new Harden());
        handle.purchase();
    }
}

执行结果跟上边的静态代理类一样。
但是在com/sun/proxy的路径下,动态生成了一个代理类$Proxy0。反编译字节码看下这个代理类的代码,可以看到代理类继承反射包的Proxy类,实现自定义的业务接口Handle。

/**
 * jdk动态代理生成的代理类
 * Created by jiapeng on 2017/12/27.
 */
public final class $Proxy0 extends Proxy implements Handle {

    private static Method m3;
    private static Method m4;
    static {
        try {
            m3 = Class.forName("com.lxqn.jiapeng.proxy.Handle").getMethod("rent", new Class[0]);
            m4 = Class.forName("com.lxqn.jiapeng.proxy.Handle").getMethod("purchase", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    public $Proxy0(InvocationHandler var1) throws Throwable{
        super(var1);
    }

    public final void rent(){
        try {
            super.h.invoke(this, m3, null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void purchase(){
        try {
            super.h.invoke(this, m4, null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}

上边代码的原理,关键是反射包下的Proxy.newProxyInstance方法和Method.invoke方法。
翻下源码,加深下了解。
我们都知道,动态代理最关键的部分,肯定是运行时字节码的生成。所以,jdk肯定是调用的某个类的 可以生成字节码文件api。

  1. 对这组接口进行一定程度的安全检查
  2. 从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表
  3. 动态创建代理类的类对象
  4. 根据结果更新缓存表

代码过程分这四步,其实关键的就是第三部,其余的是考虑安全问题,本地缓存优化,多线程请求考虑。
字节码的生成在Proxy类的ProxyClassFactory的内部类apply方法里(jdk1.8)。截取其中关键的一行代码:

        /*
         * Generate the specified proxy class.
         */
         byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
             proxyName, interfaces, accessFlags);
         try {
             return defineClass0(loader, proxyName,
                                 proxyClassFile, 0, proxyClassFile.length);
         } catch (ClassFormatError e) {
         }

这里使用了神秘的ProxyGenerator类,这个类是jdk里sun.misc包下提供的一个类,在早的jdk版本里,这个类的源码是没有提供的。1.8是能看到,粗略的看一眼。能看到saveGeneratedFiles变量是sun.misc.ProxyGenerator.saveGeneratedFiles控制的。代码里充斥着写文件流,byte字节码的定义。

    public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();
        if(saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction() {
                public Void run() {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if(var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar), new String[0]);
                            Files.createDirectories(var3, new FileAttribute[0]);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class", new String[0]);
                        }

                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: " + var4x);
                    }
                }
            });
        }

        return var4;
    }

defineClass0方法也是一个native的方法。

以上贴了这么多,有个关键点结论

JDK提供了sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces) 底层方法来产生动态代理类的字节码。这就是jdk动态代理,动态生成字节码的底层依赖。

so,我们可以把这部分抽出来,自定义一个工具类,自己将生成的动态代理类保存到硬盘中。实现动态代理。

/**
 * 工具类
 * 使用ProxyGenerator类创建动态代理
 * Created by jiapeng on 2017/12/27.
 */
public class ProxyUtils {

    /**
     * 生成动态代理类方法
     * @param clazz 需要生成动态代理类的类
     * @param proxyName 为动态生成的代理类的名称
     */
    public static void generateClassFile(Class clazz, String proxyName) {
        //根据类信息和提供的代理类名称,生成字节码
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
        String paths = clazz.getResource(".").getPath();
        System.out.println(paths);
        FileOutputStream out = null;

        try {
            //保留到硬盘中
            out = new FileOutputStream(paths + proxyName + ".class");
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
ProxyUtils.generateClassFile(new Harden().getClass(),"DynamicProxyFromUtil");

客户端执行工具方法,会在同级目录生成DynamicProxyFromUtil.class字节码动态代理,比较了下,竟然完全一样。


image.png

问题:如果生成的动态代理字节码完全一样的话,还用反射包的Proxy干什么。有区别吗?

cglib动态代理

cglib动态代理的使用需要引入cglib和asm的依赖jar包。

/**
 * cglib实现的动态代理类
 * Created by jiapeng on 2017/12/27.
 */
public class CglibDynamicProxy implements MethodInterceptor {

    private Object target;

    public Object getProxyInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        // call back method
        enhancer.setCallback(this);
        // create proxy instance
        return enhancer.create();  
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        preAction();
        Object result = methodProxy.invokeSuper(o, objects);
        afterAction();
        return result;
    }

    /**
     * 链家(cglib动态)代理,之前的动作
     */
    private void preAction() {
        System.out.println("链家收定金,跑腿,帮助准备材料and so on");
    }

    /**
     * 链家(cglib动态)代理,之后的动作
     */
    private void afterAction() {
        System.out.println("链接收尾款,跑腿,帮助确保交接房屋顺利and so on");
    }
}

客户端代码调用

/**
 * cglib动态代理客户端调用类
 * Created by jiapeng on 2017/12/26.
 */
public class Client {

    public static void main(String[] args) {

        //设置代理类字节码生成到指定位置  
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".");
        CglibDynamicProxy proxy = new CglibDynamicProxy();
        Handle handle = (Handle) proxy.getProxyInstance(new Harden());
        handle.purchase();
    }
}

执行结果跟之前的例子一样。可以看出来,跟jdk动态代理的使用方式很像。cglib使用的是asm底层字节码框架,动态的生成字节码代理类。

参考文章:
https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html
http://blog.csdn.net/dreamrealised/article/details/12885739

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

推荐阅读更多精彩内容

  • 基础:class文件简介及加载流程 Java编译器编译好Java文件之后,产生.class 文件在磁盘中。这种cl...
    jiangmo阅读 469评论 0 1
  • 以前看了很多博客文章,一段时间后,对JDK动态代理还是模模糊糊。这次从思考方式上做一个梳理和推理,彻底搞懂动态代理...
    hanxingruo阅读 760评论 0 4
  • 在工作之余看一些优秀源码的时候发现很多地方使用了动态代理,所以抽了一些时间对java的动态代理深入熟悉一下,这篇文...
    半支铅笔半块橡皮阅读 487评论 0 3
  • title: Jdk动态代理原理解析 tags:代理 categories:笔记 date: 2017-06-14...
    行径行阅读 19,176评论 3 36
  • 心里五味杂陈的,被骗了,但是那种伤心似乎有不能涌上我的心头. 真不知道,这感觉怎么了?好奇怪,本以为会是个好故事....
    xiao钱钱阅读 136评论 0 0