1 什么是代理模式
代理模式的定义: 代理模式给某一个对象提供一个代理对象, 并由代理对象控制对原对象的引用. 举例说明, 就是一个人或者一个机构代表另一个人或者另一个机构采取行动. 在一些情况下, 一个客户不想或者不能够直接引用一个对象, 而代理对象可以在客户端和目标对象之前起到中介的作用.
代理模式的关键点: 代理对象与目标对象. 代理对象是对目标对象的扩展, 并会调用目标对象
2 为什么使用代理
我们在写一个功能函数时, 经常需要在其中写入与功能不是直接相关但很有必要的代 码, 如日志记录, 信息发送, 安全和事务支持等, 这些枝节性代码虽然是必要的, 但它会带来以下麻烦:
枝节性代码游离在功能性代码之外, 它不是函数的目的, 这是对 OO 是一种破坏
枝节性代码会造成功能性代码对其它类的依赖, 加深类之间的耦合, 可重用性降低
从法理上说, 枝节性代码应该监视着功能性代码, 然后采取行动, 而不是功能性代码 通知枝节性代码采取行动, 这好比吟游诗人应该是主动记录骑士的功绩而不是骑士主动要求诗人记录自己的功绩.
静态代理和动态代理的区别
Proxy 类的代码被固定下来, 不会因为业务的逐渐庞大而庞大;
可以实现 AOP 编程, 这是静态代理无法实现的;
解耦, 如果用在 web 业务下, 可以实现数据层和业务层的分离.
动态代理的优势就是实现无侵入式的代码扩展.
静态代理这个模式本身有个大问题, 如果类方法数量越来越多的时候, 代理类的代码量是十分庞大的. 所以引入动态代理来解决此类问题
3 静态代理
静态代理在使用时, 需要定义接口或者父类, 被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
关键: 在编译期确定代理对象, 在程序运行前代理类的. class 文件就已经存在了.
应用场景举例:
以买房子为例, 在实际生活中, 我们为例方便买房子会通过中介, 中介就充当代理角色.
首先建一个买房接口
- package com.itqf.testStaticProxy;
- // 接口 规定买房子方法
- public interface IBuyHouse {void buyHouse();
- }
创建一个客户类, 继承 IBuyHouse
- package com.itqf.testStaticProxy;
- // 消费者买房子
- public class Costomer implements IBuyHouse {
- // 买房子需要多少钱
- private int money;
- public int getMoney() {
- return money;
- }
- public void setMoney(int money) {
- this.money = money;
- }
- @Override
- public void buyHouse() {
- // TODO Auto-generated method stub
- System.out.println("买房子花了"+money+"元");
- }
- }
创建代理对象
- package com.itqf.testStaticProxy;
- // 中介类 代理
- public class BuyHouseProxy implements IBuyHouse{
- private Costomer costomer;
- public BuyHouseProxy(Costomer costomer) {
- this.costomer = costomer;
- }
- @Override
- public void buyHouse() {
- // TODO Auto-generated method stub
- System.out.println("中介给客户介绍房源");
- costomer.buyHouse();
- System.out.println("客户买房成功");
- }
- }
测试
- package com.itqf.testStaticProxy;
- public class TestBuyHouse {
- public static void main(String[] args) {
- Costomer costomer = new Costomer();
- costomer.setMoney(1000000);
- BuyHouseProxy proxy = new BuyHouseProxy(costomer);
- proxy.buyHouse();
- }
- }
中介给客户介绍房源
买房子花了 1000000 元
客户买房成功
静态代理总结:
可以做到在不修改目标对象的情况下, 对目标对象进行功能扩展
缺点: 代理类和委托类实现相同的接口, 同时要实现相同的方法. 这样就出现了大量的代码重复. 如果接口增加一个方法, 除了所有实现类需要实现这个方法外, 所有代理类也需要实现此方法. 增加了代码维护的复杂度.
4 动态代理
4.1 动态代理的特点
在运行期, 通过反射机制创建一个实现了一组给定接口的新类
在运行时生成的 class, 必须提供一组 interface 给它, 然后该 class 就宣称它实现了这些 interface. 该 class 的实 例可以当作这些 interface 中的任何一个来用. 但是这个 Dynamic Proxy 其实就是一个 Proxy, 它不会替你作实质性的工作, 在生成它的实例时你必须提供一个 handler, 由它接管实际的工 作.
动态代理也叫做: JDK 代理, 接口代理
接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理 (InvocationHandler.invoke). 这样, 在接口方法数量比较多的时候, 我们可以进行灵活处理, 而不需要像静态代理那样每一个方法进行中转. 而且动态代理的应用使我们的类职责更加单一, 复用性更强
JDK 中生成代理对象的 API
代理类所在包: java.lang.reflect.Proxy
JDK 实现代理只需要使用 newProxyInstance 方法
static Object newProxyInstance(ClassLoader loader, Class [] interfaces, InvocationHandler handler)
注意该方法是在 Proxy 类中是静态方法, 且接收的三个参数:
ClassLoader loader: 指定当前目标对象使用类加载器, 用 null 表示默认类加载器
Class [] interfaces: 需要实现的接口数组
InvocationHandler handler: 调用处理器, 执行目标对象的方法时, 会触发调用处理器的方法, 从而把当前执行目标对象的方法作为参数传入
java.lang.reflect.InvocationHandler: 这是调用处理器接口, 它自定义了一个 invoke 方法, 用于集中处理在动态代理类对象上的方法调用, 通常在该方法中实现对委托类的代理访问.
- // 该方法负责集中处理动态代理类上的所有方法调用. 第一个参数既是代理类实例, 第二个参数是被调用的方法对象. 第三个方法是调用参数.
- Object invoke(Object proxy, Method method, Object[] args)
场景应用: 还是以买房子为例
首先建一个买房接口
- package com.itqf.testDynamicProxy;
- // 接口 规定买房子方法
- public interface IBuyHouse {
- void buyHouse();
- }
创建消费者
- package com.itqf.testDynamicProxy;
- // 消费者买房子
- public class Costomer implements IBuyHouse {
- // 买房子需要多少钱
- private int money;
- public int getMoney() {
- return money;
- }
- public void setMoney(int money) {
- this.money = money;
- }
- @Override
- public void buyHouse() {
- // TODO Auto-generated method stub
- System.out.println("买房子花了"+money+"元");
- }
- }
创建代理工厂类:
- package com.itqf.testDynamicProxy;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- // 实现 InvocationHandler 接口
- public class ProxyFactory implements InvocationHandler{
- private Object obj;
- public ProxyFactory(Object obj) {
- this.obj = obj;
- }
- /*
- 重写 InvocationHandler 接口中的 invoke() 方法,
- 动态代理模式可以使得我们在不改变原来已有的代码结构的情况下, 对原来的 "真实方法" 进行扩展, 增强其功能, 并且可以达到控制被代理对象的行为.
- */
- @Override
- public Object invoke(Object o, Method method, Object[] args) throws Throwable {
- System.out.println("中介给客户介绍房源");
- Object invoke = method.invoke(this.obj, args);
- System.out.println("客户买房成功");
- return invoke;
- }
- }
测试
- package com.itqf.testDynamicProxy;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Proxy;
- public class TestBuyHouse {
- public static void main(String[] args) {
- Costomer costomer = new Costomer();
- costomer.setMoney(1000000);
- InvocationHandler factory = new ProxyFactory(costomer);
- // 使用 Proxy 的 newProxyInstance() 方法
- IBuyHouse buyhouse =(IBuyHouse) Proxy.newProxyInstance(costomer.getClass().getClassLoader(), costomer.getClass().getInterfaces(), factory);
- buyhouse.buyHouse();
- }
- }
中介给客户介绍房源
买房子花了 1000000 元
客户买房成功
4.2 动态代理的好处
减少编程的工作量: 假如需要实现多种代理处理逻辑, 只要写多个代理处理器就可以了, 无需每种方式都写一个代理类.
系统扩展性和维护性增强, 程序修改起来也方便多了 (一般只要改代理处理器类就行了).
来源: https://juejin.im/post/5ad73e7e6fb9a045f1335d77