设计模式-代理模式Java语言实现

简介: 关于**代理模式**的介绍,可以看我的另外一篇文章[设计模式-代理模式](https://oneyoung.top/设计模式-代理模式/)。这篇文章主要以代码演示Java语言的实现。

前言

关于代理模式的介绍,可以看我的另外一篇文章设计模式-代理模式

这篇文章主要以代码演示Java语言的实现。

介绍

代理又可以分为静态代理和动态代理两种,下面大概说下两者的主要区别

  1. 静态代理,被代理类必须基于接口实现,必须编写代理类,代理类与被代理类实现相同的接口。
  2. 动态代理

    1. JDK,被代理类必须基于接口实现,不用编写代理实现,需要编写InvocationHandler实现
    2. CgLib,可以代理类、接口,需要编写MethodInterceptor实现

静态代理

电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如提供按摩椅,娃娃机(这个每次去电影院都会尝试下,基本上是夹不起来,有木有大神可以传授下诀窍),卖爆米花、饮料(贵的要死,反正吃不起)等。我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?然后在影片开始结束时播放一些广告。

下面我们通过代码来模拟下电影院这一系列的赚钱操作。

首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Cnima,代表电影播放的能力。

public interface Cinema {

    /**
     * 播放电影
     *
     * @param filmName name
     */
    void play(String filmName);
}
  • 接下来我们要创建一个真正的实现这个 Cinema 接口的类,和一个实现该接口的代理类。

真正的实现类

public class DefaultCinema implements Cinema {

    @Override
    public void play(String filmName) {
        System.out.println("现在正在播放电影:" + filmName);
    }
}
  • 代理类
public class CinemaProxy implements Cinema {

    private final Cinema cinema;

    public CinemaProxy() {
        this.cinema = new DefaultCinema();
    }

    @Override
    public void play(String filmName) {
        CinemaProxyTask.playStartAd();
        cinema.play(filmName);
        CinemaProxyTask.playEndAd();
    }
}
  • 代理任务类
public class CinemaProxyTask {

    public static void playStartAd() {
        System.out.println("正在播放片头广告");
    }

    public static void playEndAd() {
        System.out.println("正在播放片尾广告");
    }
}
  • 测试类
public class MainTest {

    public static void main(String[] args) {
        String filmName = "中国医生";
        staticProxy(filmName);
    }

    public static void staticProxy(String filmName) {
        Cinema proxyCinema = new CinemaProxy();
        proxyCinema.play(filmName);
    }
}

image-20220102164025624.png

  • 运行结果
正在播放片头广告
现在正在播放电影:中国医生
正在播放片尾广告

现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。这个就是是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 CinemaProxy 这个类。

优点

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
  • 代理对象可以扩展目标对象的功能
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。

缺点

  • 代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护。

与装饰者模式的比较

  • 装饰静态代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。

JDK 动态代理

与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由 Java 反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为 Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的 Proxy 类和InvocationHandler 接口提供了生成动态代理类的能力。

还是刚才的Cinema接口,我们编写个动态代理

public class JdkCinemaProxy implements InvocationHandler {

    private final Object object;

    public JdkCinemaProxy(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object invoke = null;
        if (method.getName().equals("play")) {
            CinemaProxyTask.playStartAd();
            invoke = method.invoke(object, args);
            CinemaProxyTask.playEndAd();
        }
        return invoke;
    }
}

这个invoke方法有很多参数

  • proxy,这个是生成的代理类的实例
  • method,表示代理的方法
  • args,表示传入方法的参数
public class MainTest {

    public static void main(String[] args) {
        String filmName = "中国医生";
        dynamicJdkProxy(filmName);
    }

    public static void dynamicJdkProxy(String filmName) {
      // 保存动态生成的代理类
        System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        DefaultCinema defaultCinema = new DefaultCinema();
        JdkCinemaProxy jdkCinemaProxy = new JdkCinemaProxy(defaultCinema);
      // 动态生成代理类,返回的Object 就是生成的代理类实例
        Object o = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class[]{Cinema.class}, jdkCinemaProxy);
        Cinema cinema = (Cinema) o;
        cinema.play(filmName);
    }
}
  • newProxyInstance,方法需要你传入,类加载器、需要代理的接口,以及处理的InvocationHandler
  • 运行时会生成代理类,并实时加载进JVM。

