首页 > 编程语言 >Java设计模式 —— 代理模式

Java设计模式 —— 代理模式

时间:2022-10-26 20:58:29浏览次数:50  
标签:Java String 对象 代理 class 设计模式 public 客户端

15 代理模式

15.1 代理模式概述

Proxy Pattern: 给某一个对象提供一个代理或占位符,由代理对象来控制对原对象的访问。

代理对象是客户端和目标对象的之前的桥梁,它接收来自客户端的请求并转发给目标对象,去掉客户端不能看到的内容或添加额外的服务。为了保证客户端使用的透明性,代理对象和原对象需要实现相同的接口。

代理模式结构图如下所示:

15.2 代理模式实现

15.2.1 抽象主题接口

代理对象和原访问对象都需要实现该接口,使得任何使用真实主题对象的地方都可以使用代理对象,客户端也可以针对抽象层编程。

public interface Subject {
  public void request();
}

15.2.2 真实主题类

public class RealSubject implements Subject {
  public void request() {
    // 业务逻辑代码
  }
}

15.2.3 代理类

代理类中包含对真实主题的引用,可以把客户端请求转发给真实主题并将请求结果返回给客户端。同时,代理类在将请求转发给真实主题之前和之后还可以执行一些其他操作,增加客户端需要的额外服务。

public class Proxy implements Subject {
  private Subject realSubject = new RealSubject();
  
  public void request() {
    preRequest();
    
    // 请求转发给真实主题
    realSubject.request();
    
    postRequest();
  }
  
  public void preRequest() {
    // 转发前业务代码
  }
  
  public void postRequest() {
    // 转发后业务代码
  }
}

15.2.4 客户端调用

public class Client {
  public static void main(String[] args) {
    // 针对抽象层编程, 具体类名 Proxy 可以在配置文件写入
    // 该代码等价于 Subject subject = new Proxy();
    Subject subject = (Subject) XMLUtil.getBean();
    
    // 通过代理类访问真实主题
    subject.request();
  }
}

15.3 远程代理

Remote Proxy: 远程代理使得客户端可以访问远程主机上的对象并调用其方法。

远程代理示意图如下所示:代理对象负责接收客户端请求,并通过网络通信访问远程对象,代理对象对于客户端是透明的。

15.3.1 Java RMI

Method Invocation是Java对象间通信最基本的方法,对于同一虚拟机内的对象通信,简单的方法调用即可实现。而对于位于不同主机上的虚拟机中的对象之间通信,则需要一套远程方法调用的机制,帮助我们获得远程主机上对象的引用,并在自己的虚拟机中像使用自己的对象一样使用它。

RMI 允许我们调用远程对象上的方法,可以将 Java 对象作为参数传递,并获得 Java 对象作为返回值。因此 RMI 需要使用对象序列化,在网络上传输对象,必要时还可以使用动态类加载和安全管理器安全地传输 Java 对象。

15.3.2 Stubs and skeletons

Java RMI 主要工作流程如下所示:

Stub: 被称为 ”存根“,保存在客户端,即代理模式中出现的代理类,其负责与远程对象通信

Skeletons: 被称为 ”骨架“,保存在服务端,用于接收 Stub 发送过来的请求,调用服务端对象的方法,并将返回值发送给 Stub

15.3.3 Java RMI Server

Java RMI 服务端主要是构建一个可以通过网络传输的结果类和一个可以被远程访问的目标类,同时需要将该目标类的实例对象注册到 RMI Registry

// 自定义远程接口, 需要继承自 Remote
public interface RemoteObject extends Remote {
  public String remoteHello() throws RemoteException;
}


/**
 * 这个例子没用构建结果类,直接使用 String 类(可序列化)
 * 如果要自定义对象结果类,只需要实现 Serializable 接口即可
 */
public class RemoteObjectImpl extends UnicastRemoteObject implements RemoteObject {
  // 空的构造方法,不能使用默认构造,因为需要抛出异常
  protected RemoteObjectImpl() throws RemoteException {}
  
  public String remoteHello() throws RemoteException() {
    // 业务逻辑代码
    // return String 对象
  }
}


// 注册
public class RMIServer {
  public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
    try {
      Registry registry = LocateRegistry.createRegistry(1900);
      
      //实例化远程对象类
      RemoteObject remoteObject = new RemoteObjectImpl();
      //通过 Naming.bind()方法绑定别名与远程对象
      Naming.bind("rmi://127.0.0.1:1900/remoteService", remoteObject);
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
}

15.3.4 Java RMI Client

public class RMIClient {
  public static void main(String[] args) throws RemoteException, NotBoundException {
    Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1900);
    
    //打印注册中心的远程对象列表
    System.out.println(Arrays.toString(registry.list()));

    //通过别名获取远程对象代理 stub 并调用远程对象的方法
    RemoteObject stub = (RemoteObject) registry.lookup("remoteService");
    System.out.println(stub.remoteHello());
  }
}

15.4 动态代理

在传统的代理模式中,客户端通过代理对象调用真实主题中的方法。这种情况下,代理类和真实主题类都事先存在,且代理方法也明确指定。每个代理类经过编译之后生成字节码文件,其实现的接口和代理的方法均被固定,这种代理模式被称为静态代理

静态代理扩展非常不便,无论是需要给不同的主题增加代理还是给同一主题代理不同方法,都需要增加新的代理类,或者修改原代码(不符合开闭原则)。

动态代理(Dynamic Proxy)技术可以让系统在运行时根据需要动态创建代理类,使同一个代理类能够代理多个不同的真实主题类和不同方法。

