Opencv 私有变量 canal安装 css text 虚拟机 clojure openssl Web Uploader Draggabilly 建筑资质 jquery对象 short几个字节 mysql合并结果集 matlab取绝对值 查看mysql密码 python安装 java教程 java的数据结构 java类的继承 java语言代码大全 java时间格式化 java中接口的定义 java代码注释 java创建对象 怪物猎人ol捏脸数据 销售单打印软件 js删除数组指定元素 主板排名天梯图 彻底删除mysql flash制作工具 小米手环充电多久 mysql时间比较 web聊天室 摇骰子表情包 idea下载 戴尔xps怎么样 ps反光效果 cad标题栏 smartupload
当前位置: 首页 > 学习教程  > 编程语言

静态代理,JDK动态代理,Cglib动态代理详解

2021/2/13 17:07:15 文章标签: 测试文章如有侵权请发送至邮箱809451989@qq.com投诉后文章立即删除

关注微信公众号【Java之言】,更多干货文章和学习资料,助你放弃编程之路! 文章目录一、代理模式二、静态代理三、动态代理3.1 JDK动态代理3.1 Cglib动态代理四、两种动态代理区别一、代理模式 代理模式(Proxy Pattern)是…

关注微信公众号【Java之言】,更多干货文章学习资料,助你放弃编程之路!

文章目录

  • 一、代理模式
  • 二、静态代理
  • 三、动态代理
    • 3.1 JDK动态代理
    • 3.1 Cglib动态代理
  • 四、两种动态代理区别


一、代理模式

代理模式(Proxy Pattern)是程序设计中的一种设计模式,他的特征是代理类和委托类实现有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

代理类与委托类之间通常会存在关联关系,一个代理类对象与一个委托类对象(目标对象)关联,代理类对象本身并不真正实现服务,而是通过调用委托类对象的相关方法,来提供特定的服务。

即通过代理对象访问目标对象,这样我们可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

以最近很火的贵州茅台为例子,假如我们要买1箱茅台,我们不是直接跟茅台公司买的,而是通过代理商(例如沃尔玛,京东等)进行购买。茅台公司就是一个目标对象,它只负责生产茅台,而其他如何销售,寄快递等琐碎的事交由代理商(代理对象)来处理。

在这里插入图片描述

二、静态代理

代理对象与目标对象一起实现相同的接口或者继承相同父类,由程序员创建或特定工具自动生成源代码,即在编译时就已经确定了接口,目标类,代理类等。在程序运行之前,代理类的 .class 文件就已经生成。

你可以简单认为代理对象写死持有目标对象。

package com.nobody.staticproxy;

/**
 * @Description 白酒厂商
 * @Author Mr.nobody
 * @Date 2021/2/12
 * @Version 1.0
 */
public interface WhileWineCompany {
    // 生产酒 (演示才写一个方法,实际多个方法都是能被代理的)
    void product();
}
package com.nobody.staticproxy;

/**
 * @Description 委托类,贵州茅台
 * @Author Mr.nobody
 * @Date 2021/2/12
 * @Version 1.0
 */
public class Moutai implements WhileWineCompany {
    public void product() {
        System.out.println("生产贵州茅台...");
    }
}
package com.nobody.staticproxy;

/**
 * @Description 代理类,京东代理商
 * @Author Mr.nobody
 * @Date 2021/2/12
 * @Version 1.0
 */
public class JDProxy implements WhileWineCompany {

    // 被代理的贵州茅台公司
    private Moutai moutai;

    public JDProxy(Moutai moutai) {
        this.moutai = moutai;
    }

    public void product() {
        System.out.println("京东商城下订单购买");
        // 实际调用目标对象的方法
        moutai.product();
        System.out.println("京东商城发快递");
    }
}
package com.nobody.staticproxy;

/**
 * @Description
 * @Author Mr.nobody
 * @Date 2021/2/12
 * @Version 1.0
 */
