java
proxy
因为最近一段时间准备将这几年做的一些业务和技术做个沉淀,也自己造的一些轮子,发现时不时就会需要用到动态代理和反射,所以今天打算先对jdk
的动态代理这部分内容做个简单的整理
介绍
先来说说怎么理解动态代理吧
首先在java
中有一种模式叫代理模式,代理的定义是
代理是指以他人的名义,在授权范围内进行对被代理人直接发生法律效力的法律行为
对应到代理模式
- 以他人名义 > 代理对象
- 授权范围内 > 基于接口或类的规范限制(如无法代理接口或类中不存在的方法等)或是可实现的功能范围(如无法获得被代理方法的中间变量等)
- 被代理人 > 被代理的对象
- 法律 > java中的规范(如访问限制等)
- 直接发生法律效力 > 直接影响方法执行逻辑(如在前后打印日志或是修改入参返回值,对方法的最终执行结果产生了影响)
- 法律行为 > 及代理本身的行为(符合java规范)
所以代理模式就是在通过代理对象调用被代理对象的方法过程中改变原有方法的执行逻辑
好了,完全被自己绕晕了,其实代理能做到的功能完全可以枚举出来
- 修改入参
- 修改返回值
- 异常处理
- 日志打印
- 完全重写
一般也就用到这些功能
而所谓的动态代理,就是这个代理对象是通过代码动态生成的,也就是用代码生成代码,经典套娃了属于是
使用场景
对于动态代理的使用场景,主要还是用于一些大型框架中
其中一个场景就是
Spring
利用动态代理实现强大的切面功能另一个场景就是
Feign
在接口上添加注解来实现HTTP
的调用,或是MyBatis
在接口上添加注解来执行SQL
等等
方式
主流的动态代理有2种:jdk
动态代理和cglib
动态代理
说说这两种方式的区别
-
jdk
动态代理只能代理接口(统一继承Proxy
类),cglib
动态代理可以代理接口也可以代理类(通过生成子类,所以不能被final
修饰) -
jdk
动态代理直接写Class
文件,cglib
借助ASM
框架处理Class
文件 -
jdk
动态代理方法内调用其他方法无法被代理(通过某个实现类调用方法时),cglib
动态代理方法内调用的其他方法也可以被代理
接下来详细聊聊jdk
动态代理吧,由于篇幅原因cglib
动态代理考虑另开一篇
JDK动态代理
基于jdk8
用法
用法就简单过一下吧
public interface JdkDynamicProxy {
String methodToProxy();
}
JdkDynamicProxy target = new JdkDynamicProxy() {
@Override
public String methodToProxy() {
return "Target method to proxy";
}
};
JdkDynamicProxy proxy = (JdkDynamicProxy) Proxy
.newProxyInstance(JdkDynamicProxy.class.getClassLoader(),
new Class[]{JdkDynamicProxy.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy,
Method method,
Object[] args)
throws Throwable {
System.out.println(method.getName());
return method.invoke(target, args);
}
});
System.out.println(proxy.methodToProxy());
用法很简单,调用Proxy.newProxyInstance
方法就能生成一个代理对象,然后就可以在被代理对象的方法调用的前后做一些有限的事情
参数
接下来说说这个方法的参数
先说第二个参数,传入一个类型为Class<?>[]
,名称为interfaces
的对象,也就是你要代理的接口,因为java
可以实现多个接口,所以是一个数组
再来说一下第三个参数InvocationHandler
,这是一个接口,当你通过代理对象调用接口的方法时,就会回调该接口的invoke
方法并回传代理对象,方法和方法入参
最后说第一个参数,需要传入一个ClassLoader
,一般情况下你用这个项目里面任何一个能得到的类加载器都没问题,这个类加载器的主要作用就是校验其可以访问指定的接口以及加载这个代理类
//省略部分代码
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
//校验类加载器可以访问指定的接口
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
}
//使用类加载器定义类
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}
流程
接下来就说说生成代理对象的整个流程
克隆接口数组
final Class<?>[] intfs = interfaces.clone();
首先会对我们传入的第二个参数,也就是接口数组进行拷贝
这里我猜想可能是防止数组中的元素在生成代理的过程中被修改导致出现一些问题,不得不佩服果然是心思缜密啊
接口数量检查
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
这边限制了一个接口的数量为65535,那么这个65535是怎么来的呢
如果大家对Class
文件结构有一定了解的话就知道文件中会通过interfaces_count
定义接口数量,该数据项占2个字节,所以最大能表示65535
查找缓存
// 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);
然后会在缓存中查找,这是肯定的,不然每次都重新生成一遍也太蠢了
这个缓存用的是WeakCache
对象,通过两个key
来定位一个value
,就是通过我们传入的类加载器和接口数组来定位一个动态生成的代理类,类似于Map<ClassLoader, Map<Class<?>[], Class<? entends Proxy>>>
// the key type is Object for supporting null key
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
= new ConcurrentHashMap<>();
而他使用的key
和value
都是WeakReference
类型,防止当动态生成的类不再使用时导致内存泄漏的问题
第一个key
,使用我们传入的ClassLoader
为key
//key为我们传入的ClassLoader
Object cacheKey = CacheKey.valueOf(key, refQueue);
private static final class CacheKey<K> extends WeakReference<K> {
//代码省略
}
其中refQueue
是ReferenceQueue
类型(不了解的可以看一下WeakReference
的构造器)而且都是同一个对象,ReferenceQueue
是属于gc
回收这部分的内容了,先不展开了吧
猜想由于ClassLoader
可能是URLClassLoader
甚至一些自定义的类加载器,就有可能导致内存泄漏,所以也用了弱引用
另外需要注意这个key
也就是类加载器是可以为null
的,猜测为null
时被判定为bootstrap
类加载器
但是如果我们传入的类加载器为null
就可能会报错xxx is not visible from class loader
,这是为什么呢
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
可以看到通过用我们传入的类加载器重新加载接口并判断和我们传入的接口是否相等来做校验
我们都知道两个Class
是否相等是需要满足全限定名相等和类加载器相等这两个条件的,而如果我们传入null
也就是使用bootstrap
类加载器,那么和接口的类加载器AppClassLoader
是两个不同的类加载器,导致两个类不相等抛出异常
简单来说,就是直接使用接口的类加载器就完事儿了
那么这个类加载器到底应该怎么传呢
如果是我们自定义的接口,那么需要传入AppClassLoader
(或者是加载该接口的类加载器,当然也可以是以对应类加载器为父类加载器的自定义类加载器)而不能传入null
如果我们要代理的接口是java.util.List
这种,那就可以传null
(本身就是由bootstrap
类加载器加载)或者AppClassLoader
(基于双亲委派模型能够加载java.util.List
)
这里就不展开类加载器了(强制拉回)
继续讲第二个key
,第二个key
使用ClassLoader
和接口数组生成
// create subKey and retrieve the possible Supplier<V> stored by that
// subKey from valuesMap
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
这里通过subKeyFactory
来生成一个subKey
,subKeyFactory
是KeyFactory
对象
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
不过KeyFactory
完全没用到ClassLoader
,所以其实就是接口数组作为key
动态生成代理类
如果缓存中不存在,那么就需要通过ProxyClassFactory
来生成一个代理类
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
valueFactory
也就是ProxyClassFactory
对象,我们来看看ProxyClassFactory
是怎么生成代理类的
接口校验
首先会对我们传入的接口进行ClassLoader
的校验
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
这块前面已经大致分析过了,即我们传入的ClassLoader
需要是这些接口的类加载器
然后判断这些接口(由于接口也是通过Class
表示所以需要额外校验)必须为接口
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
最后这些接口不能重复
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
设置访问标志
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
首先会将代理类的访问标志设置为public
和final
然后会判断需要代理的接口,如果存在不是public
的接口,则把代理类的访问标志改为final
,并且将代理类的包名设置为非public
接口的包名,如果有多个非public
接口,就需要判断这些非public
的接口包名是否一样,否则抛出异常(因为如果不是public
就无法被其他包访问到)
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
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) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
如果上一步没有非public
包名时,那么指定包名为com.sun.proxy
,否则使用那个非public
接口的包名
指定类名
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
其中proxyClassNamePrefix
为常量$Proxy
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
所以最后生成的代理类的全名就是com.sun.proxy.$Proxy0
,com.sun.proxy.$Proxy1
,com.sun.proxy.$Proxy2
以此类推(所有接口都是public
的情况下)
生成类文件
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
public static byte[] generateProxyClass(final String name,
Class<?>[] interfaces,
int accessFlags) {
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
final byte[] classFile = gen.generateClassFile();
//省略部分代码
}
将代理类的全限定名,需要实现的接口,访问标志交给ProxyGenerator
去生成类文件
接下来看看generateClassFile
方法做了什么
添加Object方法
/*
* Record that proxy methods are needed for the hashCode, equals,
* and toString methods of java.lang.Object. This is done before
* the methods from the proxy interfaces so that the methods from
* java.lang.Object take precedence over duplicate methods in the
* proxy interfaces.
*/
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
首先会添加hashCode
,equals
和toString
这3个Object
的方法
添加接口方法
/*
* Now record all of the methods from the proxy interfaces, giving
* earlier interfaces precedence over later ones with duplicate
* methods.
*/
for (Class<?> intf : interfaces) {
for (Method m : intf.getMethods()) {
addProxyMethod(m, intf);
}
}
接着将需要被代理的所有接口方法也添加进去
方法返回值校验
/*
* For each set of proxy methods with the same signature,
* verify that the methods' return types are compatible.
*/
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
之前我们添加的所有方法都会保存在proxyMethods
中,然后会校验所有同名同入参方法的返回值
由于java
中不允许方法名相同,入参相同但是返回值不同的方法定义,比如
public interface Demo {
String demo(String s);
int demo(String s);
}
而我们平时写代码时,上面的写法就直接报红了
添加构造方法
methods.add(generateConstructor());
接着添加构造方法,generateConstructor
里面的内容已经是用byte[]
来拼Class
文件的内容了,就不跟进去讲Class
文件结构的内容了
添加静态方法变量
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
// add static field for method's Method object
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));
// generate code for proxy method and add it
methods.add(pm.generateMethod());
}
}
这里其实光看代码会有点困惑,不过如果看过最终生成的代理类就会比较好理解
这边会把所有的方法都添加为代理类的private
和static
属性字段,大家还记得InvocationHandler
的invoke
方法传回来的其中一个参数就是Method
对象,实际上这个Method
对象就是这里添加的字段
而最后的generateMethod
方法也是拼接Class
文件内容
添加静态代码块的初始化方法
methods.add(generateStaticInitializer());
这里主要就是初始化上面定义的Method
类型的字段,因为我们上面只是为每个方法都添加了一个Method
类型的字段定义,但是这个字段并没有赋值,就是在静态代码块中对这些字段进行了赋值
校验字段和方法的数量
if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}
和接口数量一样,方法数量和字段数量在Class
文件中也是用2个字节表示的,所以都有65535的限制
拼接类文件
/* ============================================================
* Step 3: Write the final class file.
*/
/*
* Make sure that constant pool indexes are reserved for the
* following items before starting to write the final class file.
*/
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (Class<?> intf: interfaces) {
cp.getClass(dotToSlash(intf.getName()));
}
/*
* Disallow new constant pool additions beyond this point, since
* we are about to write the final constant pool table.
*/
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
/*
* Write all the items of the "ClassFile" structure.
* See JVMS section 4.1.
*/
// u4 magic;
dout.writeInt(0xCAFEBABE);
// u2 minor_version;
dout.writeShort(CLASSFILE_MINOR_VERSION);
// u2 major_version;
dout.writeShort(CLASSFILE_MAJOR_VERSION);
cp.write(dout); // (write constant pool)
// u2 access_flags;
dout.writeShort(accessFlags);
// u2 this_class;
dout.writeShort(cp.getClass(dotToSlash(className)));
// u2 super_class;
dout.writeShort(cp.getClass(superclassName));
// u2 interfaces_count;
dout.writeShort(interfaces.length);
// u2 interfaces[interfaces_count];
for (Class<?> intf : interfaces) {
dout.writeShort(cp.getClass(
dotToSlash(intf.getName())));
}
// u2 fields_count;
dout.writeShort(fields.size());
// field_info fields[fields_count];
for (FieldInfo f : fields) {
f.write(dout);
}
// u2 methods_count;
dout.writeShort(methods.size());
// method_info methods[methods_count];
for (MethodInfo m : methods) {
m.write(dout);
}
// u2 attributes_count;
dout.writeShort(0); // (no ClassFile attributes for proxy classes)
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
这里其实没啥好说的,这是Class
文件结构的内容了,包括上面的generateConstructor
,generateMethod
,generateStaticInitializer
都是类似的代码
重点讲一下这句代码
// u2 super_class;
dout.writeShort(cp.getClass(superclassName));
这里的superclassName
为常量java/lang/reflect/Proxy
/** name of the superclass of proxy classes */
private final static String superclassName = "java/lang/reflect/Proxy";
所以所有的代理类的父类都是Proxy
这个类,这也就是jdk
动态代理只能代理接口的原因(java
不允许多继承)
定义类
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
这个方法是native
的,就不深入了
总结
是不是突然觉得可以手撸一套定制化的动态代理框架了(如果对Class
文件比较熟悉,或者借助ASM
)
其实jdk
动态代理的整个逻辑并没有多复杂,无非就是按照Class
文件的结构要求拼接各个部分数据,但是在整个过程中做了很多校验的逻辑
相对应我们平时的开发,业务功能其实很多情况下都不复杂甚至还非常简单,但是业务定义之外的边缘数据的校验和适配也不能马虎,又或是对于一些并发等其他场景下的风险考虑,逻辑严密性比功能实现来的更为重要