15.4.1 动态代理实现

  1. 构建两个不同的真实主题接口

    public interface SubjectA {
      public Boolean proxyMethod1(String s);
    
      public String proxyMethod2(String s);
    }
    
    
    public interface SubjectB {
      public String proxyMethod1(String s);
    }
    
  2. 构建具体主题类

    public class ConcreteSubjectA implements SubjectA{
      @Override
      public Boolean proxyMethod1(String s) {
        // 业务逻辑代码
        return true;
      }
    
      @Override
      public String proxyMethod2(String s) {
        String res = s + "Call SubjectA proxy method2 success!";
        return res;
      }
    }
    
    
    public class ConcreteSubjectB implements SubjectB{
      @Override
      public String proxyMethod1(String s) {
        String res = s + "Call SubjectB proxy method1 success!";
        return res;
      }
    }
    
  3. 构建请求处理程序,需要实现 InvocationHandler 接口

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    
    public class DynamicProxyHandler implements InvocationHandler{
      // object: 需要动态创建的真实主题对象
      private Object object;
    
      public DynamicProxyHandler() {}
    
      public DynamicProxyHandler(Object object) {
        this.object = object;
      }
    
    
      /**
       * 实现 InvocationHandler 的 invoke() 方法
       * @param proxy: 代理对象
       * @param method: 代理方法
       * @param args: 代理方法参数列表
       */
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeInvoke();
        
        调用真实主题中定义的方法
        Object res = method.invoke(object, args);
        
        afterInvoke();
        return res;
      }
    
      // 织入一些调用前和调用后的处理逻辑, 可以为客户端提供额外的服务
      public void beforeInvoke() {
        // 真实主题代理方法调用前
        // 逻辑代码
      }
    
      public void afterInvoke() {
        // 真实主题代理方法调用后
        // 逻辑代码
      }
    }
    
  4. 客户端调用

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    public class Client {
      public static void main(String[] args) {
        InvocationHandler invocationHandler = null;
    
        SubjectA subjectA = new ConcreteSubjectA();
        SubjectB subjectB = new ConcreteSubjectB();
    
        // 创建请求转发到的处理程序
        invocationHandler = new DynamicProxyHandler(subjectA);
        SubjectA proxyA = null;
    
        /**
         * 通过反射创建代理类实例对象
         * @param loader: 代理类的加载器
         * @param interfaces: 代理类所实现的接口列表
         * @param handler: 客户端请求转发到的处理程序
         */
        proxyA = (SubjectA)Proxy.newProxyInstance(SubjectA.class.getClassLoader(), new Class[]{SubjectA.class}, invocationHandler);
        String res = proxyA.proxyMethod2("Hi ");
        System.out.println(res);
    
        invocationHandler = new DynamicProxyHandler(subjectB);
        SubjectB proxyB = null;
        proxyB = (SubjectB)Proxy.newProxyInstance(SubjectB.class.getClassLoader(), new Class[]{SubjectB.class}, invocationHandler);
        res = proxyB.proxyMethod1("Hi ");
        System.out.println(res);
      }
    }
    

15.5 代理模式优/缺点

代理模式使用场景:Java RMI (远程代理), Spring AOP(动态代理)

代理模式的优点主要如下:

  • 客户端可以针对抽象层编程,增加和更换代理类不需要修改原代码,符合开闭原则
  • 远程代理为位于不同地址空间对象的访问提供实现机制,将耗时耗资源的操作移至性能更好的远程主机,提高系统的运行效率
  • 动态代理让系统根据需求动态创建代理类,且可以让同一个代理类代理多种不同的方法,相较于静态代理,显著降低了系统中类的个数

代理模式的缺点主要如下:

  • 一些代理模式可能会需要复杂的实现过程,如远程代理
  • 一些代理模式可能会造成请求的处理速度变慢,如保护代理

标签:Java,String,对象,代理,class,设计模式,public,客户端
From: https://www.cnblogs.com/ylyzty/p/16829970.html

相关文章

  • java 封装
    封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类随机访问。要访问该类......
  • weka遇到java.util.zip.ZipException: invalid LOC header (bad signature)
    这是因为有jar包没有下载完全引起的,需要将相应包重新下载。到maven的reposity目录下搜aether*****in-progress(可以搜aether或者in-progress都行)文件,如果存在,把这个文件对......
  • java实现邮件发送功能
    配置环境 tomcat10,JDK15,MAVEN3.6.3 导入jar包   要在网络上实现邮件功能,必须要有专门的邮件服务器。这些邮件服务器类似于现实生活中的邮局,它主要负责接收用......
  • Java方法覆盖的语法规则
    一、定义方法覆盖是指,继承父类的子类,写出的方法对于其父类中某一个方法,名称相同,参数列表、名称相同,返回类型相同,而此时,以父类作申请类型,子类作申请空间时的构造函数时,子类......
  • Java连接Redis
    Java连接RedisRedis(RemoteDictionaryServer),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语......
  • Java 注释
    Java基础语法---注释注释---好习惯(是写给人看的),一定要规范!单行://多行:/*comments*/文档:/***/注解:@Authorlizhe标识符不能用关键字作为变量名或方......
  • Java异常处理机制
    程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言风格:用函数返回......
  • Java 常见数据类型
    数据类型基本类型数值类型整数byte:1个字节short:2个字节int:4个字节long:8个字节浮点float:4个字节double:8个字节字符char:2个字节Boolean类型:占1位......
  • Java背景简介
    Java特性及优势简单性面向对象可移植性:Writeonce,Runanywhere!(JVM)高性能分布式动态性:反射机制多线程:同时看视频和听音乐安全性:去掉指针和内存,异常检查机制健壮......
  • Java 8 Time API
    Java8系列文章持续更新中日期时间API也是Java8重要的更新之一,Java从一开始就缺少一致的日期和时间方法,Java8DateTimeAPI是Java核心API的一个非常好的补充。为什......