首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >读源码——JDK动态代理

读源码——JDK动态代理

作者头像
早安嵩骏
发布2020-08-11 16:19:58
2950
发布2020-08-11 16:19:58
举报
文章被收录于专栏:程序猿人程序猿人

背景

在 java 中,动态代理算的上是底层架构的发动机。最熟悉的包括 Spring 的 aop、rpc 的实现中也都少不了它的影子。所以,从源码的角度对 jdk 实现的动态代理加以总结。

简单实现

1. 首先 jdk 的动态代理必须要有一个接口

public interface Developer {
    void code();
}

2. 被代理的类,实现了 Developer 接口,在真实的业务开发中,有时候可以没有这个类。

public class JavaDeveloper implements Developer{
    private String name;
    JavaDeveloper(String name){
        this.name = name;
    }

    @Override
    public void code() {
        System.out.println(this.name+"is coding java");
    }
}

3. 具体调用

import java.lang.reflect.Proxy;

public class ProxyTest {
    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); //代码行 1
        JavaDeveloper zack = new JavaDeveloper("Zack");
        Developer zackProxy = (Developer) Proxy.newProxyInstance(zack.getClass().getClassLoader(),//代码行2
                zack.getClass().getInterfaces(), ((proxy, method, args1) -> {
                    System.out.println(proxy.getClass());
                    if (method.getName().equals("code")) {
                        System.out.println("Zack is praying for the code!");
                        return method.invoke(zack, args);
                    }
                    if (method.getName().equals("debug")) {
                        System.out.println("Zack's have no bug!No need to debug!");
                        return null;
                    }
                    return null;
                }));
        zackProxy.code();
    }
}

4. 结果

"C:\Program Files\Java\jdk1.8.0_221\bin\java.exe"
class com.sun.proxy.$Proxy0
Zack is praying for the code!
Zackis coding java

Process finished with exit code 0

5. 源码解读

整个动态代理的过程,其实就是一个代理类对象的生成过程,而这一过程就是在“代码行 2”中完成的。我精简了这个函数,如下:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
        throws IllegalArgumentException {
    Class<?> cl = getProxyClass0(loader, intfs);//获得代理类,代码行3
    return cons.newInstance(new Object[]{h});//获得代理类的对象
}

其实这个函数真正发挥作用的就是这两行代码,其他的包括它的注解在内,都是一些校验和对操作权限的判断。继续对“代码行 3”向下追溯。

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
    proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    // If the proxy class defined by the given loader implementing
    // the given interfaces exists, this will simply return the cached copy;
    // otherwise, it will create the proxy class via the ProxyClassFactory
    return proxyClassCache.get(loader, interfaces);
}

代理 Class 的获取,使用了缓存来处理:WeakCache。它是一个弱引用缓存,通过向它的构造函数传入两个工厂类的对象完成了初始化。仔细向下看的话,KeyFactory 和 ProxyClassFactory 均采用函数式接口的方式实现了工厂模式,那么整个 WeakCache 的构造过程,也就是一个抽象工厂。KeyFactory 主要是获取接口(有可能多个)的散列码,而 ProxyClassFactory 则是代理类的生成过程。

private static final class KeyFactory
    implements BiFunction<ClassLoader, Class<?>[], Object>
{
    @Override
    public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
        switch (interfaces.length) {
            case 1: return new Key1(interfaces[0]); // the most frequent
            case 2: return new Key2(interfaces[0], interfaces[1]);
            case 0: return key0;
            default: return new KeyX(interfaces);
        }
    }
}
private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
    // 代理类前缀
    private static final String proxyClassNamePrefix = "$Proxy";
    // 代理类编号,递增。
    private static final AtomicLong nextUniqueNumber = new AtomicLong();
    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        String proxyPkg = null;     // package to define proxy class in
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;//java的描述符是通过2进制的方式实现的,标识明确,速度快,值得借鉴
        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) {
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        //获取动态代理的class文件,代码行4
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
        //将动态代理clss文件加载进内存
        return defineClass0(loader, proxyName,
                proxyClassFile, 0, proxyClassFile.length);
    }
}

再回过头看一眼,整个 WeakCache 架构。它最主要的是一个二维映射表,最外层 key 是 ClassLoader,subKey 是传入 interfaces 的 hash 值,value 是生成的代理类。key 值均采用弱引用处理,防止存储太多导致 OOM。关于弱引用 WeakReference,可以看java 强引用、软引用、弱引用、虚引用以及 FinalReference

final class WeakCache<K, P, V> {

    private final ReferenceQueue<K> refQueue
            = new ReferenceQueue<>();//引用队列
    private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
            = new ConcurrentHashMap<>();//真正的缓存用map
    private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
            = new ConcurrentHashMap<>();// 缓存失效机制
    private final BiFunction<K, P, ?> subKeyFactory;
    private final BiFunction<K, P, V> valueFactory;

    public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                     BiFunction<K, P, V> valueFactory) {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }

    public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);
        expungeStaleEntries();//清理map中已经没有引用对象的WeakReference
        Object cacheKey = CacheKey.valueOf(key, refQueue);//生成key值得WeakReference引用
        // lazily install the 2nd level valuesMap for the particular cacheKey
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                    = map.putIfAbsent(cacheKey,
                    valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }
        //生成subKey,也就是调用上面的KeyFactory的apply方法
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;
        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            // 懒加载一个Factory,这个Factory在内部调用了ProxyClassFactory
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }
            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    supplier = factory;
                }
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    supplier = factory;
                } else {
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }
}

生成的动态代理类去哪了?

查看“代码行 4”,是这行生成了动态代理的类文件。其中有个 boolean 类型:saveGeneratedFiles,是它控制了,是否输出 class 文件。所以我们在“代码行 1”中,将这个参数置为 true。

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);
              }
           }
      });
}

字节码

如果不做特殊处理的话,默认是在 com.sun.proxy 目录下,生成 Proxy0.class 文件。通过 javap -c '.Proxy0.class' 可以查看它的字节码。简单点,通过 idea 反编译出它的代码。

可以清楚看到,这个类同样实现了 Developer 接口,拥有一个带有 InvocationHandler 参数的构造函数,code()方法以及其他 Object 都有的方法。

  • h:InvocationHandler,初始化的时候传入了,它是一个匿名函数,有三个参数,proxy, method, args1
  • this:当前类,也就是参数 proxy
  • m3:Method 对象,通过 static 静态代码块实现了:m3 = Class.forName("Developer").getMethod("code");
  • args1:没有参数传入,所以为 null

以上。。。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序猿人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 简单实现
    • 1. 首先 jdk 的动态代理必须要有一个接口
      • 2. 被代理的类,实现了 Developer 接口,在真实的业务开发中,有时候可以没有这个类。
        • 3. 具体调用
          • 4. 结果
            • 5. 源码解读
            • 生成的动态代理类去哪了?
            • 字节码
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档