上一章我们学习了静态代理,举个栗子比如我想在一批Controller里进行入参和出参的打印。那么静态代理就会创建若干个Controller的代理类。
再比如我除了要出参入参打印,我还需要在出参入参之后在打印出每个函数的耗时,那么就需要重新在每个函数里在加上耗时的日志打印。动态代理则会帮我们省了很多代码量的编写。
下面介绍动态代理,动态代理是JDK自有的功能。
Java 动态代理类位于 Java.lang.reflect 包下,一般主要涉及到以下两个类:
- Interface InvocationHandler:该接口中仅定义了一个方法 Object:invoke(Object obj,Method method,Object[] args)。在实际使用时,第一个参数 obj 一般是指代理类,method 是被代理的方法,args 为该方法的参数数组。这个抽象方法在代理类中动态实现。
- Proxy:该类即为动态代理类,Static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用。
代码实现
public interface Subject {
void request();
}
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("真实的请求!");
}
}
public class ProxyHandler implements InvocationHandler {
private Object obj;
public ProxyHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行前的打印测试!");
return method.invoke(this.obj, args);
}
}
public class TestMain {
public static void main(String[] args) {
final Subject realSubject = new RealSubject();
Subject realSubjectProxy = (Subject) Proxy
.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { Subject.class },
new ProxyHandler(realSubject));
realSubjectProxy.request();
}
}
运行结果:
执行前的打印测试!
真实的请求!
Process finished with exit code 0
主要代码是 ProxyHandler 中的这个代理类的作用是可以代理任何类,因为它被传入的对象是Object,而不再是具体的类。
代理机制及特点
- 通过实现 InvocationHandler 接口创建自己的调用处理器;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
第三点也就说明了为什么使用代理类的任意方法都能进入 InvocationHandler 中的 public Object invoke(Object proxy, Method method, Object[] args); 函数中。因为在生成代理类的时候会将 public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h); 中的 h 参数通过反射给代理类赋值,在代理类每个函数中都 h.invoke(this, method, args); 这样子的调用。
下面是极其简易版的实现,只做参考。写的不好有不对的地方或建议可评论谢谢!
public static Object newProxyInstance(Class<?>[] interfaces, InvocationHandler h) throws Exception {
// String 也是方便才写,用 StringBuilder 效率更高
// interfaces[0].getSimpleName() 只是为了省事其实是一条一条 for 得出
String str = "";
str += "public Proxy01 implements " + interfaces[0].getSimpleName() + " {
";
str += " private InvocationHandler h;
";
str += " public Proxy01(InvocationHandler h) {
";
str += " this.h = h;
";
str += " }";
for (Method m : interfaces[0].getMethods()) {
str += "public " + m.getReturnType() + " " + m.getName() + "(参数) {
";
// 下面会抛出异常,为了简单的示范就省略了
str += "Method m = " + interfaces[0].getSimpleName() + ".getMethod(" + """ + m.getName() + """ + ");";
str += "h.invoke(this, m);";
str += "}";
}
str += "}";
// 生成对应的 java 文件
File file = new File("路径");
file.createNewFile();
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
bos.write(str.getBytes());
bos.flush();
bos.close();
// 既然生成了 java 文件,那么下一步肯定就是编译成 class 了
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable iterable = fileManager.getJavaFileObjects("路径\Proxy01.java");
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, iterable);
task.call();
fileManager.close();
// 编译完成那么下一步就是加载到内存了
URL[] urls = new URL[]{new URL("路径")};
URLClassLoader classLoader = new URLClassLoader(urls);
Class<?> aClass = classLoader.loadClass("Proxy01.class");
Constructor<?> constructor = aClass.getConstructor();
// 重点就在最后一行
return constructor.newInstance(h);
}
优点
减少代理类的数量,相对降低了应用程序的复杂度,灵活性更高。
缺点
目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。(其实不实现接口也可以)只是看起来很傻。