代理模式是常见的设计模式之一,用意在为指定对象提供一种代理以管制对这个对象的拜访。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:依据构造函数实例化代理对象。
@CallerSensitivepublic 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的动静代理不反对对实现类的代理,只反对接口的代理。

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

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