我们来看下动态生成的代理类。

public final class $Proxy0 extends Proxy implements Cinema {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void play(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("top.oneyoung.design.proxy.Cinema").getMethod("play", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
  • 我们可以看到,该类有四个成员变量分别对应Object的三个公共方法以及一个Cinema接口的play方法。
  • 构造方法需要传入IvocationHandler,并继续传入父类的构造方法。这里传入的IvocationHandler的其实就是我们刚才编写的JdkCinemaProxy
  • 代理类的play方法,super.h.invoke(this, m3, new Object[]{var1});

    • super.h就是我们编写的JdkCinemaProxy
    • 然后调用invoke方法,完成了代理。

至此整个流程就清晰了。这就是 jdk 的动态代理。

可以看到,JDK的动态代理其实就是自动帮我们生成了代理类,避免我们手动编写。

CgLib动态代理

// TODO

目录
相关文章
|
3月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
102 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
2月前
|
监控 Java API
如何使用Java语言快速开发一套智慧工地系统
使用Java开发智慧工地系统,采用Spring Cloud微服务架构和前后端分离设计,结合MySQL、MongoDB数据库及RESTful API,集成人脸识别、视频监控、设备与环境监测等功能模块,运用Spark/Flink处理大数据,ECharts/AntV G2实现数据可视化,确保系统安全与性能,采用敏捷开发模式,提供详尽文档与用户培训,支持云部署与容器化管理,快速构建高效、灵活的智慧工地解决方案。
|
18天前
|
设计模式 前端开发 数据安全/隐私保护
前端必须掌握的设计模式——代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,通过引入“替身”对象来间接访问真实对象,从而解耦并提升性能和安全性。例如,知名艺人复出后,经纪人作为代理筛选商单,确保只处理符合团队利益的请求。代码实现中,定义接口`IService`,艺人和经纪人都实现该接口,经纪人在访问时进行过滤和转发。代理模式常用于权限控制、性能优化等场景,如前端中的Tree-shaking和ES6的Proxy构造方法。
前端必须掌握的设计模式——代理模式
|
2月前
|
设计模式 消息中间件 搜索推荐
Java 设计模式——观察者模式:从优衣库不使用新疆棉事件看系统的动态响应
【11月更文挑战第17天】观察者模式是一种行为设计模式,定义了一对多的依赖关系,使多个观察者对象能直接监听并响应某一主题对象的状态变化。本文介绍了观察者模式的基本概念、商业系统中的应用实例,如优衣库事件中各相关方的动态响应,以及模式的优势和实际系统设计中的应用建议,包括事件驱动架构和消息队列的使用。
|
2月前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
42 4
|
2月前
|
SQL 安全 Java
安全问题已经成为软件开发中不可忽视的重要议题。对于使用Java语言开发的应用程序来说,安全性更是至关重要
在当今网络环境下,Java应用的安全性至关重要。本文深入探讨了Java安全编程的最佳实践,包括代码审查、输入验证、输出编码、访问控制和加密技术等,帮助开发者构建安全可靠的应用。通过掌握相关技术和工具,开发者可以有效防范安全威胁,确保应用的安全性。
59 4
|
3月前
|
Java Spring 数据库连接
[Java]代理模式
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
47 0
[Java]代理模式
|
3月前
|
设计模式 Java 程序员
[Java]23种设计模式
本文介绍了设计模式的概念及其七大原则,强调了设计模式在提高代码重用性、可读性、可扩展性和可靠性方面的作用。文章还简要概述了23种设计模式,并提供了进一步学习的资源链接。
60 0
[Java]23种设计模式
|
3月前
|
Java 程序员 编译器
在Java编程中,保留字(如class、int、for等)是具有特定语法意义的预定义词汇,被语言本身占用,不能用作变量名、方法名或类名。
在Java编程中,保留字(如class、int、for等)是具有特定语法意义的预定义词汇,被语言本身占用,不能用作变量名、方法名或类名。本文通过示例详细解析了保留字的定义、作用及与自定义标识符的区别,帮助开发者避免因误用保留字而导致的编译错误,确保代码的正确性和可读性。
67 3
|
3月前
|
移动开发 Java 大数据
深入探索Java语言的核心优势与现代应用实践
【10月更文挑战第10天】深入探索Java语言的核心优势与现代应用实践
116 4