文章目录
前言
常用的动态代理实现有JDK动态代理以及CGLIB动态代理,本文对jdk动态代理的使用和原理做了粗浅的分析。
一、什么是动态代理?
动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。
代理类在程序运行期间,创建的代理对象称之为动态代理对象。这种情况下,创建的代理对象,并不是事先在Java代码中定义好的。而是在运行期间,根据我们在动态代理对象中的“指示”,动态生成的。也就是说,你想获取哪个对象的代理,动态代理就会为你动态的生成这个对象的代理对象。动态代理可以对被代理对象的方法进行功能增强。有了动态代理的技术,那么就可以在不修改方法源码的情况下,增强被代理对象的方法的功能,在方法执行前后做任何你想做的事情。
二、如何使用JDK动态代理
JDK动态代理所需要使用到得类为java.lang.reflect.Proxy
代码示例如下:
1 写一个被代理类
public class Runner implements Runnable {
@Override
public void run() {
Random random = new Random();
try {
TimeUnit.SECONDS.sleep(random.nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("run run run");
}
}
2 实现InvocationHandler
我们的代理逻辑就是实现在这个类里
public class CostTimeInvocationHandler implements InvocationHandler {
// 保存被代理对象
private final Runnable runnable;
public CostTimeInvocationHandler(Runnable runnable) {
this.runnable = runnable;
}
/**
* 在这个类实现代理逻辑
* @param proxy 代理对象
* @param method 调用得方法
* @param args 调用方法时得参数
* @return 原样返回
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
System.out.println("开始执行"+ name + "方法");
long s = System.nanoTime();
Object result = method.invoke(runnable, args);
long e = System.nanoTime();
System.out.println(name+"方法执行了"+ (e-s)/1000000 + "ms");
return result;
}
}
3 创建代理对象
public class Main {
public static void main(String[] args) {
Runner runner = new Runner(); //创建被代理对象
Runnable runnable = (Runnable)Proxy.newProxyInstance(runner.getClass().getClassLoader(),
new Class[]{Runnable.class},
new CostTimeInvocationHandler(runner));
runnable.run();
}
}
输出结果
开始执行run方法
run run run
run方法执行了2001ms
三 源码分析
Proxy暴露了四个静态方法
- isProxyClass(判断传入的类是否是代理类)
- getInvocationHandler(根据传入的代理对象返回与之关联的InvocationHandler对象)
- newProxyInstance(生成代理对象)
- getProxyClass (生成代理类)
isProxyClass
这个方法的作用是判断一个类是否是Proxy类生成的代理类
public static boolean isProxyClass(Class<?> cl) {
return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
}
可以看到实现很简单,主要是判断传入进来的类是否是Proxy类的子类以及proxyClassCache中是否包含了该类
看到这里,我们就应该要知道以下两点:
- 生成的代理类是Proxy类的派生类
- 创建出来的代理类被缓存在了一个叫proxyClassCache的缓存中
getInvocationHandler
这个方法更简单,判断传入的对象是否是代理对象,如果不是则抛出异常,否则强转为Proxy,返回该代理对象关联的InvocationHandler对象
简化后的代码如下
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException
{
if (!isProxyClass(proxy.getClass())) {
throw new IllegalArgumentException("not a proxy instance");
}
final Proxy p = (Proxy) proxy;
final InvocationHandler ih = p.h;
return ih;
}
newProxyInstance
简化后的代码如下
// loader 用来加载创建的代理类
// interfaces 代理类需要实现的接口,也可以说是代理哪些接口
// h 此类来实现具体的代理逻辑
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h); // 断言入参不为空
final Class<?>[] intfs = interfaces.clone(); // 克隆一份
Class<?> cl = getProxyClass0(loader, intfs); // 生成代理类
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}); // 调用构造方法
}
首先校验入参是否为空,如果为空则抛异常
然后克隆一份传入的interfaces,为什么要克隆一份呢?因为在缓存代理类的时候使用到了interfaces和ClassLoader作为缓存的Key,所以自然不希望key会被用户改变。
随后调用getProxyClass0来创建代理类,创建代理的具体实现下静态方法再分析。
最后便是获取代理类的构造函数,通过反射的方式创建代理对象。
getProxyClass
方法本身没什么逻辑,就是调用getProxyClass0而已
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
final Class<?>[] intfs = interfaces.clone();
return getProxyClass0(loader, intfs);
}
看看getProxyClass0的源码
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);
}
该方法校验了传入的接口数组的数量必须小于或等于65535,为什么要这个做呢?为什么是65535?
在字节码文件中,需要保存该类实现的接口,而java虚拟机规范是这么定义class文件格式的
interfaces_count表示该类实现接口的数量,它是使用两个字节存储的,所以如果超过两个字节所能表示的范围,那么是无法生成class的。传送门
校验接口数量后直接从proxyClassCache也就是缓存中获取代理类。看到这里肯定一脸问号,我明明是还没创建呢,怎么就从缓存中获取了呢?带着疑问看proxyClassCache是个什么鬼。
proxyClassCache的定义
/**
* a cache of proxy classes
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
好吧,继续看WeakCache的说明吧,WeakCache类上的注释是这么写的(机翻),应该还是能看懂的
缓存(key, sub-key) -> value映射对。 键和值是弱引用,但子键被强引用。 键直接传递给get方法,该方法也接受一个parameter 。 使用传递给构造函数的subKeyFactory函数根据键和参数计算子键。 使用传递给构造函数的valueFactory函数从键和参数计算值。 键可以为null并通过标识进行比较,而subKeyFactory返回的子键或valueFactory返回的值不能为空。 子键使用它们的equals方法进行比较。 当清除键的 WeakReferences 时,条目会在每次调用get 、 containsValue或size方法时从缓存中延迟删除。 清除对单个值的 WeakReferences 不会导致删除,但此类条目在逻辑上被视为不存在,并根据对其键/子键的请求触发对valueFactory重新评估。
构造函数
public WeakCache(BiFunction<K, P, ?> subKeyFactory,
BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}
读了文档就知道当没有找到对应的缓存也就是代理类时,将会调用valueFactory去生成,这下直接看ProxyClassFactory的实现就行了。
ProxyClassFactory类是Proxy的内部类,源码如下
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 生成的代理类的名字的前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 原子Long类型,每个代理类都有唯一的序号,就是通过这个生成的
private static final AtomicLong nextUniqueNumber = new AtomicLong();
// 这里的loader就是入参一路传过来的,interfaces就是入参的克隆
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* 验证类加载器是否将此接口的名称解析为相同的 Class 对象,这里就是类加载相关的知识了
* 简略的说,每个类在虚拟机的唯一标识是该类本身和加载它的类加载器共同组成的
* 一个行为良好的类加载器对于名字相同的类总是返回相同的Class对象(行为良好的类加载器的定义参考java语言规范)
* @see <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.2"></a>
*/
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");
}
/*
* 如果不是接口直接报错
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* 如果重复直接报错
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
*这里进行包校验,如果有多个非public的接口且它们属于不同的包,那么就无法生成合法的class
*/
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) {
// 如果没有非公共代理接口,使用 com.sun.proxy 包
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* 所有的校验都过了,开始生成代理类
*/
long num = nextUniqueNumber.getAndIncrement(); // 生成代理类的唯一ID
String proxyName = proxyPkg + proxyClassNamePrefix + num; // 组装代理类的全限定类名
/*
* 调用ProxyGenerator的generateProxyClass方法去生成class的字节数组
* javac编译一个类保存为文件,这个文件是符合字节码文件规范的
* 实际上,用java代码生成一个byte数组,只要byte数组的格式符合虚拟机规范
* 就能被加载进来
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 此方法为native的方法,其功能就是加载生成的代理类
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
以上是ProxyClassFactory的代码,实际上它只是做了很多校验,然后生成类名,随后调用ProxyGenerator的generateProxyClass方法生成字节码。继续看ProxyGenerator类
generateProxyClass方法
/**
* Generate a proxy class given a name and a list of proxy interfaces.
*
* @param name 类名
* @param interfaces proxy interfaces
* @param accessFlags access flags of the proxy class
*/
public static byte[] generateProxyClass(final String name,
Class<?>[] interfaces,
int accessFlags)
{
// 创建ProxyGenerator类实例,生成字节码
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
final byte[] classFile = gen.generateClassFile();
// 如果需要保存为文件就执行保存操作
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
int i = name.lastIndexOf('.');
Path path;
if (i > 0) {
Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
Files.createDirectories(dir);
path = dir.resolve(name.substring(i+1, name.length()) + ".class");
} else {
path = Paths.get(name + ".class");
}
Files.write(path, classFile);
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
return classFile;
}
该方法调用generateClassFile方法去真正的生成字节码,随后判断是否需要保存,如果需要保存就保存。
private final static boolean saveGeneratedFiles =
java.security.AccessController.doPrivileged(
new GetBooleanAction(
"sun.misc.ProxyGenerator.saveGeneratedFiles")).booleanValue();
从这个字段的定义来看,只要我们在启动时设置对应的系统属性就能把字节码文件给保存下来,试试吧
可以看到确实生成了class文件,使用Idea打开,可以看到反编译后的结果,一切都跟之前看源码分析的那样,代理类继承了Proxy,实现了我们传入的接口
接下来继续看generateClassFile方法
private byte[] generateClassFile() {
/*
*将hashcode,equals,toString 方法加入需要拦截的方法列表
*/
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
/*
*将每个接口的方法加入需要拦截的方法列表
*/
for (Class<?> intf : interfaces) {
for (Method m : intf.getMethods()) {
addProxyMethod(m, intf);
}
}
/*
* 一些校验操作,主要是为了满足字节码的规范
*/
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
try {
// 添加构造器
methods.add(generateConstructor());
// 为正在生成的类中的所有字段和方法组装 FieldInfo 和 MethodInfo 结构
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());
}
}
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (Class<?> intf: interfaces) {
cp.getClass(dotToSlash(intf.getName()));
}
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
/*
* 按照字节码的格式写入
*/
// 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);
}
return bout.toByteArray();
}
方法里的内容只需要稍微了解字节码的格式就能理解,本质上就是按照字节码文件的规范,一步步写进去
总结
本文对jdk动态代理做了一点简单的分析。