简介

定义

    代理是一种常用的Java设计模式,为其他对象提供一种代理,以控制对这个对象的访问。使用代理模式创建代理对象,能让代理对象控制目标对象的访问(目标对象可以是远程的对象、创建开销大的对象或需要安全控制的对象),并且可以在不改变目标对象的情况下添加一些额外的功能。

构成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-avv5ctYs-1645420362416)(https://upload.wikimedia.org/wikipedia/commons/thumb/7/75/Proxy_pattern_diagram.svg/400px-Proxy_pattern_diagram.svg.png)]

代理模式一般涉及到的角色有4种:

  • 主题接口:定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
  • 真实主题:真正实现业务逻辑的类;
  • 代理类:用来代理和封装真实主题;
  • 客户端:使用代理类和主题接口完成一些工作。

在代理模式中真实主题角色对于客户端角色来说的透明的,也就是客户端不知道也无需知道真实主题的存在。

为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。

通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。

特征

  • 代理对象存在的价值主要用于拦截对真实业务对象的访问;
  • 代理对象应该具有和目标对象(真实业务对象)相同的方法。

优点

  • 隐藏委托类的实现,调用者只需要和代理类进行交互即可。
  • 解耦,在不改变委托类代码情况下做一些额外处理,比如添加初始判断及其他公共操作

应用

  • 远程代理(Remote Proxy):可以隐藏一个对象存在于不同地址空间的事实。也使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求,既RPC调用。
  • 虚拟代理(Virtual Proxy):允许内存开销较大的对象在需要的时候创建。只有我们真正需要这个对象的时候才创建。
  • 写入时复制代理(Copy-On-Write Proxy):用来控制对象的复制,方法是延迟对象的复制,直到客户真的需要为止。是虚拟代理的一个变体。
  • 保护代理(Protection (Access)Proxy): 为不同的客户提供不同级别的目标对象访问权限
  • 缓存代理(Cache Proxy): 为开销大的运算结果提供暂时存储,它允许多个客户共享结果,以减少计算或网络延迟。
  • 防火墙代理(Firewall Proxy):控制网络资源的访问,保护主题免于恶意客户的侵害。
  • 同步代理(SynchronizationProxy):在多线程的情况下为主题提供安全的访问。
  • 智能引用代理(Smart ReferenceProxy) : 当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
  • 复杂隐藏代理(Complexity HidingProxy):用来隐藏一个类的复杂集合的复杂度,并进行访问控制。有时候也称为外观代理(Façade Proxy),这不难理解。复杂隐藏代理和外观模式是不一样的,因为代理控制访问,而外观模式是不一样的,因为代理控制访问,而外观模式只提供另一组接口。

分类

类型 作用 代理对象创建时机 代理对象绑定时机 原理 效率 工具
静态代理 代理单一目标对象 代码运行前 在代理类实现时就指定目标对象类(RealSubject)相同的接口 - -
动态代理 代理多个目标对象 运行时 不需要显示实现与目标对象类(RealSubject)相同的接口,而是将这种实现推迟到程序运行时由JVM来实现 反射 JDK Proxy
CGLIB Proxy

静态代理

所谓静态代理也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

优点

  • 业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。

缺点

  • 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了;
  • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

示例

下面有个场景,一个房主要出售自己的房子,但房主不知道谁要买房,也没有时间去接待每一个看房者。

现在我们就用静态代理的方式来实现房主的这一需求。

  • 首先,将出售房屋抽象成公共代理接口(Sales)
  /*** 主题接口:定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法。<br>* 在这里表示销售*/public interface Sales {/*** 提供了sell方法表示出售房屋*/void sell();}
  • 其次,房主做为整个事件的主角,自然而然的就成了真实主题,也是代理的委托者
  /*** 真实主题,具体处理业务。<br>* 在这里表示房东*/public class Owner implements Sales {@Overridepublic void sell() {System.out.println("我是房东我正在出售我的房产");}}

真实主题Owner实现了Sales接口,在接口提供的sell()方法里出售自己的房屋。

  • 再次,给房主找个中介(Agents),作为房主出售房屋的代理
  /*** 代理类:用来代理和封装真实主题<br>* 在这里表示房产中介*/public class Agents implements Sales {private Owner owner;public Agents() {}@Overridepublic void sell() {System.out.println("我是房产中介,正在核实买房者是否符合购买该房屋的资格");getOwner().sell();System.out.println("我是房产中介,正在收取提成");}private Owner getOwner() {if (owner == null) {owner = new Owner();}return owner;}}

    为了帮房主出售房屋,Agents代理也实现了Sales接口。同时Agents也拥有Owner的成员对象,在实现的sell()接口方法里,代理Agents帮房主Owner预处理了一些消息,然后调用了owner对象的sell()方法通知房主出售房屋,在房主Owner出售完房屋后,代理Agents开始收取中介费。

    有心的读者可以发现,代理Agents在访问Owner对象的时候使用了getOwner()方法,从而达到了在客户真正决定买房的时候才初始化owner对象,而不是在Agents初始化的时候就将Owner初始化。

    真实情境中,会有很多购房者要看房,但真正买的人只有一个,所以在代理Agents帮房东预处理和过滤掉所有信息之后,告诉房东你可以出售房屋了,这样大大节省了房东的时间和简化了售房的繁琐过程。而这也是用代理来实现延迟加载的好处。

  • 最后,万事俱备只欠买房的顾客了(Customer)
  /*** 客户端,使用代理类和主题接口完成一些工作。<br>* 在这里表示买房子的客户*/public class Customer {public static void main(String[]args) {Sales sales = new Agents();sales.sell();}}

    在这里买房的顾客Customer找到房产代理Agents,告诉他要买房。整个过程房东Owner对顾客Customer来说是透明的,Customer只与Agents打交道,Owner也只与Agents打交道,Agents作为Owner和Customer通信的桥梁从而有效控制了Customer和Owner的直接交流。

    观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。

动态代理

JDK Proxy

在Java的动态代理API中,有两个重要的类和接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。

InvocationHandler(Interface)

InvocationHandler是负责连接代理类和委托类的中间类必须实现的接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

InvocationHandler的核心方法:Object invoke(Object proxy, Method method, Object[] args)

  • proxy 该参数为代理类的实例;
  • method 被调用的方法对象;
  • args 调用method对象的方法参数;

该方法也是InvocationHandler接口所定义的唯一的一个方法,该方法负责集中处理动态代理类上的所有方法的调用。调用处理器根据这三个参数进行预处理或分派到委托类实例上执行。

Proxy(Class)

Proxy是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

Proxy 的静态方法

  • 获取指定代理对象所关联的调用处理器 :static InvocationHandler getInvocationHandler(Object proxy)
  • 获取关联于指定类装载器和一组接口的动态代理类的类对象 :static Class getProxyClass(ClassLoader loader, Class[] interfaces)
  • 判断指定类对象是否是一个动态代理类:static boolean isProxyClass(Class cl)
  • 为指定类装载器、一组接口及调用处理器生成动态代理类实例:static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler invocationHandler)
    • loader 指定代理类的ClassLoader加载器
    • interfaces 指定代理类要实现的接口
    • invocationHandler: 表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

