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

代理模式

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

代理的组成

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

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

代理的优点

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

代理的例子

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

  1. import java.util.*;
  2. interface Image {
  3. public void displayImage();
  4. }
  5. //on System A
  6. class RealImage implements Image {
  7. private String filename;
  8. public RealImage(String filename) {
  9. this.filename = filename;
  10. loadImageFromDisk();
  11. }
  12. private void loadImageFromDisk() {
  13. System.out.println("Loading " + filename);
  14. }
  15. public void displayImage() {
  16. System.out.println("Displaying " + filename);
  17. }
  18. }
  19. //on System B
  20. class ProxyImage implements Image {
  21. private String filename;
  22. private Image image;
  23. public ProxyImage(String filename) {
  24. this.filename = filename;
  25. }
  26. public void displayImage() {
  27. if(image == null)
  28. image = new RealImage(filename);
  29. image.displayImage();
  30. }
  31. }
  32. class ProxyExample {
  33. public static void main(String[] args) {
  34. Image image1 = new ProxyImage("HiRes_10MB_Photo1");
  35. Image image2 = new ProxyImage("HiRes_10MB_Photo2");
  36. image1.displayImage(); // loading necessary
  37. image2.displayImage(); // loading necessary
  38. }
  39. }

静态代理

静态代理需要在程序中定义两个类:目标对象类和代理对象类,为了保证二者行为的一致性,目标对象和代理对象实现了相同的接口。代理类的信息在程序运行之前就已经确定,代理对象中会包含目标对象的引用。

举例说明静态代理的使用: 假设我们有一个接口方法用于计算员工工资,有一个实现类实现了具体的逻辑,如果我们需要给计算员工工资的逻辑添加日志应该怎么办呢?直接在计算工资的实现逻辑里面添加会导致引入非业务逻辑,不符合规范。这个时候我们就可以引入一个日志代理,在计算工资前后输出相关的日志信息。

  • 计算员工工资的接口定义如下:
  1. public interface Employee {
  2. double calculateSalary(int id);
  3. }
  • 计算员工工资的实现类如下:
  1. public class EmployeeImpl {
  2. public double calculateSalary(int id){
  3. return 100;
  4. }
  5. }
  • 带有日志的代理类的实现如下:
  1. public class EmployeeLogProxy implements Employee {
  2. //代理类需要包含一个目标类的对象引用
  3. private EmployeeImpl employee;
  4. //并提供一个带参的构造方法用于指定代理哪个对象
  5. public EmployeeProxyImpl(EmployeeImpl employee){
  6. this.employee = employee;
  7. }
  8. public double calculateSalary(int id) {
  9. //在调用目标类的calculateSalary方法之前记录日志
  10. System.out.println("当前正在计算员工: " + id + "的税后工资");
  11. double salary = employee.calculateSalary(id);
  12. System.out.println("计算员工: " + id + "的税后工资结束");
  13. // 在调用目标类方法之后记录日志
  14. return salary;
  15. }
  16. }

动态代理

动态代理的代理对象类在程序运行时被创建,而静态代理对象类则是在程序编译期就确定好的,这是二者最大的不同之处。动态代理的优势再于不需要开发者手工写很多代理类,比如上面的例子中,如果再来一个Manager类计算工资的逻辑需要日志,那么我们就需要新建一个ManagerLogProxy来代理对象,如果需要代理的对象很多,那么需要写的代理类也会很多。

而使用动态代理则没有这种问题,一种类型的代理只需要写一次,就可以适用于所有的代理对象。比如上文中的EmployeeManager,二者只需要抽象一个计算薪资相关的接口,就可以使用同一套动态代理逻辑实现代理。

动态代理示例

下面我们使用上文中的EmployeeManager计算薪资的逻辑来展示动态代理的用法。

接口的抽象

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

  1. public interface SalaryCalculator {
  2. double calculateSalary(int id);
  3. }

接口的实现

  1. public class EmployeeSalaryCalculator implements SalaryCalculator{
  2. public double calculateSalary(int id){
  3. return 100;
  4. }
  5. }
  1. public class ManagerSalaryCalculator implements SalaryCalculator{
  2. public double calculateSalary(int id){
  3. return 1000000;
  4. }
  5. }

创建动态代理的InvocationHandler

  1. public class SalaryLogProxy implements InvocationHandler {
  2. private SalaryCalculator calculator;
  3. public SalaryLogProxy(SalaryCalculator calculator) {
  4. this.calculator = calculator;
  5. }
  6. @Override
  7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  8. System.out.println("--------------begin-------------");
  9. Object invoke = method.invoke(subject, args);
  10. System.out.println("--------------end-------------");
  11. return invoke;
  12. }
  13. }

