Java jdk动态代理Proxy的核心源码分析

上篇介绍过Java代理的定义和使用,下面我们分析下jdk1.8中的Proxy类的核心源码,想想就兴奋,坚持10分钟看完不睡着!或者睡着更好,能帮你入眠,我也很开心!甚至还发现了源码中有无用的代码或者作者忘记使用的代码,一起来看吧!

分析newProxyInstance逻辑

话不多说,直接看源码,最核心的newProxyInstance方法

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {
		Objects.requireNonNull(h);

		// 复制一份代理接口类型数组,以防止代理接口类型数组被外部修改导致不一致的问题。
		final Class<?>[] intfs = interfaces.clone();
		// 安全检查,先不用看
		final SecurityManager sm = System.getSecurityManager();
		if (sm != null) {
			checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
		}
 		/*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        ...省略
}
  • ClassLoader参数为类加载器
  • interfaces参数为实现的接口,可以传数组
  • InvocationHandler参数为InvocationHandler的实现类,这个实现类的构造函数入参需要包含目标实现的接口类

getProxyClass0 获取或生成代理类

方法返回了代理类,其内部如下:

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

根据源码名称和注释可知,如果代理类已经存在,那么直接返回缓存中的代理类;否则,通过ProxyClassFactory生成代理对类,进入WeakCache类大概看下get方法,内部使用了ConcurrentMap来实现缓存,如果不存在时将会使用Proxy中的静态内部类ProxyClassFactory来生成代理类字节码,内部会去调用ProxyGenerator.generateProxyClass去实现:

  private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
        // prefix for all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";
        ...
          @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

        ...
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
            ...
     }

cue一下WeakCache

还要注意下,WeakCache是一个弱引用的缓存!说到弱引用,学习过jvm相关知识的同学应该有些印象,没听过也没关系,我们只要知道这个概念就好,当缓存的对象不再被其他对象引用时,就会被垃圾回收掉。此处埋个坑,以后写篇弱引用及弱引用缓存的使用文章。

这么说还不形象是吧,想象一下你是一个舔狗,当女神需要时你有用,女神不需要你会被一个叫GC的爪机丢到垃圾桶里。可是,女神又有什么错呢?是你自己不够好罢了!

在这里插入图片描述
说回正题,这种先获取缓存,缓存没有再生成的编程思想在很多时候都有使用。

反射创建代理类对象

至此,我们已经获取到代理类,存在于内存中。那下面就得创建一个代理类对象并返回了,别忘了我们的初衷,newProxyInstance创建一个代理实例对象。那如何创建呢?没错,就是反射,直接看剩下的代码:

 		/*
         * Invoke its constructor with the designated invocation handler.
         * 使用特定的InvocationHandler调用动态代理类的构造函数
         */
        try {
        	// 安全管理器是否允许当前执行的代码创建新的动态代理对象
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        }
        ... 省略

获取到代理类的构造函数,并将入参的InvocationHandler h作为构造函数参数创建代理类对象,并返回。最终我们得到一个代理类对象实例。

总结newProxyInstance流程图

我将newProxyInstance关键步骤总结画图如下,用了三个软件画的图,难搞:
在这里插入图片描述

代理类执行方法的底层步骤

代理类字节码文件

你应该好奇,上面的代理类字节码是加载生成在内存中,我们也看不到具体什么样子,说着很玄乎,我们能否将动态生成的字节码文件以文件形式输出呢?当然可以!有两种方式:

  1. 设置系统参数(不同jdk版本可能不一致)
    查看jdk中的ProxyGenerator类中的常量saveGeneratedFiles
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));

在main方法内部,设置系统属性:

  public static void main(String[] args) {
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        Class[] interfaces = {UserDao.class};
        UserDao dao = new UserDaoImpl();
        UserDao userDao = (UserDao) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), interfaces, new MyInvocationHandler(dao));
        userDao.add(1, 2);
    }

在项目根路径会生成com.sun.proxy.$Proxy0.class文件
在这里插入图片描述
为什么设置这个参数就可以生成?因为在generateProxyClass方法内部会根据常量值是否write文件

 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);
                    }
                }
            });
        }
  1. 自己手动写文件,代码参考如下:
	public static void main(String[] args) {
        Class[] interfaces = {UserDao.class};
        genClassByProxy(interfaces);
    }

	private static void genClassByProxy(Class<?>[] interfaces) {
        byte[] proxyClassBytes = ProxyGenerator.generateProxyClass("$Proxy0", interfaces);
        // 将字节码输出到文件 "ProxyClass.class" 中
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("E:\\diy\\java-study-master\\spring-study\\src\\main\\java\\com\\spring\\proxy\\" + "$Proxy0.class");
            fos.write(proxyClassBytes);
            fos.flush();
        } catch (IOException e) {
            if (fos!=null){
                try {
                    fos.flush();
                    fos.close();
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
            e.printStackTrace();
        }
    }

执行main方法我们将会在指定目录下生成一个$Proxy0.class文件,打开该文件我们就揭开了动态代理的面纱。
哦~ 莎莉哇莎莉哇 ~ 是谁送你来到我身边~ 我身边~
在这里插入图片描述

import com.spring.dao.UserDao;
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 UserDao {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m4;
    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 int add(int var1, int var2) throws  {
        try {
            return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    ...

    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.spring.dao.UserDao").getMethod("add", Integer.TYPE, Integer.TYPE);
            m4 = Class.forName("com.spring.dao.UserDao").getMethod("sub", Integer.TYPE, Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

这个生成的代理类继承了Proxy类,实现目标类接口。我们还可以看到,代理类中:

  1. 有一个构造函数,参数为InvocationHandler
  2. static静态代码块内,通过反射的方式加载了目标类的方法
  3. 重写了目标类的方法和一些如haschCode,equals,toString等方法。重写的方法内部,都是执行InvocationHandler.invoke执行目标方法

理解JDK的Proxy动态代理

至此,你应该理解,为什么写Jdk动态代理需要先写一个InvocationHandler实现类,并传入目标类了吧

class MyInvocationHandler implements InvocationHandler {

    private Object object;

    public MyInvocationHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法执行前执行" + method.getName() + Arrays.toString(args));
        Object res = method.invoke(object, args);
        System.out.println("方法执行后执行" + res);
        return res;
    }
}

总结使用JDK动态代理执行方法顺序如下:

  1. 创建代理类对象
  2. 通过代理类对象,调用对应方法
  3. 代理类方法,调用自定义的InvocationHandler 方法invoke(this, m3, new Object[]{var1, var2});传入三个参数:当前代理类,目标类方法和方法入参
  4. 自定义的InvocationHandler 方法可以加上方法执行前后的操作,实际通过反射调用method.invoke(object, args);加载目标类方法

发现proxy源码的优化点

在Proxy类中,创建代理类对象时,有将invacationHandler声明为final变量的操作,

 final InvocationHandler ih = h;

但是后续没有使用,你可以自己打开1.8的源码查看,可能是作者后面忘记使用了吧,即return cons.newInstance(new Object[]{h});应该改成return cons.newInstance(new Object[]{ih});不知道高版本的java是否优化这个地方。

 final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});

至此,打完收工!码字不易,感谢你的坚持观看!
如果您对技术有兴趣,友好交流,可以加v进技术群一起沟通,v:zzs1067632338,备注csdn即可

在这里插入图片描述

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值