public class Main {
    public static void main(String[] args) {
        // 生成代理对象,并传入被代理对象
        WhileWineCompany proxy = new JDProxy(new Moutai());
        proxy.product();
    }
}

// 输出结果
京东商城下订单购买
生产贵州茅台...
京东商城发快递

静态代理优缺点:

  • 优点:在不修改目标对象的功能前提下,可以对目标功能扩展。
  • 缺点:假如又有一个目标类,也要做增强,则还需要新增相对应的代理类,导致我们要手动编写很多代理类。同时,一旦接口增加方法,目标对象与代理对象都要维护。

三、动态代理

代理类在程序运行时才创建的代理方式被成为动态代理。

静态代理中,代理类(JDProxy)是我们程序员定义的,在程序运行之前就已经编译完成。而动态代理中的代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。

在 Java 中,有2种动态代理实现方式,JDK动态代理CGLIB动态代理

Spring 中的 AOP 是依靠动态代理来实现切面编程的。

3.1 JDK动态代理

JDK动态代理是基于反射机制,生成一个实现代理接口的匿名类,然后重写方法进行方法增强。在调用具体方法前通过调用 InvokeHandlerinvoke 方法来处理。

它的特点是生成代理类速度很快,但是运行时调用方法操作会比较慢,因为是基于反射机制的,而且只能针对接口编程,即目标对象要实现接口。

如果目标对象实现了接口,默认情况下会采用JDK的动态代理。

实现JDK动态代理,我们需要借助 java.lang.reflect 包下的 Proxy 类InvocationHandler接口

package com.nobody.jdkproxy;

/**
 * @Description 白酒厂商
 * @Author Mr.nobody
 * @Date 2021/2/12
 * @Version 1.0
 */
public interface WhileWineCompany {
    // 生产酒 (演示才写一个方法,实际多个方法都是能被代理的)
    void product();
}
package com.nobody.jdkproxy;

/**
 * @Description 委托类,贵州茅台
 * @Author Mr.nobody
 * @Date 2021/2/12
 * @Version 1.0
 */
public class Moutai implements WhileWineCompany {
    public void product() {
        System.out.println("生产贵州茅台...");
    }
}
package com.nobody.jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Description JDK动态代理实现InvocationHandler接口
 * @Author Mr.nobody
 * @Date 2021/2/12
 * @Version 1.0
 */
public class JDKProxy implements InvocationHandler {

    // 被代理的目标对象
    private Object target;

    /**
     * 所有执行代理对象的方法都会被替换成执行invoke方法
     * 
     * @param proxy 代理对象
     * @param method 将要执行的方法信息
     * @param args 执行方法需要的参数
     * @return 方法执行后的返回值
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("京东商城下订单");
        // 通过反射,调用目标对象的方法并传入参数
        Object result = method.invoke(target, args);
        System.out.println("京东商城发快递");
        return result;
    }

    /**
     * 获取代理对象
     * 
     * @param target 被代理的对象
     * @return 代理对象
     */
    public Object getJDKProxy(Object target) {
        this.target = target;
        // loader:ClassLoader对象,定义哪个ClassLoader对象加载生成代理对象
        // interfaces:一个Interface对象的数组,即给代理的对象提供一组接口,代理对象就会实现了这些接口(多态),这样我们就能调用这些接口中的方法
        // h::InvocationHandler对象,当动态代理对象在调用方法的时候,会调用InvocationHandler对象的invoke方法
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }
}
package com.nobody.jdkproxy;

/**
 * @Description
 * @Author Mr.nobody
 * @Date 2021/2/12
 * @Version 1.0
 */
public class Main {
    public static void main(String[] args) {
        JDKProxy jdkProxy = new JDKProxy();
        // 获取贵州茅台的代理对象
        WhileWineCompany proxy = (WhileWineCompany) jdkProxy.getJDKProxy(new Moutai());
        // 执行方法
        proxy.product();
    }
}

// 输出结果
京东商城下订单
生产贵州茅台...
京东商城发快递