使用步骤

  1. 通过实现InvocationHandler接口创建自己的调用处理器;
  2. 通过为Proxy类的newProxyInstance方法指定代理类的ClassLoader对象和代理要实现的interface以及调用处理器InvocationHandler对象 来创建动态代理类的对象;

优点

  • 动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
  • 动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。

缺点

  • JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

示例

我们还是使用上面房东出售房屋的例子,来用动态代理去实现。委托类(Owner)和公共代理接口(Sales)和静态代理的例子中的一样。

  • 实现 InvocationHandler 接口
/*** 动态代理类对应的调用处理程序类 */
public class SalesInvocationHandler implements InvocationHandler {// 代理类持有一个委托类的对象引用  private Object delegate;public SalesInvocationHandler(Object delegate) {this.delegate = delegate;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Enter method " + method.getName());long start = System.currentTimeMillis();Object result = method.invoke(delegate, args);long end = System.currentTimeMillis();System.out.println("Exit method " + method.getName());System.out.println("执行时间:" + (end - start));return result;}
}

SalesInvocationHandler实现了InvocationHandler的invoke方法,当代理对象的方法被调用时,invoke方法会被回调。其中proxy表示实现了公共代理方法的动态代理对象。

  • 通过 Proxy 类静态函数生成代理对象
