五分钟彻底理解Java Proxy原理

今天看了下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讲完了,你理解了吗?

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值