如果此时我们又增加了一个目标对象,也要进行代理,我们只需要定义一个委托类实现接口即可。

package com.nobody.jdkproxy;

/**
 * @Description 委托类,酒鬼酒
 * @Author Mr.nobody
 * @Date 2021/2/12
 * @Version 1.0
 */
public class JiuGuiJiu implements WhileWineCompany {

    public void product() {
        System.out.println("生产酒鬼酒...");
    }
}
package com.nobody.jdkproxy;

/**
 * @Description
 * @Author Mr.nobody
 * @Date 2021/2/12
 * @Version 1.0
 */
public class Main {
    public static void main(String[] args) {
        JDKProxy jdkProxy = new JDKProxy();
        // 获取贵州茅台的代理对象
        WhileWineCompany proxy = (WhileWineCompany) jdkProxy.getJDKProxy(new Moutai());
        // 执行方法
        proxy.product();

        System.out.println("----------------------");

        // 获取酒鬼酒的代理对象
        proxy = (WhileWineCompany) jdkProxy.getJDKProxy(new JiuGuiJiu());
        // 执行方法
        proxy.product();
    }
}

// 输出结果
京东商城下订单
生产贵州茅台...
京东商城发快递
----------------------
京东商城下订单
生产酒鬼酒...
京东商城发快递

动态代理让我们方便对代理类的方法进行统一处理,而不用修改每个代理类中的方法。因为所有被代理执行的方法,最终都是调用 InvocationHandler 中的 invoke 方法,所以我们可以在 invoke 方法中统一进行增强处理。

在JDK动态代理的过程中,没有看到实际的代理类,代理对象又是如何通过调用 InvocationHandler 的
invoke方法来完成代理过程的?通过JDK源码分析其实是 Proxy 类的 newProxyInstance
方法在运行时动态生成字节码生成代理类(缓存在Java虚拟机内存中),从而创建了一个动态代理对象。

我们通过以下方法将生成的代理类打印到本地查看:

byte[] classFile =
        ProxyGenerator.generateProxyClass("$Proxy0", Moutai.class.getInterfaces());
String path = "D:/$Proxy0.class";
try (FileOutputStream fos = new FileOutputStream(path)) {
    fos.write(classFile);
    fos.flush();
} catch (Exception e) {
    e.printStackTrace();
}

最终,生成的class文件通过反编译如下,可以看出代理类继承了 Proxy,并且实现了与目标对象同样的 WhileWineCompany 接口,所以不难理解为什么 newProxyInstance 方法生成的代理对象能赋值给 WhileWineCompany 接口。而且从中也看出代理类的 product 方法实际调用了 InvocationHandler 的 invoke 方法,从而最终实现对目标对象的方法代理。

