携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第16天,点击查看活动详情
Java反射提供了一种类动态代理机制,可以通过代理接口实现类来完成程序无侵入式扩展。
Java动态代理主要使用场景:
- 统计方法执行所耗时间。
- 在方法执行前后添加日志。
- 检测方法的参数或返回值。
- 方法访问权限控制。
- 方法
Mock测试。
动态代理API
创建动态代理类会使用到java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。java.lang.reflect.Proxy主要用于生成动态代理类Class、创建代理类实例,该类实现了java.io.Serializable接口。
java.lang.reflect.Proxy类主要方法如下:
package java.lang.reflect;
import java.lang.reflect.InvocationHandler;
/**
* Creator: yz
* Date: 2020/1/15
*/
public class Proxy implements java.io.Serializable {
// 省去成员变量和部分类方法...
/**
* 获取动态代理处理类对象
*
* @param proxy 返回调用处理程序的代理实例
* @return 代理实例的调用处理程序
* @throws IllegalArgumentException 如果参数不是一个代理实例
*/
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException {
...
}
/**
* 创建动态代理类实例
*
* @param loader 指定动态代理类的类加载器
* @param interfaces 指定动态代理类的类需要实现的接口数组
* @param h 动态代理处理类
* @return 返回动态代理生成的代理类实例
* @throws IllegalArgumentException 不正确的参数异常
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException {
...
}
/**
* 创建动态代理类
*
* @param loader 定义代理类的类加载器
* @param interfaces 代理类要实现的接口列表
* @return 用指定的类加载器定义的代理类,它可以实现指定的接口
*/
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) {
...
}
/**
* 检测某个类是否是动态代理类
*
* @param cl 要测试的类
* @return 如该类为代理类,则为 true,否则为 false
*/
public static boolean isProxyClass(Class<?> cl) {
return java.lang.reflect.Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
}
/**
* 向指定的类加载器中定义一个类对象
*
* @param loader 类加载器
* @param name 类名
* @param b 类字节码
* @param off 截取开始位置
* @param len 截取长度
* @return JVM创建的类Class对象
*/
private static native Class defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);
}
java.lang.reflect.InvocationHandler接口用于调用Proxy类生成的代理类方法,该类只有一个invoke方法。
java.lang.reflect.InvocationHandler接口代码(注释直接搬的JDK6中文版文档):
package java.lang.reflect;
import java.lang.reflect.Method;
/**
* 每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并
* 将其指派到它的调用处理程序的 invoke 方法。
*/
public interface InvocationHandler {
/**
* 在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
*
* @param proxy 在其上调用方法的代理实例
* @param method 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明
* 方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
* @param args 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,
* 则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer
* 或 java.lang.Boolean)的实例中。
* @return 从代理实例的方法调用返回的值。如果接口方法的声明返回类型是基本类型,
* 则此方法返回的值一定是相应基本包装对象类的实例;否则,它一定是可分配到声明返回类型的类型。
* 如果此方法返回的值为 null 并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出
* NullPointerException。否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,
* 则代理实例上的方法调用将抛出 ClassCastException。
* @throws Throwable 从代理实例上的方法调用抛出的异常。该异常的类型必须可以分配到在接口方法的
* throws 子句中声明的任一异常类型或未经检查的异常类型 java.lang.RuntimeException 或
* java.lang.Error。如果此方法抛出经过检查的异常,该异常不可分配到在接口方法的 throws 子句中
* 声明的任一异常类型,代理实例的方法调用将抛出包含此方法曾抛出的异常的
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
使用java.lang.reflect.Proxy动态创建类对象
前面章节我们讲到了ClassLoader和Unsafe都有一个叫做defineClassXXX的native方法,我们可以通过调用这个native方法动态的向JVM创建一个类对象,而java.lang.reflect.Proxy类恰好也有这么一个native方法,所以我们也将可以通过调用java.lang.reflect.Proxy类defineClass0方法实现动态创建类对象。
ProxyDefineClassTest示例代码:
package com.anbai.sec.proxy;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import static com.anbai.sec.classloader.TestClassLoader.TEST_CLASS_BYTES;
import static com.anbai.sec.classloader.TestClassLoader.TEST_CLASS_NAME;
/**
* Creator: yz
* Date: 2020/1/15
*/
public class ProxyDefineClassTest {
public static void main(String[] args) {
// 获取系统的类加载器,可以根据具体情况换成一个存在的类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try {
// 反射java.lang.reflect.Proxy类获取其中的defineClass0方法
Method method = Proxy.class.getDeclaredMethod("defineClass0", new Class[]{
ClassLoader.class, String.class, byte[].class, int.class, int.class
});
// 修改方法的访问权限
method.setAccessible(true);
// 反射调用java.lang.reflect.Proxy.defineClass0()方法,动态向JVM注册
// com.anbai.sec.classloader.TestHelloWorld类对象
Class helloWorldClass = (Class) method.invoke(null, new Object[]{
classLoader, TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length
});
// 输出TestHelloWorld类对象
System.out.println(helloWorldClass);
} catch (Exception e) {
e.printStackTrace();
}
}
}
程序运行结果:
class com.anbai.sec.classloader.TestHelloWorld
创建代理类实例
我们可以使用Proxy.newProxyInstance来创建动态代理类实例,或者使用Proxy.getProxyClass()获取代理类对象再反射的方式来创建,下面我们以com.anbai.sec.proxy.FileSystem接口为例,演示如何创建其动态代理类实例。
Proxy.newProxyInstance示例代码:
// 创建UnixFileSystem类实例
FileSystem fileSystem = new UnixFileSystem();
// 使用JDK动态代理生成FileSystem动态代理类实例
FileSystem proxyInstance = (FileSystem) Proxy.newProxyInstance(
FileSystem.class.getClassLoader(),// 指定动态代理类的类加载器
new Class[]{FileSystem.class}, // 定义动态代理生成的类实现的接口
new JDKInvocationHandler(fileSystem)// 动态代理处理类
);
Proxy.getProxyClass反射示例代码:
// 创建UnixFileSystem类实例
FileSystem fileSystem = new UnixFileSystem();
// 创建动态代理处理类
InvocationHandler handler = new JDKInvocationHandler(fileSystem);
// 通过指定类加载器、类实现的接口数组生成一个动态代理类
Class proxyClass = Proxy.getProxyClass(
FileSystem.class.getClassLoader(),// 指定动态代理类的类加载器
new Class[]{FileSystem.class}// 定义动态代理生成的类实现的接口
);
// 使用反射获取Proxy类构造器并创建动态代理类实例
FileSystem proxyInstance = (FileSystem) proxyClass.getConstructor(
new Class[]{InvocationHandler.class}).newInstance(new Object[]{handler}
);
动态代理添加方法调用日志示例
假设我们有一个叫做FileSystem接口,UnixFileSystem类实现了FileSystem接口,我们可以使用JDK动态代理的方式给FileSystem的接口方法执行前后都添加日志输出。
com.anbai.sec.proxy.FileSystem示例代码:
package com.anbai.sec.proxy;
import java.io.File;
import java.io.Serializable;
/**
* Creator: yz
* Date: 2020/1/14
*/
public interface FileSystem extends Serializable {
String[] list(File file);
}
com.anbai.sec.proxy.UnixFileSystem示例代码:
package com.anbai.sec.proxy;
import java.io.File;
/**
* Creator: yz
* Date: 2020/1/14
*/
public class UnixFileSystem implements FileSystem {
/* -- Disk usage -- */
public int spaceTotal = 996;
@Override
public String[] list(File file) {
System.out.println("正在执行[" + this.getClass().getName() + "]类的list方法,参数:[" + file + "]");
return file.list();
}
}
com.anbai.sec.proxy.JDKInvocationHandler示例代码:
package com.anbai.sec.proxy;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* Creator: yz
* Date: 2020/1/14
*/
public class JDKInvocationHandler implements InvocationHandler, Serializable {
private final Object target;
public JDKInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 为了不影响测试Demo的输出结果,这里忽略掉toString方法
if ("toString".equals(method.getName())) {
return method.invoke(target, args);
}
System.out.println("即将调用[" + target.getClass().getName() + "]类的[" + method.getName() + "]方法...");
Object obj = method.invoke(target, args);
System.out.println("已完成[" + target.getClass().getName() + "]类的[" + method.getName() + "]方法调用...");
return obj;
}
}