JDK动态代理是代理模式的一种,且只能代理接口。spring也有动态代理,称为CGLib,现在主要来看一下JDK动态代理是如何实现的?
一、介绍
JDK动态代理是有JDK提供的工具类Proxy实现的,动态代理类是在运行时生成指定接口的代理类,每个代理实例(实现需要代理的接口)都有一个关联的调用处理程序对象,此对象实现了InvocationHandler,最终的业务逻辑是在InvocationHandler实现类的invoke方法上。
也即是在invoke方法上可以实现原方法中没有的业务逻辑,相当于spring aop的@Before、@After等注解。
二、样例
(1)接口
public interface ProxySource { void test(); }
(2)实现类
public class ProxySourceImpl implements ProxySource { @Override public void test() { System.out.println("原有业务逻辑"); } }
(3)实现InvocationHandler接口和invoke方法
static class MyHandler implements InvocationHandler { //需要代理的类 Object target; public MyHandler(Object target) { this.target = target; } /** * @param proxy 动态代理实例 * @param method 需要执行的方法 * @param args 方法中参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("do other things befor"); method.invoke(target, args); System.out.println("do other things after"); return null; } }
(4)利用Proxy实现代理类
//此参数设置是为了保存生成代理类的字节码文件 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); ProxySource proxySource = (ProxySource) Proxy.newProxyInstance( ProxySourceImpl.class.getClassLoader(), ProxySourceImpl.class.getInterfaces(), new MyHandler(new ProxySourceImpl()) ); //执行方法 proxySource.test();
(5)方法调用结果
可以看到,在原有方法执行前后都执行了其他代码。
三、源码分析(主要看Proxy.newProxyInstance方法,省略非核心代码)
(1)newProxyInstance方法
Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) { final Class>[] intfs = interfaces.clone(); //获取代理接口class Class> cl = getProxyClass0(loader, intfs); //获取到class之后用反射获取构造方法,然后创建代理类实例 try { final Constructor> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; return cons.newInstance(new Object[]{h}); } catch (Exception e) { throw new InternalError(e.toString(), e); } }
由上述代码可知,主要是通过getProxyClass0方法获取到代理接口的class
(2)getProxyClass0方法
Class> getProxyClass0(ClassLoader loader, Class>... interfaces) { //如果已经有相应的字节码文件,则之间返回,否则通过代理类工厂创建代理类 return proxyClassCache.get(loader, interfaces); }
而proxyClassCache又是什么东东呢?
WeakCache[], Class>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
proxyClassCache是一个WeakCache对象,可知这是一个缓存对象,这个类结构是通过ConcurrentHashMap实现的,
ConcurrentMap
数据结构是(key,sub-key)->value
存的值也就是
(3)get方法
public V get(K key, P parameter) { Object cacheKey = CacheKey.valueOf(key, refQueue); //根据classloader为key查看缓存中是否已有 ConcurrentMap
这个supplier.get方法点进去,核心就是ProxyClassFactory的apply方法
(4)ProxyClassFactory的apply方法
Class> apply(ClassLoader loader, Class>[] interfaces) { for (Class> intf : interfaces) { Class> interfaceClass = null; try { //通过类权限定名反射获取class interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } //判断是否可以通过系统加载器加载 if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } //校验是否是接口 if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } } long num = nextUniqueNumber.getAndIncrement(); //生成代理类的权限定名,例如com.sun.proxy.$Proxy0 String proxyName = proxyPkg + proxyClassNamePrefix + num; //生成字节码文件 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { //调用本地方法生成class return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } }
上述代码中核心是生成字节码,即是ProxyGenerator.generateProxyClass方法。
(5)ProxyGenerator.generateProxyClass方法。
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) { //保存生成代理类的字节码文件 } return var4; }
上述代码中saveGeneratedFiles点进去是这样的,也即是开头样例中的设置属性。
private static final boolean saveGeneratedFiles = ((Boolean)AccessController .doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();
但主要还是ProxyGenerator的generateClassFile这个方法。
其默认给代理类生成了hashcode、equals和toString方法,也限制了代理接口和字段都不能超过65535个。
现在来看一下保存的代理类字节码文件是怎么样的(通过idea反编译后)
public final class $Proxy0 extends Proxy implements ProxySource { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { //代码省略 } public final void test() throws { //h是invocationhandler,所以最后是执行invoke方法 super.h.invoke(this, m3, (Object[])null); } public final String toString() throws { //代码省略 } public final int hashCode() throws { //代码省略 } static { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("com.liusy.lang.ProxySource").getMethod("test"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } }
可以看到,生成的$Proxy0继承了Proxy,实现了我定义的接口ProxySource,里面有四个方法,m0~m3,通过静态代码块中根据类的全限定名和方法名反射获取,而最后是执行InvocationHandler的invoke方法。
至此,JDK动态代理已经说完,希望对你有所帮助。
=======================================================
我是Liusy,一个喜欢健身的程序员。
欢迎关注微信公众号【Liusy01】,一起交流Java技术及健身,获取更多干货。