/*** 客户端,使用代理类和主题接口完成一些工作。<br>* 在这里表示买房子的客户*/
public class Customer {public static void main(String[]args) {Sales delegate = new Owner();InvocationHandler handler = new SalesInvocationHandler(delegate);Sales proxy = (Sales)Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), handler);proxy.sell();}
}

上面代码通过InvocationHandler handler = new SalesInvocationHandler(delegate);将委托对象作为构造方法的参数传递给了SalesInvocationHandler来作为代理方法调用的对象。当我们调用代理对象的sell()方法时,该调用将会被转发到SalesInvocationHandler对象的invoke上,从而达到动态代理的效果。从上面代码可以看出,客户端需要负责自己创建代理对象,显得有点繁琐,其实我们可以将代理对象的创建封装到代理协调器的实现中。

  • 改进后的代理协调器
/*** 动态代理类对应的调用处理程序类 */
public class SalesInvocationHandler implements InvocationHandler {// 代理类持有一个委托类的对象引用  private Object delegate;/*** 绑定委托对象并返回一个代理类 * @param delegate* @return*/public Object bind(Object delegate) {this.delegate = delegate;return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(proxy.getClass().getInterfaces()[0]);System.out.println("Enter method " + method.getName());long start = System.currentTimeMillis();Object result = method.invoke(delegate, args);long end = System.currentTimeMillis();System.out.println("Exit method " + method.getName());System.out.println("执行时间:" + (end - start));return result;}
}
  • 这样客户端就简化成了
/*** 客户端,使用代理类和主题接口完成一些工作。<br>* 在这里表示买房子的客户*/
public class Customer {public static void main(String[]args) {Sales delegate = new Owner();Sales proxy = (Sales) new SalesInvocationHandler().bind(delegate);proxy.sell();}
}

CGLIB Proxy

CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类/实现Java接口。它广泛的被许多AOP的框架

使用,例如Spring AOP 和 dynaop,为他们提供方法的interception(拦截)。

CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。

不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

使用步骤

  1. 需要引入spring-core依赖;
  2. 引入功能包后,就可以在内存中动态构建子类;
  3. 代理的类不能为final, 否则报错;
  4. 目标对象的方法如果为final/static, 那么就不会被拦截,即不会执行目标对象额外的业务方法。

示例

  • 引入 Maven 依赖
    <dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.3.12</version></dependency>
  • 创建房东
public class Owner {/*** 提供了sell方法表示出售房屋*/public void sell() {System.out.println("我是房东我正在出售我的房产");}}
  • 创建中介
public class Agents {// 被代理对象private Object target;public Agents(Object target) {this.target = target;}/*** 创建代理对象* @return*/public Object getProxyInstance() {return Enhancer.create(target.getClass(), new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 这里只改造被代理对象的sell方法if ("sell".equals(method.getName())) {System.out.println("我是房产中介,正在核实买房者是否符合购买该房屋的资格");// 执行被代理对象的方法Object invoke = method.invoke(target, args);System.out.println("我是房产中介,正在收取提成");return invoke;}// 其余方法按原样执行return method.invoke(target, args);}});}}
  • 客户向中介购买房产
public class Customer {public static void main(String[] args) {Owner owner = new Owner();Owner agents = (Owner) new Agents(owner).getProxyInstance();agents.sell();}}

JAVA 笔记 09 代理相关推荐

  1. Java笔记09——常用类

    常用类 单例模式:是java中的一种设计模式,是优秀的程序员总结的一套比较成熟的健壮的一套规范.有23种,到架构师的时候才可以用到. 特点: 1.私有的构造器 2.静态的成员对象 3.公有的静态方法 ...

  2. Java笔记09——类和对象

    面向对象的两个要素:类和对象 类:是对一类事物的描述,是抽象的,概念上的定义. 对象:实际存在的某类事物的个体,也称为实例(instance). 属性: 类中的成员变量 行为:类中的成员方法 生活中描 ...

