今天看了下Proxy源码,我尝试以一种比较容易理解的方式把它讲透了。
首先假设我要自己写一个动态代理,我该怎么设计呢?有了,思路如下:
简单的分三步处理:
1、实现一个代理接口的空实现类
2、在类每个方法调用时,把调用权直接交给回调函数处理(其实就是接口的时候,改成调用回调函数而已~)
3、返回回调函数的调用值
如下图
按照上面思路,以代理Person接口为例。先定义一个Person接口
/**
* 要被代理的接口
*/
public interface Person {
/**
* 打个招呼
* @param name
*/
void say(String name);
}
1、实现一个代理接口的空实现类:DefaultImpl
/**
* 空实现
*/
public class DefaultImpl implements Person{
@Override
public void say(String name) {
}
}
2、在类每个方法调用时,把调用权直接交给回调处理
这时候有两个问题:
A:如何拿到我的回调函数的对象?
B: 如何调用方法的时候调用回调函数?
对于A问题,很简单,我们把它当成构造函数传入即可,于是代码改成这样:
/**
* 空实现
*/
public class DefaultImpl implements Person{
//这是回调函数
private Object callBackHandler;
/**
* 回调函数当成入参传进来
*/
DefaultImpl(Object callBackHandler){
this.callBackHandler = callBackHandler;
}
@Override
public void say(String name) {
}
}
B问题呢?也很简单,我直接掉函数就行了。这里又有个小问题,回调函数要能支持调用所有的方法,所以设计时就考虑反射调用了
首先定义回调函数接口:
/**
* 回调接口
*/
public interface CallBackHandler {
/**
* 回调函数
* @param method 反射的Method
* @param args 代理接口的入参列表
* @return 替代代理接口返回的值
* @throws Throwable
*/
public Object invoke(Method method, Object[] args) throws Throwable;
}
代入DefaultImpl后就变成了这样:
/**
* 空实现
*/
public class DefaultImpl implements Person{
//这是回调函数
private CallBackHandler callBackHandler;
/**
* 回调函数当成入参传进来
*/
DefaultImpl(CallBackHandler callBackHandler){
this.callBackHandler = callBackHandler;
}
@Override
public void say(String name) {
//这行代码就是通过反射找到当前方法的Method对象,以便后面调用时使用
Method method = Arrays.stream(DefaultImpl.class.getMethods())
.filter(e -> e.getName().equals(Thread.currentThread() .getStackTrace()[1].getMethodName())).findFirst().orElse(null);
//这里没有返回值,所以就不用返回了,如果有,直接return即可
callBackHandler.invoke(method,new Object[]{name});
}
}
OK,大功告成,我们写一个简单的测试方法测试下:
PS:要跑通下面测试时记得给CallBackHandler接口加上 @FunctionalInterface 注解(要么你换种写法也行)
public static void main(String[] args) {
DefaultImpl defaul = new DefaultImpl((Method method, Object[] params)->{
System.out.println("我是代理类!我截胡了.");
return null;
});
defaul.say("李雷");
}
运行结果:
我是代理类!我截胡了.
从上面可以看到被代理类截掉了。
如果你上面的例子看懂了,那么恭喜你,Proxy的原理你已经懂了90%了。
现在开始讲Proxy原理。其实原理跟上面的逻辑是一样的。但上面是有个问题要解决,就是每次生成的空实现类DefaultImpl ,是java文件的类,要做动态代理时,就不能事先编译好,得改成实时编译。原理很简单,就是动态编译实时生成一个空代理类,然后再用ClassLoader 加载到内存去。
动态编译生成空代理类并加载到内存,Proxy是用java.lang.reflect.Proxy.ProxyClassFactory 实现的。
Java Proxy生成的类跟上面的例子逻辑是一样的。也是构造函数传入回调接口,然后内部截取并回调。
附上图:
关于InvocationHandler接口参数的理解
让我们再回过头看下JDK里面的java.lang.reflect.InvocationHandler 的接口定义:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
估计很多人开始时对第一个参数Object proxy都很懵逼,不知道这是干什么的,也很容易用错,那现在也给你讲明白了。它传入的就是接口空实现的类对象的本身(就是把this传了进去)。那具体有什么作用呢,其实我也没想遇到过使用场景,但可以写代码简单测试下验证:
public class ProxyTest {
public static void main(String[] args) {
Person person = (Person) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(),new Class[]{Person.class},new JdkProxy());
person.say("hello");
}
static class JdkProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//证明proxy实现了Person接口
System.out.println(proxy instanceof Person);
return null;
}
}
}
上面例子是使用JDK的Proxy实现了一个Person 的代理类,调用时打印invoke回调方法的第一个参数,可以看到打印结果是true ,即proxy对象是实现了Person的类,进一步验证了我们的想法。
好了。Proxy讲完了,你理解了吗?