MENU

谈一谈 JDK 的动态代理

September 27, 2020 • Read: 4762 • 后端

设计模式 ---proxy 展开目录

什么是代理

增强一个对象的功能

买火车票,app 就是一个代理,他代理了火车站,小区当中的代售窗口

java 当中如何实现代理

java 实现的代理的两种办法

1、代理的名词展开目录

代理对象 增强后的对象

目标对象 被增强的对象

他们不是绝对的,会根据情况发生变化

2、静态代理展开目录

继承展开目录

代理对象继承目标对象,重写需要增强的方法;

缺点:会代理类过多,非常复杂

聚合展开目录

目标对象和代理对象实现同一个接口,代理对象当中要包含目标对象。

缺点:也会产生类爆炸,只不过比继承少一点点

总结:如果在不确定的情况下,尽量不要去使用静态代理。因为一旦你写代码,就会产生类,一旦产生类就爆炸。

3、动态代理展开目录

几种方式:CGLIB、JDK动态代理、Javassist(hibernate的代理实现方法)

动态产生一个类:增强的内容 + 目标对象

3.1 自己模拟的动态代理展开目录

不需要手动创建类文件(因为一旦手动创建类文件,就会产生类爆炸),通过接口反射生成一个类文件,然后调用第三方的编译技术,动态编译这个产生的类文件生成 class 文件,继而利用 UrlclassLoader (因为这个动态产生的 class 不在工程当中所以需要使用 UrlclassLoader) 把这个动态编译的类加载到 jvm 当中,最后通过反射把这个类实例化。

缺点:首先要生成文件

缺点:动态编译文件 class

缺点:需要一个 URLclassloader

这些缺点导致:软件性能的最终体现在 IO 操作

3.2 JDK 动态代理展开目录

通过接口反射得到字节码,然后把字节码转成 Class (使用 native 方法:defineClass0 ())。

JDK动态代理是继承了Proxy然后实现需要代理的接口,由于Java是单继承,所以JDK动态代理只能基于接口

JDK 动态代理,代理类继承 Proxy 类,实现了传入的接口类,代理的方法的内容是: 直接调用你实现的 InvocationHandler 的 invoke 方法里面的逻辑。

