关于java:Java基础之代理模式

34次阅读

共计 4894 个字符,预计需要花费 13 分钟才能阅读完成。

代理模式是常见的设计模式之一,用意在为指定对象提供一种代理以管制对这个对象的拜访。Java 中的代理分为动静代理和动态代理,动静代理在 Java 中的利用比拟宽泛,比方 Spring 的 AOP 实现、近程 RPC 调用等。动态代理和动静代理的最大区别就是代理类是 JVM 启动之前还是之后生成。本文会介绍 Java 的动态代理和动静代理,以及二者之间的比照,重点是介绍动静代理的原理及其实现。

代理模式

代理模式的定义:为其余对象提供一种代理以管制对这个对象的拜访。在某些状况下,一个对象不适宜或者不能间接援用另一个对象,而代理对象能够在客户端和指标对象之间起到中介的作用。比如说:要拜访的对象在近程的机器上。在面向对象零碎中,有些对象因为某些起因(比方对象创立开销很大,或者某些操作须要安全控制,或者须要过程外的拜访),间接拜访会给使用者或者系统结构带来很多麻烦,咱们能够在拜访此对象时加上一个对此对象的拜访层。

代理的组成

代理由以下三局部角色组成:

  • 形象角色:通过接口或抽象类申明实在角色实现的业务办法。
  • 代理角色:实现形象角色,是实在角色的代理,通过实在角色的业务逻辑办法来实现形象办法,并能够附加本人的操作。
  • 实在角色:实现形象角色,定义实在角色所要实现的业务逻辑,供代理角色调用。

代理的长处

  1. 职责清晰:实在的角色就是实现理论业务的逻辑,不必关系非业务的逻辑(如事务管理)。
  2. 隔离作用:代理对象能够在客户端和指标对象之间起到中介作用,指标对象不间接裸露给客户端,从而实现隔离指标对象的作用
  3. 高可扩展性:代理对象能够对指标对象进行灵便的扩大。

代理的例子

咱们用一个加载并显示图片的例子来解释代理的工作原理,图片存在磁盘上,每次 IO 会破费比拟多的事件,如果咱们须要频繁的显示图片,每次都从磁盘读取会破费比拟长的工夫。咱们通过一个代理来缓存图片,只有第一次读取图片的时候才从磁盘读取,之后都从缓存中读取,源码示例如下:

import java.util.*;
 
interface Image {public void displayImage();
}

//on System A 
class RealImage implements Image {
    private String filename;
    public RealImage(String filename) { 
        this.filename = filename;
        loadImageFromDisk();}

    private void loadImageFromDisk() {System.out.println("Loading" + filename);
    }

    public void displayImage() {System.out.println("Displaying" + filename); 
    }
}

//on System B 
class ProxyImage implements Image {
    private String filename;
    private Image image;
 
    public ProxyImage(String filename) {this.filename = filename;}
    public void displayImage() {if(image == null)
              image = new RealImage(filename);
        image.displayImage();}
}
 
class ProxyExample {public static void main(String[] args) {Image image1 = new ProxyImage("HiRes_10MB_Photo1");
        Image image2 = new ProxyImage("HiRes_10MB_Photo2");     
        
        image1.displayImage(); // loading necessary
        image2.displayImage(); // loading necessary}
}

动态代理

动态代理须要在程序中定义两个类:指标对象类和代理对象类,为了保障二者行为的一致性,指标对象和代理对象实现了雷同的接口。代理类的信息在程序运行之前就曾经确定,代理对象中会蕴含指标对象的援用。

举例说明动态代理的应用:假如咱们有一个接口办法用于计算员工工资,有一个实现类实现了具体的逻辑,如果咱们须要给计算员工工资的逻辑增加日志应该怎么办呢?间接在计算工资的实现逻辑外面增加会导致引入非业务逻辑,不符合规范。这个时候咱们就能够引入一个日志代理,在计算工资前后输入相干的日志信息。

  • 计算员工工资的接口定义如下:
public interface Employee {double calculateSalary(int id);
}
  • 计算员工工资的实现类如下:
public class EmployeeImpl {public double calculateSalary(int id){return 100;}
}
  • 带有日志的代理类的实现如下:
public class EmployeeLogProxy implements Employee {

    // 代理类须要蕴含一个指标类的对象援用
    private EmployeeImpl employee;