创建代理对象

  1. public class Main {
  2. public static void main(String[] args) {
  3. SalaryCalculator calculator = new ManagerSalaryCalculator();
  4. InvocationHandler calculatorProxy = new SalaryLogProxy(subject);
  5. SalaryCalculator proxyInstance = (SalaryCalculator) Proxy.newProxyInstance(calculatorProxy.getClass().getClassLoader(), subject.getClass().getInterfaces(), calculatorProxy);
  6. proxyInstance.calculateSalary(1);
  7. }
  8. }

动态代理源码分析

动态代理的流程如下图所示,可以看到动态代理中包含以下内容:

  • 目标对象:我们需要代理的对象,对应上文中的new ManagerSalaryCalculator()
  • 接口:目标对象和代理对象需要共同提供的方法,对应上文中的SalaryCalculator
  • Proxy代理:用于生成代理对象类。
  • 代理对象类:通过代理和对应的参数得到的代理对象。
  • 类加载器:用于加载代理对象类的类加载器,对应上文中的calculatorProxy.getClass().getClassLoader()

Proxy.newProxyInstance

动态代理的关键代码就是Proxy.newProxyInstance(classLoader, interfaces, handler).

  • 可以看到Proxy.newProxyInstance一共做了两件事情:1.获取代理对象类的构造函数,2:根据构造函数实例化代理对象。
  1. @CallerSensitive
  2. public static Object newProxyInstance(ClassLoader loader,
  3. Class<?>[] interfaces, InvocationHandler h) {
  4. Objects.requireNonNull(h);
  5. final Class<?> caller = System.getSecurityManager() == null
  6. ? null : Reflection.getCallerClass();
  7. /*
  8. * Look up or generate the designated proxy class and its constructor.
  9. */
  10. // 获取代理对象类的构造函数,里面就包含了代理对象类的构建和加载
  11. Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
  12. // 根据构造函数生成代理实例.
  13. return newProxyInstance(caller, cons, h);
  14. }

代理对象类

通过查看源码,我们可以发现代理对象类都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. python,ctf笔记随笔

    一.在centos虚拟机中安装pyhton3环境: 安装pip3:yum install python36-pip 将pip升级到最新版本:pip3 install --upgrade pip 运行p ...

  2. 来看看是什么原因导致生产服上的系统CPU高的?

    我们可能会遇到生产服务器CPU很高的问题,有时候能确定是哪个进程,但是不知道这个进程都在干什么,所以也无从下手,无法解决问题.只能不断的重启,重启等. 最近也看了[一线码农]的一些教程,觉得都很不错, ...

  3. SpringCloud升级之路2020.0.x版-11.Log4j2 监控相关

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford Log4j2 异步 ...

  4. Setup a Simple HTTP Proxy Server

    The host 10.21.3.69 has no H3C client, so it can't access the internet. With Tinyproxy, we can setuu ...

  5. Git(9)-- 远程仓库的使用

    @ 目录 1.查看远程仓库:git remote 2.添加远程仓库:git remote add 3.从远程仓库中抓取与拉取:git fetch和 git pull 4.推送到远程仓库:git pus ...

  6. Windows常用命令汇总以及基础知识

    命令部分: dir dir指定要列出的驱动器.目录和/或文件 ,/?显示所有命令 例:dir /b /s /o:n /a:a 表示显示当前路径下的所有文件的绝对路径,包含子文件夹的内容 /b表示去除摘 ...

  7. Proteus仿真—51单片机实现AC信号测频、显示、双机通信

    文章目录 一.原理图部分 二.源码部分 单片机1 单片机2 在Proteus仿真软件里面使用STC89C52实现指定频率的AC信号的测频.显示.双机通信. 一.原理图部分 整体的电路图如示: DC-A ...

  8. 你真的熟悉ASP.NET MVC的整个生命周期吗?

    一.介绍 我们做开发的,尤其是做微软技术栈的,有一个方向是跳不过去的,那就是MVC开发.我相信大家,做ASP.NET MVC 开发有的有很长时间,当然,也有刚进入这个行业的.无论如何,如果有人问你,你 ...

  9. 一张图带你搞懂Node事件循环

    说一件重要的事儿:你还没关注公众号[前端印记],更多精彩内容等你探索-- 以下全文7000字,请在你思路清晰.精力充沛的时刻观看.保证你理解后很长时间忘不掉. Node事件循环 Node底层使用的语言 ...

  10. npm常用命令及其node相关工具汇总

    它是一个事件驱动异步I/O单进程的服务端JS环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好. 浏览器是JS的前端运行环境. Node.js是JS的后端运行环 ...