一个 Demo 展开目录

  1. Service 接口

    copy
    • public interface Service {
    • void query();
    • List<Integer> lists();
    • }
  2. ServiceImpl 实现类和 InvocationHandler 实现类

    copy
    • public class ServiceImpl implements Service{
    • public void query() {
    • System.out.println("impl");
    • }
    • public List<Integer> lists() {
    • List<Integer> list = new ArrayList<Integer>();
    • list.add(1);
    • return list;
    • }
    • }

    InvocationHandler实现类

    copy
    • public class MyInvo implements InvocationHandler {
    • public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    • System.out.println("invoke");
    • return null;
    • }
    • }
  3. Test 类

    copy
    • public class Test {
    • public static void main(String[] args) throws Exception {
    • Service o = (Service) Proxy.newProxyInstance(Test.class.getClassLoader(),
    • ServiceImpl.class.getInterfaces(), new MyInvo());
    • o.query();
    • // Proxy.newProxyInstance()最终会调用 ProxyGenerator.generateProxyClass()方法生成字节码文件,
    • // 然后调用 Proxy.defineClass0()方法,根据字节码生成代理类的类对象(也就是Class对象),这是个native方法
    • byte[] ts = ProxyGenerator.generateProxyClass("Ts", ServiceImpl.class.getInterfaces());
    • // 所以我们可以通过把这个字节码写到一个文件来查看生成的代理文件
    • File f = new File("./test1.class");
    • FileOutputStream out = new FileOutputStream(f);
    • out.write(ts);
    • }
    • }
  4. 展示生成的 Ts 类部分内容

    这个生成的 Ts 类里面

    copy
    • // 生成的test1.class,是放在idea里面进行反编译后查看的
    • public final class Ts extends Proxy implements Service {
    • private static Method m1;
    • private static Method m3;
    • // ......
    • public Ts(InvocationHandler var1) throws {
    • super(var1);
    • }
    • public final boolean equals(Object var1) throws {
    • try {
    • // 这个h就是代表InvocationHandler实现类,所以用代理类调用方法时,调用的是invoke()方法里面的实现逻辑
    • 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 void query() throws {
    • try {
    • super.h.invoke(this, m3, (Object[])null);
    • } catch (RuntimeException | Error var2) {
    • throw var2;
    • } catch (Throwable var3) {
    • throw new UndeclaredThrowableException(var3);
    • }
    • }
    • //......
    • }

源码解析展开目录

下面的源码解析只是简单的说下流程,还有很多东西没说 (但是好像都不太重要,跟产生代理类没有关系,更多的是为了性能。比如:缓存这些)
  1. 首先找到入口,在 Test 类创建代理类,调用的是 Proxy.newProxyInstance () 方法

    copy
    • public class Test {
    • public static void main(String[] args) throws Exception {
    • Service o = (Service) Proxy.newProxyInstance(Test.class.getClassLoader(),
    • ServiceImpl.class.getInterfaces(), new MyInvo());
    • o.query();
    • }
    • }
  1. 查看 Proxy.newProxyInstance () 方法

    • 在 Proxy 这个类当中首先实例化一个对象 ProxyClassFactory

    image-20200805224205592.png

  • 然后在这个方法内部调用 getProxyClass0 () 方法

    copy
    • public static Object newProxyInstance(ClassLoader loader,
    • Class<?>[] interfaces,
    • InvocationHandler h)
    • throws IllegalArgumentException
    • {
    • //......
    • // 这个就是代理类的Class对象,这里是关键地方,需要查看里面的Class是如何生成的
    • Class<?> cl = getProxyClass0(loader, intfs);
    • try {
    • // constructorParams = {InvocationHandler.class} ,
    • // 得到代理类的里参数对象类型为InvocationHandler的构造器对象
    • final Constructor<?> cons = cl.getConstructor(constructorParams);
    • final InvocationHandler ih = h;
    • //......
    • // 用构造器直接反射得到代理对象,由于生成的代理类继承了Proxy,所以调用的是下面展示的构造方法
    • return cons.newInstance(new Object[]{h});
    • //......
    • }

Proxy内部给生成代理类调用的构造方法

copy
  • protected InvocationHandler h; // 属性
  • protected Proxy(InvocationHandler h) {
  • Objects.requireNonNull(h);
  • // 把传进来的InvocationHandler实现类,赋值给全局变量h。
  • this.h = h;
  • }

当 h 全局属性被赋值后,代理生成的 equals () 方法里面 super.h.invoke (this, m1, new Object []{var1}),这里 h 就有值了。

  • getProxyClass0 () 方法,然后在 get 方法中调用了 apply 方法,完成对代理类的创建。

    image-20200927001703454.png

  • proxyClassCache 属性是 Proxy 类的一个静态变量,被赋值为 WeakCache 类创建的实例

    copy
    • //WeakCache类的get()方法
    • public V get(K key, P parameter) {
    • // ......
    • while (true) {
    • if (supplier != null) {
    • // supplier might be a Factory or a CacheValue<V> instance
    • // 重点方法,调用内部类Factory类的get方法
    • V value = supplier.get();
    • if (value != null) {
    • // 返回代理对象的Class对象
    • return value;
    • }
    • }
    • //......
    • }
    • }
  • 内部类 Factory 类的 get () 方法

    copy
    • public synchronized V get() { // serialize access
    • // ......
    • V value = null;
    • try {
    • // 得到value的地方,调用Proxy的内部类 ProxyClassFactory 的apply方法
    • value = Objects.requireNonNull(valueFactory.apply(key, parameter));
    • } finally {
    • if (value == null) { // remove us on failure
    • valuesMap.remove(subKey, this);
    • }
    • }
    • // ......
    • return value;
    • }
  • 调用 Proxy 的内部类 ProxyClassFactory 的 apply 方法

    apply 方法

    copy
    • public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    • Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
    • // 循环所有接口
    • for (Class<?> intf : interfaces) {
    • /*
    • * Verify that the class loader resolves the name of this
    • * interface to the same Class object.
    • */
    • Class<?> interfaceClass = null;
    • try {
    • // 判断两个类相等的前提:是否是同一个类加载器加载的
    • // 这里的interfaceClass 和循环的intf是否相等。在下面的if判断。
    • // 这里用传来的loader重新得到接口的Class类对象
    • interfaceClass = Class.forName(intf.getName(), false, loader);
    • } catch (ClassNotFoundException e) {
    • }
    • // 不相等,说明intf加载器不是和传来的loader不是一个。
    • if (interfaceClass != intf) {
    • throw new IllegalArgumentException(
    • intf + " is not visible from class loader");
    • }
    • /*
    • * Verify that the Class object actually represents an
    • * interface.
    • */
    • if (!interfaceClass.isInterface()) {
    • throw new IllegalArgumentException(
    • interfaceClass.getName() + " is not an interface");
    • }
    • /*
    • * Verify that this interface is not a duplicate.
    • */
    • if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
    • throw new IllegalArgumentException(
    • "repeated interface: " + interfaceClass.getName());
    • }
    • }
    • // 代理对象的包名
    • String proxyPkg = null; // package to define proxy class in
    • int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    • /*
    • * Record the package of a non-public proxy interface so that the
    • * proxy class will be defined in the same package. Verify that
    • * all non-public proxy interfaces are in the same package.
    • */
    • // 验证interface类的修饰符
    • for (Class<?> intf : interfaces) {
    • int flags = intf.getModifiers();
    • if (!Modifier.isPublic(flags)) {
    • accessFlags = Modifier.FINAL;
    • String name = intf.getName();
    • int n = name.lastIndexOf('.');
    • String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
    • if (proxyPkg == null) {
    • proxyPkg = pkg;
    • } else if (!pkg.equals(proxyPkg)) {
    • throw new IllegalArgumentException(
    • "non-public interfaces from different packages");
    • }
    • }
    • }
    • if (proxyPkg == null) {
    • // if no non-public proxy interfaces, use com.sun.proxy package
    • proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
    • }
    • /*
    • * Choose a name for the proxy class to generate.
    • */
    • // 这个的num,是调用原子类自增,防止并发时的名字相同
    • long num = nextUniqueNumber.getAndIncrement();
    • String proxyName = proxyPkg + proxyClassNamePrefix + num;
    • /*
    • * Generate the specified proxy class.
    • */
    • // 下面是生成一个指定的代理类的生成字节码
    • byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    • proxyName, interfaces, accessFlags);
    • try {
    • // 根据字节码生成代理类的类对象(也就是Class对象),这是个native方法
    • // 这里就直接返回到 Proxy.newProxyInstance()方法里面调用的getProxyClass0()方法
    • return defineClass0(loader, proxyName,
    • proxyClassFile, 0, proxyClassFile.length);
    • }
    • // ......
    • }

