1. 回顾动态代理
- jdk的动态代理中关键核心就在于一个Proxy类。这个类是jdk提供的内置类。这个类能干嘛?
- 前面学习反射机制、和java中常见机制时,曾经反复强调两个动态性的概念:动态加载和静态加载、动态字节码和静态字节码。在这个我们首先通过Proxy类来直观的感受下到底什么叫做动态字节码机制?
- 学习动态字节码机制时,核心的一点就是字节码的生成过程是在一个正在运行当中的JVM中动态进行的,生成的字节码是在JVM内存中虚拟缓存的,而不是java编译器预编译后产生的*.class文件。那么具体怎么样呢?
Proxy类我们分为两节来学习,本节先学习Proxy对外暴露的4个API。
2. Proxy类
Proxy类对外只暴露了4个业务方法:
Proxy.newProxyInstance();
Proxy.getInvocationHandler();
Proxy.isProxyClass();
Proxy.getProxyClass();
下面学习。 为了深入学习Proxy类的API,先编写几个类:
IUser
package zeh.test.demo.com.proxy;
// 2021-03-30
public interface IUser {
void doSome();
void doOther();
}
UserImpl
package zeh.test.demo.com.proxy;
// IUser的实现类
public class UserImpl implements IUser {
@Override
public void doSome() {
System.out.println("执行doSome.");
}
@Override
public void doOther() {
System.out.println("执行doOther.");
}
}
2.1 newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);
该方法目的是创建一个动态字节码文件的对象。
第一个参数 loader:表示需要动态生成的目标字节码类型的类装载器。
第二个参数 interfaces:表示需要动态生成的目标字节码对应的接口,可以是多个接口。
第三个参数 h:一个回调器,是InvocationHandler回调接口的对象,目的是当外界通过动态生成的字节码对象去调用对应的目标方法时,需要回调执行的逻辑。
ProxyMain测试
package zeh.test.demo.com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyMain {
public static void main(String[] args) {
IUser proxy = (IUser) Proxy.newProxyInstance(UserImpl.class.getClassLoader(), UserImpl.class.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行动态字节码代理对象的前置逻辑...");
Object returnValue = method.invoke(new UserImpl(), args);
System.out.println("执行动态字节码代理对象的后置逻辑...");
return returnValue;
}
});
// 执行动态字节码代理对象的doSome和doOther方法,最终会回调到 InvocationHandler 的 invoke 方法去执行真正传入的对象上的指定方法逻辑
proxy.doSome();
proxy.doOther();
// 打印生成的动态字节码代理对象的所属Class描述
System.out.println("proxy:" + proxy.getClass());
}
}
测试结果
执行动态字节码代理对象的前置逻辑...
执行doSome.
执行动态字节码代理对象的后置逻辑...
执行动态字节码代理对象的前置逻辑...
执行doOther.
执行动态字节码代理对象的后置逻辑...
proxy:class com.sun.proxy.$Proxy0
Process finished with exit code 0
结果分析
这个案例就是jdk动态代理的实现。
newProxyInstance方法是动态代理的核心方法。
此处重点关注,该方法生成的代理对象对应的class打印出来是:class com.sun.proxy.$Proxy0。这是一个class,然而在整个工程中未找到对应的class文件。
那这个class字节码到底在哪里呢?它在jvm的内存中存储,执行完毕便释放了。
2.2 getInvocationHandler方法
public static InvocationHandler getInvocationHandler(Object proxy);
参数 proxy:之前生成的某个动态字节码代理对象。
ProxyMain测试
package zeh.test.demo.com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// zWX5331241 2021-03-30
public class ProxyMain {
public static void main(String[] args) throws Throwable {
IUser proxy = (IUser) Proxy.newProxyInstance(UserImpl.class.getClassLoader(), UserImpl.class.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行动态字节码代理对象的前置逻辑...");
Object returnValue = method.invoke(new UserImpl(), args);
System.out.println("执行动态字节码代理对象的后置逻辑...");
return returnValue;
}
});
// 执行动态字节码对象的doSome和doOther方法,最终会回调到 InvocationHandler 的 invoke 方法去执行真正传入的对象上的指定方法逻辑
proxy.doSome();
proxy.doOther();
// 打印生成的动态字节码对象的所属Class描述
System.out.println("proxy:" + proxy.getClass());
// 获取动态字节码对象的回调器
InvocationHandler invocationHandler = Proxy.getInvocationHandler(proxy);
System.out.println("invocationHandler:" + invocationHandler.getClass());
// 通过获取到的回调器,手动执行回调器中的回调方法
invocationHandler.invoke(proxy, IUser.class.getMethod("doSome"), null);
}
}
测试结果
执行动态字节码代理对象的前置逻辑...
执行doSome.
执行动态字节码代理对象的后置逻辑...
执行动态字节码代理对象的前置逻辑...
执行doOther.
执行动态字节码代理对象的后置逻辑...
proxy:class com.sun.proxy.$Proxy0
invocationHandler:class zeh.test.demo.com.proxy.ProxyMain$1
执行动态字节码代理对象的前置逻辑...
执行doSome.
执行动态字节码代理对象的后置逻辑...
Process finished with exit code 0
结果分析
在上一个案例的基础上,测试了Proxy类的getInvocationHandler方法。
目的是获取动态字节码代理对象的回调器,获取之后可以自己手动调用回调器中的回调方法,只需要传入对应的参数即可。
2.3 isProxyClass方法
public static boolean isProxyClass(Class<?> cl);
参数 cl:一个目标class对象。
ProxyMain测试
package zeh.test.demo.com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyMain {
public static void main(String[] args) throws Throwable {
IUser proxy = (IUser) Proxy.newProxyInstance(UserImpl.class.getClassLoader(), UserImpl.class.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行动态字节码代理对象的前置逻辑...");
Object returnValue = method.invoke(new UserImpl(), args);
System.out.println("执行动态字节码代理对象的后置逻辑...");
return returnValue;
}
});
// 用于判断目标class是否是动态字节码代理类
System.out.println(Proxy.isProxyClass(proxy.getClass()));
System.out.println(Proxy.isProxyClass(IUser.class));
}
}
测试结果
true
false
Process finished with exit code 0
结果分析
isProxyClass方法用于判断目标class对象是否是一个动态字节码,如果传入的目标class就是动态生成的字节码的对象,则返回true;如果不是,则返回false。
2.4 getProxyClass方法
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces);
第一个参数 loader:表示需要动态生成的目标字节码类型的类装载器。
第二个参数 interfaces:表示需要动态生成的目标字节码对应的接口,可以是多个接口。
ProxyMain测试
package zeh.test.demo.com.proxy;
import java.lang.reflect.Proxy;
public class ProxyMain {
public static void main(String[] args) {
// getProxyClass 方法用于获取一个目标接口的动态字节码的class对象,如果动态字节码文件不存在,则自动生成并保存在JVM内存中
Class<?> proxyClass = Proxy.getProxyClass(UserImpl.class.getClassLoader(), UserImpl.class.getInterfaces());
System.out.println("proxyClass:" + proxyClass);
}
}
测试结果
proxyClass:class com.sun.proxy.$Proxy0
Process finished with exit code 0
结果分析
getProxyClass 方法用于获取一个目标接口的动态字节码的class对象,如果动态字节码文件不存在,则自动生成并保存在JVM内存中
文档信息
- 本文作者:Marshall