代理模式是常见的设计模式之一,用意在为指定对象提供一种代理以管制对这个对象的拜访。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:依据构造函数实例化代理对象。
@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的动静代理不反对对实现类的代理,只反对接口的代理。
我是御狐神,欢送大家关注我的微信公众号
本文最先公布至微信公众号,版权所有,禁止转载!