JDK1.8 动态代理浅谈
由来
作为GOF23种设计模式中结构型设计模式中的一种, 代理模式在我们项目工程中算是较为常见的了。见名知意,代理就像是中间人一样控制客户端对真实主题(Real Subject)的访问,通过访问代理对象, 客户端能获得比直接访问真实主题更加强大的功能,同时也被限制了部分对真实主题破坏性的操作,起到了在访问类与真实主题之间解耦的作用。 在很多场景下,访问类只要知道抽象主题定义的功能方法,通过访问代理类(此时代理类以抽象主题的形式对外暴露)就能间接调用到真实主题的能力。
最近本人的项目使用了开源框架Feign, Feign在实现中采用了大量JDK提供的动态代理(DynamicProxy)技术在客户端创建了远程服务绑定的接口的实现,通过调用这些远程代理对象,一个对外的服务工程就能够像调用本地服务一样对能力层进行一次RPC调用(Feign透明了http请求的细节),出于好奇,本人写了这片文章尝试对技术实现细节进行一次浅析
注:本文仅包含作者本文理解,各位大佬有任何不同见解欢迎留言指导
动态代理 -> 代理模式PLUS
与其拿动态代理与代理模式进行比较不如说动态代理是常规代理模式的一种扩展,对应代理模式讲解本人推荐阅读文章代理模式,文章中较为清楚地描述了常规代理模式的局限性 -> 真实主题与代理类必须一一对应,这一点导致在软件开发中无可避免地创建大量结构上极其相似的类与对象(需要注意的是在动态代理中,代理类的创建往往是对开发者透明的->简单来讲就是你不用写类文件了哦)。
设计模式是为了最大程度上实现代码复用性, 减少重复工作, JDK提供对动态代理的支持非常友好地扩展了传统代理模式,让软件开发者通过使用这些库依赖支持能够既实现代理的目的又减少了重复工作量.
上图是本人基于JDK 1.8的java.lang.reflect.Proxy与相关类画的简化版结构图,不同于传统代理模式中代理类与真实主题的一一对应,动态代理这门技术的实现中采用了桥接模式在功能层与实现层上进行了分离(设计模式与设计模式之间不应该是绝对隔离的!),而连接功能层次与实现层次的桥梁就是这个InvocationHandler,通过在代理类(此时为ProxyObj)中维护的InvocationHandler对象,代理类就能够愉快地访问真实主题(RealSubj) 了->InvocationHandler与真实主题是依赖关系, invoke(…)方法的实现中往往引用了真实主题.
对于java.lang.reflect.*相关API的调用案例可以参考Feign源码:
/**部分代码,有兴趣可以读读feign.ReflectiveFeign**/
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
实现小细节(JDK 1.8)
java.lang.reflect.Proxy是动态代理的核心类,具有私有的无参构造函数,客户端通过调用静态方法newProxyInstance(…)能够创建代理类(创建过程对客户端透明)并且获得一个代理对象实例.
创建代理类的代码为:
/*
* 其中loader与intfs分别为调用newProxyInstance(...)时传递的类加载器与接口Class信息
*/
Class<?> cl = getProxyClass0(loader, intfs);
能看到先尝试从一弱缓存对象中读取信息, 弱缓存对象中维护了用于创建代理类的工厂:
//弱缓存对象中维护了用于创建代理类的工厂
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
//省略部分代码.......
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//省略部分代码.......
return proxyClassCache.get(loader, interfaces);
}
这个创建代理类的工厂使用了sun.misc.ProxyGenerator实现了代理类二进制数据的创建
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
//proxyClassNamePrefix的值为$Proxy,难怪用动态代理实现的类名开头都是这个格式
String proxyName = proxyPkg + proxyClassNamePrefix + num;
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
//...省略部分注解
throw new IllegalArgumentException(e.toString());
}
ProxyGenerator来进行文件的写入
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;
}
JVM调用C语言的API实现二进制数据与类信息对象的转换
private static native Class<?> defineClass0(ClassLoader loader, String name,
byte[] b, int off, int len);
让我们回到Proxy类,在Proxy类中通过上述创建的*.Class对象通过反射构建一个代理对象
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
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});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
总结
本文基于JDK1.8对原生动态代理的实现进行了一次浅析, sun.misc.ProxyGenerator提供了非常强大的代理类实现功能(有兴趣可以读读),本文基于作者的分析而写,如果有不足点请大佬们指点,转载烦请注明出处