AI智能
改变未来

Java基础之代理模式

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

代理模式

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

代理的组成

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

  • 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
  • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
  • 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

代理的优点

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

代理的例子

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

import java.util.*;interface Image {public void displayImage();}//on System Aclass 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 Bclass 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 necessaryimage2.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

来代理对象,如果需要代理的对象很多,那么需要写的代理类也会很多。

而使用动态代理则没有这种问题,一种类型的代理只需要写一次,就可以适用于所有的代理对象。比如上文中的

Employee

Manager

,二者只需要抽象一个计算薪资相关的接口,就可以使用同一套动态代理逻辑实现代理。

动态代理示例

下面我们使用上文中的

Employee

Manager

计算薪资的逻辑来展示动态代理的用法。

接口的抽象

我们知道

Employee

Manager

都有计算薪资的逻辑,而且需要对计算薪资的逻辑进行日志记录,所以我们需要抽象一个计算薪资的接口:

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;}@Overridepublic 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的动态代理不支持对实现类的代理,只支持接口的代理。

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

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

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Java基础之代理模式