温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

JDK动态代理的原理是什么

发布时间:2021-06-17 17:24:15 来源:亿速云 阅读:129 作者:Leah 栏目:大数据

JDK动态代理的原理是什么,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

JDK动态代理使用与原理底层解析

java对设计模式--代理模式的实现,只能针对接口进行代理。代理模式:提供一个代理对象来持有目标对象的引用,通过对代理对象的操作可以达到操作目标对象的目的。使用代理模式主要是使用者不想或者不能直接操作目标对象,需要一个代理的中间对象来维持联系。例如Mybatis中Mapper接口并没有实现类,因此使用者不能直接操作实现类,所以会产生一个代理Mapper。又例如Spring AOP中的Bean,使用者想对Bean的使用进行增强或者其他处理,于是Spring需要返回一个的代理Bean来完成目的。

一接口:

public interface ITodo {
    void doString(String desc);
}

一实现:

public class Todo implements ITodo {
    @Override
    public void doString(String desc) {
        System.out.println("doString: " + desc);
    }
}

目标,对接口的原有方法进行增强。实现方式:JDK动态代理

一代理工具类:

public class ProxyInstance implements InvocationHandler {
    // 代理目标,即被代理类
    private Object target;

    // 代理类持有被代理类 
    public ProxyInstance(Object target) {
        this.target = target;
    }

    public <T> T getProxy(){
        // 获取实例方式,这里使用newProxyInstance
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    // 这里为代理的处理流程
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy before");
        Object obj=method.invoke(target,args);
        System.out.println("proxy after");
        return obj;
    }
}
测试类:
public class Test {
    public static void main(String[] args) {
        // 开启保存代理中生成文件的代码
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        ITodo todo = new ProxyInstance(new Todo()).getProxy();
        todo.doString("------ nothing-------- ");
    }
}
控制台:
proxy before
doString: ------ nothing--------
proxy after

通过控制台就可以看到接口原有的方法被增强了,方法执行之前和执行之后执行其他代码。这就是JDK的动态代理功能。JDK动态代理的使用组件:一接口、一实现、一代理工具类,并且需要遵循以下规则:

代理工具类持有目标类
  • 作为参数传入

代理工具类需要实现接口InvocationHandler
  • 代理类需要使用Proxy的构造方法获取实例

    • 一般使用newProxyInstance

    • 定义代理处理逻辑

    • 用于通过反射生成代理类的方法

    • 因此只能代理接口中已定义的方法

    • 接口的所有方法都会被重写为final类型

    • 用于加载新生成的代理类$Proxy{n}

    • 参数1: 类加载器

    • 参数2: 接口Class数组

    • 参数3: InvocationHandler接口实现类

  • 构造参数为被代理类实现的接口

  • 代理类调用的对象是Proxy类产生的实例,与被代理类不是一个对象

    • 每次调用都需要通过反射来调用

代理工具类需要重写invoke方法
  • public Object invoke(Object proxy, Method method, Object[] args)

    • proxy为JDK生成的代理对象$Proxy{n}对象,因此是动态的,只存在于内存中

    • Method为JDK例如反射获取的调用方法

    • args为调用方法的参数

    • 因为该类是生成的,所有需要类加载器从新加载


底层原理分析

对上面的示例,查看其生成的代理类源码如下

package com.sun.proxy;


import cn.tinyice.demo.proxy.jdk.ITodo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;


// 代理对象集成java.lang.reflect.Proxy并实现了被代理对象的父接口
// 这里调整了方法位置,便于分析
public final class $Proxy0 extends Proxy implements ITodo {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

