代理模式是常见的设计模式之一,意图在为指定对象提供一种代理以控制对这个对象的访问。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的动态代理不支持对实现类的代理,只支持接口的代理。

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

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

Java基础之代理模式的更多相关文章

  1. JAVA安全基础之代理模式(二)

    JAVA安全基础之代理模式(二) 上篇讲到静态代理模式,这时候我们发现,一个代理类只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐.所以就有了动态代理 动态代理 动态代理的 ...

  2. JAVA安全基础之代理模式(一)

    JAVA安全基础之代理模式(一) 代理模式是java的一种很常用的设计模式,理解代理模式,在我们进行java代码审计时候是非常有帮助的. 静态代理 代理,或者称为 Proxy ,简单理解就是事情我不用 ...

  3. (转)轻松学,Java 中的代理模式及动态代理

    背景:讲到反射机制,肯定会想到动态代理. 轻松学,Java 中的代理模式及动态代理 代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强.值得注意的是,代理类和被代理类应该 ...

  4. Java设计模式之代理模式(静态代理和JDK、CGLib动态代理)以及应用场景

    我做了个例子 ,需要可以下载源码:代理模式 1.前言: Spring 的AOP 面向切面编程,是通过动态代理实现的, 由两部分组成:(a) 如果有接口的话 通过 JDK 接口级别的代理 (b) 如果没 ...

  5. Java设计模式:代理模式(转)

    代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.这里使用到编程中的一 ...

  6. 说说Java中的代理模式

    今天看到传智播客李勇老师的JDBC系列的第36节——通过代理模式来保持用户关闭连接的习惯.讲的我彻底蒙蔽了,由于第一次接触代理模式,感到理解很难,在博客园找到一篇文章,先记录如下: 引用自java设计 ...

  7. 谈谈Java中的代理模式

    首先来看一下代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用, 其特征是代理类与 ...

  8. java设计模式6——代理模式

    java设计模式6--代理模式 1.代理模式介绍: 1.1.为什么要学习代理模式?因为这就是Spring Aop的底层!(SpringAop 和 SpringMvc) 1.2.代理模式的分类: 静态代 ...

  9. JAVA设计模式:代理模式&& 装饰模式区别

    在前面学习了代理模式和装饰模式后,发现对两者之间有时候会混淆,因此对两者进行了区别和理解: 装饰模式你可以这样理解,就像糖一样,卖的时候商家大多要在外面包一层糖纸,其实原本还是糖. public in ...

随机推荐

  1. JUC学习笔记(一)

    1.什么是 JUC 1.1.JUC简介 在 Java 中,线程部分是一个重点,本篇文章说的 JUC 也是关于线程的.JUC 就是 java.util .concurrent 工具包的简称.这是一个处理 ...

  2. Mol Cell丨吕志民团队揭示琥珀酰化介导的肿瘤细胞氧化应激调控新机制

    蛋白质琥珀酰化修饰 (succinylation) ,作为赖氨酸酰化修饰家族的重要一员,于2011年由芝加哥大学赵英明教授团队在Nature Chemical Biology 发文被首次报道,并被评为 ...

  3. 了解CSS in JS(JSS)以及在React项目中配置并使用JSS

    目录 认识JSS 什么是JSS JSS 的常见实现 JSS 的好处与坏处 好处 坏处 使用模块化CSS实现JSS 安装插件 在React项目中的tsconfig.json中添加配置 vscode项目中 ...

  4. 第4篇-JVM终于开始调用Java主类的main()方法啦

    在前一篇 第3篇-CallStub新栈帧的创建 中我们介绍了generate_call_stub()函数的部分实现,完成了向CallStub栈帧中压入参数的操作,此时的状态如下图所示. 继续看gene ...

  5. 1day漏洞反推技巧实战(1)

    学习笔记里的存货(1) 以前看了一篇推特老外做赏金猎人的文章,感触有点深,作者没有写相关漏洞分析,只是说了自己挖了多少个漏洞,这里简单的分析下: 1day漏洞在很多时候至关重要,不管是在红蓝对抗,还是 ...

  6. 在STM32F401上移植uC/OS的一个小问题 [原创]

    STM32F401xx是意法半导体新推出的Cortex-M4内核的MCU,相较于已经非常流行的STM32F407xx和STM32F427xx等相同内核的MCU而言,其特点是功耗仅为128uA/MHz, ...

  7. Greenplum数仓监控解决方案(开源版本)

    Greenplum监控解决方案 基于Prometheus+Grafana+greenplum_exporter+node_exporter实现 关联图 一.基本概念 1.Prometheus ​ Pr ...

  8. STM32—驱动HC-SR04超声波测距模块

    文章目录 超声波测距原理 HC-SR04工作原理 STM32实现驱动 1.引脚的配置 2.时序控制 3.时间差测量 4.如何将距离测出来 超声波测距原理 利用HC-SR04超声波测距模块可以实现比较精 ...

  9. NOIP 模拟 $25\; \rm random$

    题解 \(by\;zj\varphi\) 期望好题. 通过推规律可以发现每个逆序对的贡献都是 \(1\),那么在所有排列中有多少逆序对,贡献就是多少. \[\rm num_i=(i-1)!\sum_{ ...

  10. sentinel使用(结合gateway)

    前 如果你想在Spring Cloud Gateway中使用Sentinel Starter,你需要添加Spring - Cloud -alibaba- Sentinel - Gateway依赖,并添 ...