共计 4894 个字符,预计需要花费 13 分钟才能阅读完成。
代理模式是常见的设计模式之一,用意在为指定对象提供一种代理以管制对这个对象的拜访。Java 中的代理分为动静代理和动态代理,动静代理在 Java 中的利用比拟宽泛,比方 Spring 的 AOP 实现、近程 RPC 调用等。动态代理和动静代理的最大区别就是代理类是 JVM 启动之前还是之后生成。本文会介绍 Java 的动态代理和动静代理,以及二者之间的比照,重点是介绍动静代理的原理及其实现。
代理模式
代理模式的定义:为其余对象提供一种代理以管制对这个对象的拜访。在某些状况下,一个对象不适宜或者不能间接援用另一个对象,而代理对象能够在客户端和指标对象之间起到中介的作用。比如说:要拜访的对象在近程的机器上。在面向对象零碎中,有些对象因为某些起因(比方对象创立开销很大,或者某些操作须要安全控制,或者须要过程外的拜访),间接拜访会给使用者或者系统结构带来很多麻烦,咱们能够在拜访此对象时加上一个对此对象的拜访层。
代理的组成
代理由以下三局部角色组成:
- 形象角色:通过接口或抽象类申明实在角色实现的业务办法。
- 代理角色:实现形象角色,是实在角色的代理,通过实在角色的业务逻辑办法来实现形象办法,并能够附加本人的操作。
- 实在角色:实现形象角色,定义实在角色所要实现的业务逻辑,供代理角色调用。
代理的长处
- 职责清晰:实在的角色就是实现理论业务的逻辑,不必关系非业务的逻辑(如事务管理)。
- 隔离作用:代理对象能够在客户端和指标对象之间起到中介作用,指标对象不间接裸露给客户端,从而实现隔离指标对象的作用
- 高可扩展性:代理对象能够对指标对象进行灵便的扩大。
代理的例子
咱们用一个加载并显示图片的例子来解释代理的工作原理,图片存在磁盘上,每次 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
来代理对象,如果须要代理的对象很多,那么须要写的代理类也会很多。
而应用动静代理则没有这种问题,一种类型的代理只须要写一次,就能够实用于所有的代理对象。比方上文中的 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;}
@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 的动静代理不反对对实现类的代理,只反对接口的代理。
我是御狐神,欢送大家关注我的微信公众号
本文最先公布至微信公众号,版权所有,禁止转载!