   static {
        // 需要对接口的equals、toString、hashCode和 目标方法进行重写,因此先获取原方法的运行时表示java.lang.reflect.Method
        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("cn.tinyice.demo.proxy.jdk.ITodo").getMethod("doString", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    // 构造器入参:InvocationHandler实例即代理类实例
    public $Proxy0(InvocationHandler var1) throws  {
        //  判断InvocationHandler不为空,然后赋给变量h
        super(var1);
    }

      // ----------------------------------------- 代理主要关注点: 目标方法 重写 ------------------------
    public final void doString(String var1) throws  {
        try {
            //  super.h=InvocationHandler实例,调用其invoke方法,该方法为用户重写的方法
           // 三个参数来源确定: proxy=this,method = target的method,args=参数的Object数组(发生类型转换)
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    // ----- equals、toString、hashCode 重写,实质调用 InvocationHandler的对应方法-------
    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 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);
        }
    }
}

源码部分主要分为四部分

代理类定义:

新生成的代理类定义为:public final class $Proxy0 extends Proxy implements ITodo {...},就是重新生成一个新的接口实现类,对原有实现类进行功能复制、增强。

静态块初始化equals、hashCode、toString和原有实现类的方法

equals、hashCode、toString这三个方法是Object方法,主要是验证Java对象的唯一性,与原来的实现类已经不是一个内存地址了以及其他操作。

doString是原有实现类的方法

构造方法传入InvocationHandler

InvocationHandler就是使用者编写的代码,这一步就是切入

重写静态块中定义的所有方法

所有被重写的方法都变为了final类型。所有的方法都调用了InvocationHandler的invoke方法。因此这个invoke方法就是增强的核心方法。

了解以上内容基本可以知道JDK动态代理的底层原理了。一句话:重写方法调用自定义的invoke来实现增强


源码生成流程

调用入口:获取代理类

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ... }

核心代吗

Class<?> cl = getProxyClass0(loader, intfs);

proxyClassCache.get(loader, interfaces)

proxyClassCache在Proxy类中静态定义

private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
proxyClassCache说明:
  • WeakCache实例,一个弱引用缓存对象。存储结构如下,是 K,P,V三个对象存储

ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();
  • 该对象需要2个工厂类,一个用于生成subKey,一个用于生成value

    • KeyFactory、ProxyClassFactory 均为BiFunction<ClassLoader, Class<?>[], Object>,具有apply方法

  • 获取时和常规缓存使用方式一致,先通过key从缓存Map中获取,获取不到就去生成

    ConcurrentMap<Object, Supplier<V>

    。首次使用必是生成。

Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); 
// 这里生成subKey用于缓存的key
factory = new Factory(key, parameter, subKey, valuesMap); 
// 这个工厂是实现Supplier<V>Factory的get实现中调用valueFactory的apply即ProxyClassFactory // //#apply,并最终返回了value
value = Objects.requireNonNull(valueFactory.apply(key, parameter));

subkey只是中间的关联变量,不需要关注,只需要关注value也就是代理类的生成。生成入口

ProxyClassFactory#apply(ClassLoader loader, Class<?>[] interfaces)

所有代理类的字节码定义都在该方法中:主要如下

包名称确定:
String proxyName = proxyPkg + proxyClassNamePrefix + num; // com.sun.proxy.+$Proxy+num --->com.sun.proxy.$Proxy0
  • proxyPkg在接口的访问修饰符是public时=“com.sun.proxy“,否则=被代理类的包名

  • proxyClassNamePrefix=”$Proxy“

  • num为原子递增AtomicLong,与生成代理类的个数相关

类字节码生成:
  • 接口访问修饰符 int accessFlags = Modifier.PUBLIC | Modifier.FINAL 即 public final

  • 非public接口会重写为final

  • 所有方法都是 public final

类字节码文件生成:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
类字节码加载到JVM:
defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);

此时新生成的代理类已经加载到JVM中去了


类字节码文件生成

代理字节码生成工具类 :ProxyGenerator

// 是否生成文件属性读取
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));


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<Void>() {
            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));
                        Files.createDirectories(var3);
                        var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                    } else {
                        var2 = Paths.get(var0 + ".class");
                    }
                    Files.write(var2, var4, new OpenOption[0]);
                    return null;
                } catch (IOException var4x) {
                    throw new InternalError("I/O exception saving generated file: " + var4x);
                }
            }
        });
    }
    return var4;
}


字节码生成,忽略部分代码


private byte[] generateClassFile() {
    // equals、toString、hashCode 重写
    this.addProxyMethod(hashCodeMethod, Object.class);
    this.addProxyMethod(equalsMethod, Object.class);
    this.addProxyMethod(toStringMethod, Object.class);


    // 接口所有方法重写
    Class[] var1 = this.interfaces;
    int var2 = var1.length;
    int var3;
    Class var4;
    for(var3 = 0; var3 < var2; ++var3) {
        var4 = var1[var3];
        Method[] var5 = var4.getMethods();
        int var6 = var5.length;
        for(int var7 = 0; var7 < var6; ++var7) {
            Method var8 = var5[var7];
            this.addProxyMethod(var8, var4);
        }
    }
   // ... ignore ...
}

关于JDK动态代理的原理是什么问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

jdk
AI