其中最重要的两个方法

generateProxyClass () 通过反射收集字段和属性然后生成字节

defineClass0 () jvm 内部完成对上述字节的 load

image-20200805224240498.png

总结:相比于 JDK 动态代理,cglib 是通过继承来操作子类的字节码生成代理类,JDK 是通过接口,然后利用 java 反射完成对类的动态创建,严格意义上来说 cglib 的效率高于 JDK 的反射,但是这种效率取决于代码功力,其实可以忽略不计,毕竟 JDK 是 JVM 的亲儿子........

Last Modified: October 10, 2020
Leave a Comment

  • OωO
  • |´・ω・)ノ
  • ヾ(≧∇≦*)ゝ
  • (☆ω☆)
  • (╯‵□′)╯︵┴─┴
  •  ̄﹃ ̄
  • (/ω\)
  • ∠( ᐛ 」∠)_
  • (๑•̀ㅁ•́ฅ)
  • →_→
  • ୧(๑•̀⌄•́๑)૭
  • ٩(ˊᗜˋ*)و
  • (ノ°ο°)ノ
  • (´இ皿இ`)
  • ⌇●﹏●⌇
  • (ฅ´ω`ฅ)
  • (╯°A°)╯︵○○○
  • φ( ̄∇ ̄o)
  • ヾ(´・ ・`。)ノ"
  • ( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
  • (ó﹏ò。)
  • Σ(っ °Д °;)っ
  • ( ,,´・ω・)ノ"(´っω・`。)
  • ╮(╯▽╰)╭
  • o(*////▽////*)q
  • >﹏<
  • ( ๑´•ω•) "(ㆆᴗㆆ)
  • (。•ˇ‸ˇ•。)
  • 泡泡
  • 蛆音娘
  • 阿鲁
  • 颜文字

4 Comments
  1. 蚩尤 蚩尤     Windows 10 /    Google Chrome

    大佬带带我

  2. Spoience Spoience     Android /    Google Chrome

    等我把 C 语言学好也来学 java(我好菜

    1. 未央花 未央花     Windows 10 /    QQ浏览器

      @Spoience 可以quyin feizao,深入学 Java 时,能懂 C++ 就更好了(刚好你也会 )(快更新!quyin witty

    2. Spoience Spoience     Windows 10 /    Google Chrome

      @未央花 C++ 皮毛都没学到,就勉强过考试还是老师捞的