  3. Java笔记-09 容器 Collection

    文章目录 泛型 Collection的常用方法 List ArrayList LinkedList Vector Map HashMap与HashTable的区别 HashMap底层 TreeMap ...

  4. Java笔记(二十一) 动态代理

    动态代理 一.静态代理 代理的背后一般至少有一个实际对象,代理的外部功能和实际对象一般是一样的, 用户与代理打交道,不直接接触实际对象.代理存在的价值: 1)节省成本比较高的实际对象创建开销,按需延迟 ...

  5. 类代理java设计模式---动态代理(简单笔记)

    最近研究类代理,稍微总结一下,以后继续补充: 所谓态动代理类是在运行时生成的class,在生成它时,你必须供给一组interface给它,则态动代理类就称宣它实现了这些interface.当然,态动代 ...

  6. 【Java笔记+踩坑】SpringBoot基础3——开发。热部署+配置高级+整合NoSQL/缓存/任务/邮件/监控

      导航: [黑马Java笔记+踩坑汇总]JavaSE+JavaWeb+SSM+SpringBoot+瑞吉外卖+SpringCloud/SpringCloudAlibaba+黑马旅游+谷粒商城 目录 ...

  7. Java笔记-Java日常笔记-Java核心语言-史上最全Java笔记-Java烂笔头-实时更新(~v~)

    阿一的日常Java笔记,实时更新,有什么问题可以留言交流一下,大家一起共同进步!!! 1.Java基础 1.1.基本语法 1.1.1.关键字 ​ 定义:被java赋予特殊含义的字符串(单词): ​ 关 ...

  8. Spring-学习笔记09【JdbcTemplate的基本使用】

    Java后端 学习路线 笔记汇总表[黑马程序员] Spring-学习笔记01[Spring框架简介][day01] Spring-学习笔记02[程序间耦合] Spring-学习笔记03[Spring的 ...

  9. MyBatis-学习笔记09【09.Mybatis的多表操作】

    Java后端 学习路线 笔记汇总表[黑马程序员] MyBatis-学习笔记01[01.Mybatis课程介绍及环境搭建][day01] MyBatis-学习笔记02[02.Mybatis入门案例] M ...

最新文章

  1. 小程序页面跳转传参参数值为url时参数时 会出现丢失
  2. Python5:Script
  3. qt 信号多个链接槽_Qt原理窥探信号槽的实现细节
  4. stata F值缺失_stata面板数据回归操作之GMM
  5. 升级android 6.0系统
  6. 腾讯 深圳 25928-PHP开发工程师(深圳)
  7. python中int input_python中的input是什么
  8. 声明对象_静态变量(使用同一个类声明的对象可以共享一个值)
  9. python爬虫实际应用_如何使用python爬虫论坛?
  10. 留下考题答案造福我校后来人(考试过后再看,不要抄袭)
  11. 类模版的static成员
  12. 周志华机器学习西瓜书速记第二章绪论模型评估与选择(二)
  13. 关于电子科技大学学生用餐状况的一些调查
  14. Eclipse SVN:E200030:There are unfinished transactions detected
  15. iOS开发之自定义键盘(数字,字母类型等随意切换)
  16. 微信开发获取昵称乱码 emoji表情
  17. 室外AP设备的防雷接地
  18. JavaScript制作动画
  19. Access-Control-Allow-Credentials
  20. i(1<<j)什么意思?

热门文章

  1. 【笔记】得到-《薛兆丰的经济学课》模块二:成本的深义
  2. EMCP/DDR中专业词汇(rank、bank、die、channel)的解析!
  3. onnx_calibrate calibration代码原理分析
  4. STL中list的remove和remove_if的用法
  5. 一个整数,加上100后是一个完全平方数,再加上168,还是一个完全平方数,求该整数。(JAVA)
  6. Scss或Less中:global{...}的作用
  7. 接口测试用例的编写要点有哪些?
  8. faceui助跑赢时代——产品经理大会.上海站
  9. 慕课 北大曹健《人工智能实践-Tensorflow2.0》 全套讲义ppt 和代码数据集
  10. LSTM模型参数解释