    // 并提供一个带参的构造方法用于指定代理哪个对象
    public EmployeeProxyImpl(EmployeeImpl employee){this.employee = employee;}

    public double calculateSalary(int id) {

        // 在调用指标类的 calculateSalary 办法之前记录日志
        System.out.println("以后正在计算员工:" + id + "的税后工资");
        double salary = employee.calculateSalary(id);
        System.out.println("计算员工:" + id + "的税后工资完结");
        // 在调用指标类办法之后记录日志
        return salary;
    }
}

动静代理

动静代理的代理对象类在程序运行时被创立,而动态代理对象类则是在程序编译期就确定好的,这是二者最大的不同之处。动静代理的劣势再于不须要开发者手工写很多代理类,比方下面的例子中,如果再来一个 Manager 类计算工资的逻辑须要日志,那么咱们就须要新建一个 ManagerLogProxy 来代理对象,如果须要代理的对象很多,那么须要写的代理类也会很多。

而应用动静代理则没有这种问题,一种类型的代理只须要写一次,就能够实用于所有的代理对象。比方上文中的 EmployeeManager,二者只须要形象一个计算薪资相干的接口,就能够应用同一套动静代理逻辑实现代理。

动静代理示例

上面咱们应用上文中的 EmployeeManager计算薪资的逻辑来展现动静代理的用法。

接口的形象

咱们晓得 EmployeeManager都有计算薪资的逻辑,而且须要对计算薪资的逻辑进行日志记录,所以咱们须要形象一个计算薪资的接口:

public interface SalaryCalculator {double calculateSalary(int id);
}

接口的实现

public class EmployeeSalaryCalculator implements SalaryCalculator{public double calculateSalary(int id){return 100;}
}
public class ManagerSalaryCalculator implements SalaryCalculator{public double calculateSalary(int id){return 1000000;}
}

创立动静代理的 InvocationHandler

public class SalaryLogProxy implements InvocationHandler {
    private SalaryCalculator calculator;

    public SalaryLogProxy(SalaryCalculator calculator) {this.calculator = calculator;}

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("--------------begin-------------");
        Object invoke = method.invoke(subject, args);
        System.out.println("--------------end-------------");
        return invoke;
    }
}

创立代理对象

public class Main {public static void main(String[] args) {SalaryCalculator calculator = new ManagerSalaryCalculator();
        InvocationHandler calculatorProxy = new SalaryLogProxy(subject);
        SalaryCalculator proxyInstance = (SalaryCalculator) Proxy.newProxyInstance(calculatorProxy.getClass().getClassLoader(), subject.getClass().getInterfaces(), calculatorProxy);
        proxyInstance.calculateSalary(1);
    }

}

动静代理源码剖析

动静代理的流程如下图所示,能够看到动静代理中蕴含以下内容:

  • 指标对象:咱们须要代理的对象,对应上文中的new ManagerSalaryCalculator()
  • 接口:指标对象和代理对象须要独特提供的办法,对应上文中的SalaryCalculator
  • Proxy 代理:用于生成代理对象类。
  • 代理对象类:通过代理和对应的参数失去的代理对象。
  • 类加载器:用于加载代理对象类的类加载器,对应上文中的calculatorProxy.getClass().getClassLoader()

Proxy.newProxyInstance

动静代理的要害代码就是Proxy.newProxyInstance(classLoader, interfaces, handler).

  • 能够看到 Proxy.newProxyInstance 一共做了两件事件:1. 获取代理对象类的构造函数,2:依据构造函数实例化代理对象。
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                Class<?>[] interfaces, InvocationHandler h) {Objects.requireNonNull(h);

    final Class<?> caller = System.getSecurityManager() == null
                                    ? null : Reflection.getCallerClass();

    /*
     * Look up or generate the designated proxy class and its constructor.
     */
    // 获取代理对象类的构造函数,外面就蕴含了代理对象类的构建和加载
    Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
 
    // 依据构造函数生成代理实例.
    return newProxyInstance(caller, cons, h);
}

代理对象类

通过查看源码,咱们能够发现代理对象类都 extend 了 Proxy 类并实现了指定接口中的办法。因为 java 不能多继承,这里曾经继承了 Proxy 类了,不能再继承其余的类。所以 JDK 的动静代理不反对对实现类的代理,只反对接口的代理。

我是御狐神,欢送大家关注我的微信公众号

本文最先公布至微信公众号,版权所有,禁止转载!

正文完
 0