五分钟彻底理解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讲完了,你理解了吗?

参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

阿拉希神猪

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值