资讯专栏INFORMATION COLUMN

java | 什么是动态代理?

fxp / 3227人阅读

摘要:代理模式在我们日常中很常见,生活处处有代理看张学友的演唱会很难抢票,可以找黄牛排队买嫌出去吃饭麻烦,可以叫外卖无论是黄牛外卖骑手都得帮我们干活。静态代理我还是以找黄牛帮我排队买张学友的演唱会门票的例子,写个说明。

微信公众号:一个优秀的废人。如有问题,请后台留言,反正我也不会听。

最近在复习 Java 相关,回顾了下代理模式。代理模式在 Java 领域很多地方都有应用,它分为静态代理和动态代理,其中 Spring AOP 就是动态代理的典型例子。动态代理又分为接口代理和 cglib (子类代理),结合我的理解写了几个 demo 分享给你们,这是昨晚修仙到 3 点写出来的文章,不点在看,我觉得说不过去了。

代理模式在我们日常中很常见,生活处处有代理:

看张学友的演唱会很难抢票,可以找黄牛排队买

嫌出去吃饭麻烦,可以叫外卖

无论是黄牛、外卖骑手都得帮我们干活。但是他们不能一手包办(比如黄牛不能帮我吃饭),他们只能做我们不能或者不想做的事。

找黄牛可以帮我排队买上张学友的演唱会门票

外卖骑手可以帮我把饭送到楼下

所以,你看。代理模式其实就是当前对象不愿意做的事情,委托给别的对象做。

静态代理

我还是以找黄牛帮我排队买张学友的演唱会门票的例子,写个 demo 说明。现在有一个 Human 接口,无论是我还是黄牛都实现了这个接口。

public interface Human {

    void eat();

    void sleep();

    void lookConcert();

}

例如,我这个类,我会吃饭和睡觉,如以下类:

public class Me implements Human{

    @Override
    public void eat() {
        System.out.println("eat emat ....");
    }