简单可总结为,代理对象($Proxy0)持有 InvocationHandler 对象(我们定义的JDKProxy),InvocationHandler 对象持有目标对象(target),InvocationHandler 中的 invoke 方法对目标对象的方法进行增强处理。从而达到调用代理对象的方法,代理对象调用 InvocationHandler 的 invoke方法,invoke 方法中又调用了目标对象的方法。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.nobody.staticproxy.WhileWineCompany;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements WhileWineCompany {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

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

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

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

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.nobody.staticproxy.WhileWineCompany").getMethod("product");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

JDK为我们的生成了一个叫$Proxy0(0是编号,有多个代理类会依次递增)的代理类,这个类文件信息存放在内存中,我们在创建代理对象时,是通过反射获得这个类的构造方法,然后创建的代理实例。

代理类继承了 Proxy 类,因为在Java中是单继承的,所以这就是为什么JDK动态代理中,目标对象一定要实现接口。

3.1 Cglib动态代理

JDK动态代理要求目标对象要实现接口,那如果一个类没有实现接口呢?那可以用 Cglib 实现动态代理。

Cglib(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库,它是开源的。动态代理是利用 asm 开源包,将目标对象类的 class 文件加载进来,然后修改其字节码生成新的子类来进行扩展处理。即可以在运行期扩展Java类和实现Java接口。它广泛的被许多AOP的框架使用中,例如 Spring AOP,为他们提供方法的 interception (拦截)。

Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类。不推荐直接使用ASM,除非你对JVM内部结构包括class文件的格式和指令集都了如指掌。

总结为,cglib继承被代理的类,重写方法,织入通知,动态生成字节码并运行,要求被代理的类不能final修饰符修饰。

<!-- 引入cglib依赖 -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
package com.nobody.cglib;

/**
 * @Description 委托类,贵州茅台
 * @Author Mr.nobody
 * @Date 2021/2/12
 * @Version 1.0
 */
public class Moutai {

    public void product() {
        System.out.println("生产贵州茅台...");
    }
}
package com.nobody.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @Description 动态代理类,实现MethodInterceptor方法拦截器接口
 * @Author Mr.nobody
 * @Date 2021/2/12
 * @Version 1.0
 */
public class CglibProxy implements MethodInterceptor {

    // 被代理的目标对象
    private Object target;

    // 动态生成一个新的类,使用父类的无参构造方法创建一个指定了特定回调的代理实例

    /**
     * 动态生成一个新的类
     * 
     * @param target
     * @return
     */
    public Object getProxyObject(Object target) {
        this.target = target;
        // 增强器,动态代码生成器
        Enhancer enhancer = new Enhancer();
        // 回调方法
        enhancer.setCallback(this);
        // 设置生成代理类的父类类型
        enhancer.setSuperclass(target.getClass());
        // 动态生成字节码并返回代理对象
        return enhancer.create();
    }

    /**
     * 拦截方法
     * 
     * @param o CGLib动态生成的代理类实例
     * @param method 上文中实体类所调用的被代理的方法引用
     * @param objects 方法参数值列表
     * @param methodProxy 生成的代理类对方法的代理引用
     * @return 代理对象
     * @throws Throwable
     */
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
            throws Throwable {
        System.out.println("京东商城下订单");
        Object result = methodProxy.invoke(target, objects);
        System.out.println("京东商城发快递");
        return result;
    }
}
package com.nobody.cglib;

/**
 * @Description
 * @Author Mr.nobody
 * @Date 2021/2/12
 * @Version 1.0
 */
public class Main {
    public static void main(String[] args) {
        // 目标对象
        Moutai moutai = new Moutai();
        // 代理对象
        Moutai proxy = (Moutai) new CglibProxy().getProxyObject(moutai);
        // 执行代理对象的方法
        proxy.product();
    }
}

//输出结果
京东商城下订单
生产贵州茅台...
京东商城发快递

Cglib动态代理注意的2点:

  1. 被代理类不能是 final 修饰的。
  2. 需要扩展的方法不能有 final 或 static 关键字修饰,不然不会被拦截,即执行方法只会执行目标对象的方法,不会执行方法扩展的内容。

四、两种动态代理区别

JDK动态代理是基于反射机制,生成一个实现代理接口的匿名类。而Cglib动态代理是基于继承机制,继承被代理类,底层是基于asm第三方框架对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

JDK动态代理是生成类的速度快,后续执行类的方法操作慢;Cglib动态代理是生成类的速度慢,后续执行类的方法操作快。

JDK只能针对接口编程,Cglib可以针对类和接口。在Springboot项目中,在配置文件中增加 spring.aop.proxy-target-class=true 即可强制使用Cglib动态代理实现AOP。

如果目标对象实现了接口,默认情况下是采用JDK动态实现AOP,如果目标对象没有实现接口,必须采用CGLIB库动态实现AOP。

此演示项目已上传到Github,如有需要可自行下载,欢迎 Star

https://github.com/LucioChn/dynamic-proxy-demo

关注微信公众号【Java之言】,更多干货文章学习资料,助你放弃编程之路!
在这里插入图片描述


本文链接: http://www.dtmao.cc/news_show_700059.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?