    @Override
    public void sleep() {
        System.out.println("Go to bed at one o"clock in the morning");
    }

    @Override
    public void lookConcert() {
        System.out.println("Listen to Jacky Cheung"s Concert");
    }

}

有黄牛类,例如:

public class Me implements Human{

    @Override
    public void eat() {
    }

    @Override
    public void sleep() {
    }

    @Override
    public void lookConcert() {
    }

}

现在我和黄牛都已经准备好了,怎么把这二者关联起来呢?我们要明确的是黄牛是要帮我买票的,买票必然就需要帮我排队,于是有以下黄牛类:注意这里我们不关心,黄牛的其他行为,我们只关心他能不能排队买票。

public class HuangNiu implements Human{

    private Me me;

    public HuangNiu() {
        me = new Me();
    }

    @Override
    public void eat() {
    }

    @Override
    public void sleep() {
    }

    @Override
    public void lookConcert() {
        // 添加排队买票方法
        this.lineUp();
        me.lookConcert();
    }

    public void lineUp() {

        System.out.println("line up");

    }

}

最终的 main 方法调用如下:

public class Client {

    public static void main(String[] args) {

        Human human = new HuangNiu();
        human.lookConcert();

    }

}

结果如下:

由此可见,黄牛就只是做了我们不愿意做的事(排队买票),实际看演唱会的人还是我。客户端也并不关心代理类代理了哪个类,因为代码控制了客户端对委托类的访问。客户端代码表现为 Human human = new HuangNiu();

由于代理类实现了抽象角色的接口,导致代理类无法通用。比如,我的狗病了,想去看医生,但是排队挂号很麻烦,我也想有个黄牛帮我的排队挂号看病,但是黄牛它不懂这只狗的特性(黄牛跟狗不是同一类型,黄牛属于 Human 但狗属于 Animal 类)但排队挂号和排队买票相对于黄牛来说它两就是一件事,这个方法是不变的,现场排队。那我们能不能找一个代理说既可以帮人排队买票也可以帮狗排队挂号呢?

答案肯定是可以的,可以用动态代理。

基于接口的动态代理

如静态代理的内容所描述的,静态代理受限于接口的实现。动态代理就是通过使用反射,动态地获取抽象接口的类型,从而获取相关特性进行代理。因动态代理能够为所有的委托方进行代理,因此给代理类起个通用点的名字 HuangNiuHandle。先看黄牛类可以变成什么样?

public class HuangNiuHandle implements InvocationHandler {

    private Object proxyTarget;

    public Object getProxyInstance(Object target) {
        this.proxyTarget = target;
        return Proxy.newProxyInstance(proxyTarget.getClass().getClassLoader(), proxyTarget.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object methodObject = null;

        System.out.println("line up");
        methodObject = method.invoke(proxyTarget, args);
        System.out.println("go home and sleep");

        return methodObject;
    }

}

这个时候的客户端代码就变成这样了

public class Client {

    public static void main(String[] args) {

        HuangNiuHandle huangNiuHandle = new HuangNiuHandle();
        Human human = (Human) huangNiuHandle.getProxyInstance(new Me());

        human.eat();
        human.run();
        human.lookConcert();

        System.out.println("------------------");

        Animal animal = (Animal) huangNiuHandle.getProxyInstance(new Dog());
        animal.eat();
        animal.run();
        animal.seeADoctor();
    }

}

使用动态代理有三个要点,

必须实现 InvocationHandler 接口,表明该类是一个动态代理执行类。

InvocationHandler 接口内有一实现方法如下: public Object invoke(Object proxy, Method method, Object[] args) 。使用时需要重写这个方法

获取代理类,需要使用 Proxy.newProxyInstance(Clas loader, Class[] interfaces, InvocationHandler h) 这个方法去获取Proxy对象(Proxy 类类型的实例)。

注意到 Proxy.newProxyInstance 这个方法,它需要传入 3 个参数。解析如下:

// 第一个参数,是类的加载器
// 第二个参数是委托类的接口类型,证代理类返回的是同一个实现接口下的类型,保持代理类与抽象角色行为的一致
// 第三个参数就是代理类本身,即告诉代理类,代理类遇到某个委托类的方法时该调用哪个类下的invoke方法
Proxy.newProxyInstance(Class loader, Class[] interfaces, InvocationHandler h)

再来看看 invoke 方法,用户调用代理对象的什么方法,实质上都是在调用处理器的
invoke 方法,通过该方法调用目标方法,它也有三个参数:

// 第一个参数为 Proxy 类类型实例,如匿名的 $proxy 实例
// 第二个参数为委托类的方法对象
// 第三个参数为委托类的方法参数
// 返回类型为委托类某个方法的执行结果
public Object invoke(Object proxy, Method method, Object[] args)

调用该代理类之后的输出结果:

由结果可知,黄牛不仅帮了(代理)我排队买票,还帮了(代理)我的狗排队挂号。所以,你看静态代理需要自己写代理类(代理类需要实现与目标对象相同的接口),还需要一一实现接口方法,但动态代理不需要。

注意,我们并不是所有的方法都需要黄牛这个代理去排队。我们知道只有我看演唱会和我的狗去看医生时,才需要黄牛,如果要实现我们想要的方法上面添加特定的代理,可以通过 invoke 方法里面的方法反射获取 method 对象方法名称即可实现,所以动态代理类可以变成这样:

public class HuangNiuHandle implements InvocationHandler {

    private Object proxyTarget;

    public Object getProxyInstance(Object target) {
        this.proxyTarget = target;
        return Proxy.newProxyInstance(proxyTarget.getClass().getClassLoader(), proxyTarget.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object methodObject = null;

        if ("lookConcert".equals(method.getName()) ||
        "seeADoctor".equals(method.getName())) {

            System.out.println("line up");
            // 调用目标方法
            methodObject = method.invoke(proxyTarget, args);
        } else {
            // 不使用第一个proxy参数作为参数,否则会造成死循环
            methodObject = method.invoke(proxyTarget, args);
        }

        return methodObject;
    }

}

结果如下:可以看到我们只在特定方法求助了黄牛

由此可见,动态代理一般应用在记录日志等横向业务。

值得注意的是:

基于接口类的动态代理模式,必须具备抽象角色、委托类、代理三个基本角色。委托类和代理类必须由抽象角色衍生出来,否则无法使用该模式。

动态代理模式最后返回的是具有抽象角色(顶层接口)的对象。在委托类内被 private 或者 protected 关键修饰的方法将不会予以调用,即使允许调用。也无法在客户端使用代理类转换成子类接口,对方法进行调用。也就是说上述的动态代理返回的是委托类(Me)或 (Dog)的就接口对象 (Human)或 (Animal)。

在 invoke 方法内为什么不使用第一个参数进行执行回调。在客户端使用getProxyInstance(new Child( ))时,JDK 会返回一个 proxy 的实例,实例内有InvokecationHandler 对象及动态继承下来的目标 。客户端调用了目标方法,有如下操作:首先 JDK 先查找 proxy 实例内的 handler 对象 然后执行 handler 内的 invoke 方法。

根据 public Object invoke 这个方法第一个参数 proxy 就是对应着 proxy 实例。如果在 invoke 内使用 method.invoke(proxy,args) ,会出现这样一条方法链,目标方法→invoke→目标方法→invoke...,最终导致堆栈溢出。

基于子类的动态代理

为了省事,我这里并没有继承父类,但在实际开发中是需要继承父类才比较方便扩展的。与基于接口实现类不同的是:

CGLib (基于子类的动态代理)使用的是方法拦截器 MethodInterceptor ,需要导入 cglib.jar 和 asm.jar 包

基于子类的动态代理,返回的是子类对象

方法拦截器对 protected 修饰的方法可以进行调用

代码如下:

public class Me {

    public void eat() {
        System.out.println("eat meat ....");
    }

    public void run() {
        System.out.println("I run with two legs");
    }

    public void lookConcert() {
        System.out.println("Listen to Jacky Cheung"s Concert");
    }

    protected void sleep() {
        System.out.println("Go to bed at one o"clock in the morning");
    }

}

Dog 类

public class Dog {

    public void eat() {
        System.out.println("eat Dog food ....");
    }

    public void run() {
        System.out.println("Dog running with four legs");
    }

    public void seeADoctor() {
        System.out.println("The dog go to the hospital");
    }

}

黄牛代理类,注意 invoke() 这里多了一个参数 methodProxy ,它的作用是用于执行目标(委托类)的方法,至于为什么用 methodProxy ,官方的解释是速度快且在intercep t内调用委托类方法时不用保存委托对象引用。

public class HuangNiuHandle implements MethodInterceptor {

    private Object proxyTarget;

    public Object getProxyInstance(Object target) {
        this.proxyTarget = target;
        return Enhancer.create(target.getClass(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

        Object methodObject = null;

        if ("lookConcert".equals(method.getName()) ||
                "seeADoctor".equals(method.getName())) {
            System.out.println("line up");
            // 调用目标方法
            methodObject = methodProxy.invokeSuper(proxy, args);
        } else {
            methodObject = method.invoke(proxyTarget, args);
        }

        return methodObject;
    }
}

client 类

public class Client {

    public static void main(String[] args) {
        HuangNiuHandle huangNiuHandle = new HuangNiuHandle();
        Me me = (Me) huangNiuHandle.getProxyInstance(new Me());

        me.eat();
        me.run();
        me.sleep();
        me.lookConcert();

        System.out.println("------------------");

        Dog dog = (Dog) huangNiuHandle.getProxyInstance(new Dog());
        dog.eat();
        dog.run();
        dog.seeADoctor();
    }
}

结果:

注意到 Me 类中被 protected 修饰的方法 sleep 仍然可以被客户端调用。这在基于接口的动态代理中是不被允许的。

静态代理与动态代理的区别

静态代理需要自己写代理类并一一实现目标方法,且代理类必须实现与目标对象相同的接口。

动态代理不需要自己实现代理类,它是利用 JDKAPI,动态地在内存中构建代理对象(需要我们传入被代理类),并且默认实现所有目标方法。

源码下载:https://github.com/turoDog/re...

后语

如果本文对你哪怕有一丁点帮助,请帮忙点好看,你的好看是我坚持写作的动力。关注公众号一个优秀的废人回复 1024 获取资料:Python、C++、Java、Linux、Go、前端、算法资料分享

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77643.html

相关文章

  • 你真的完全了解Java动态代理吗?看这篇就够了

    摘要:动态地代理,可以猜测一下它的含义,在运行时动态地对某些东西代理,代理它做了其他事情。所以动态代理的内容重点就是这个。所以下一篇我们来细致了解下的到底是怎么使用动态代理的。 之前讲了《零基础带你看Spring源码——IOC控制反转》,本来打算下一篇讲讲Srping的AOP的,但是其中会涉及到Java的动态代理,所以先单独一篇来了解下Java的动态代理到底是什么,Java是怎么实现它的。 ...

    haitiancoder 评论0 收藏0
  • 给女朋友讲解什么代理模式

    摘要:受知乎文章和设计模式之禅的启发,我也来搞一篇脑洞小开的文章由标题可知,这篇文章是写给我女朋友看的。于是这就让经纪人对粉丝说只有万,我才会写代码。 前言 只有光头才能变强 回顾前面: ThreadLocal就是这么简单 多线程三分钟就可以入个门了! 多线程基础必要知识点!看了学习多线程事半功倍 Java锁机制了解一下 AQS简简单单过一遍 Lock锁子类了解一下 线程池你真不来了解一下...

    stormgens 评论0 收藏0
  • java动态代理及RPC框架介绍

    摘要:这种语法,在中被称为动态代理。在动态代理机制中,这个角色只能是接口。动态代理就是实现的技术之一。 所谓动态代理,指的是语言提供的一种语法,能够将对对象中不同方法的调用重定向到一个统一的处理函数中来。python重写__getattr__函数能够做到这一点,就连世界上最好的语言也提供称为魔术方法的__call。这种语法除了能更好的实现动态代理外,还是RPC框架实现原理的一部分。 动态代理...

    2shou 评论0 收藏0
  • Java动态代理 jdk和cglib的实现比较

    摘要:与静态代理对比,动态代理是在动态生成代理类,由代理类完成对具体方法的封装,实现的功能。本文将分析中两种动态代理的实现方式,和,比较它们的异同。那如何动态编译呢你可以使用,这是一个封装了的库,帮助你方便地实现动态编译源代码。 发现Java面试很喜欢问Spring AOP怎么实现的之类的问题,所以写一篇文章来整理一下。关于AOP和代理模式的概念这里并不做赘述,而是直奔主题,即AOP的实现方...

    h9911 评论0 收藏0
  • Java 动态代理(Dynamic proxy) 小结

    摘要:代理模式基本概念不论是静态代理还是动态代理其本质都是代理模式的一种实现那么什么是代理模式呢代理模式即给某一个对象提供一个代理并由代理对象控制对原对象的引用代理模式其实取材于实际生活例如我们生活中常见的房屋租赁代理我们在租房时一般不是直接和房 代理模式 基本概念 不论是静态代理还是动态代理, 其本质都是代理模式的一种实现, 那么什么是代理模式呢?代理模式, 即给某一个对象提供一个代理, ...

    Jason 评论0 收藏0
  • Java动态代理深度解析

    摘要:动态代理深度解析引言说起动态代理,很多人可能都没有直接去使用过。因为的动态代理只能代理接口,而不能代理原始的类。接下来是真正压轴的环节,实现自己的动态代理类。 Java动态代理深度解析 引言 说起动态代理,很多人可能都没有直接去使用过。但是只要用过Spring,那动态代理就是一个是个绕不过的坎,因为Spring的核心特性之一AOP就是基于动态代理来实现的,那么什么情况下需要用到动态代理...

    whinc 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<