代理模式———动态代理

动态代理

动态代理凭借其灵活性在框架中有着广泛的应用,下面简单记录一下我的学习理解;

代理模式:

基本概念

在百度百科中没有动态代理的概念,但是动态代理其实就是代理模式中的一种,所以在了解动态代理前最好先了解一下代理模式;

代理模式(英语:Proxy Pattern)是程序设计中的一种设计模式;

代理模式就是为其他对象提供一种代理以控制对这个对象的访问;在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

通过加粗的字可以了解到,代理模式应用到什么地方,这种情况在框架中是很常见的,这也是为什么代理模式在框架中广泛应用的原因;

代理模式的组成:

  • 抽象角色:通过接口或抽象类声明真实角色实现的业务方法;

  • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作;

  • 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用;

抽象角色可以理解为是真实角色的一种行为,真实角色其实就是需要被代理的角色,而代理角色就是连接真实角色的一个桥梁;

在下面的代码演示中一定一定要清楚它们三者间的关系,不然很容易就被绕晕;

代理模式的优点:

  • 职责清晰,真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰;

  • 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用;

  • 高扩展性

代理模式的分类:

代理模式分为两种:

  1. 静态代理
  2. 动态代理

静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了;

动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象,这也就意味着你不再需要关心代理类如何实现,也不再需要写代理类了;


这样一看就能看出来动态代理显然比静态代理更加灵活;但是动态代理和静态代理的原理依旧是相同的,所以学习代理模式最好从简单的静态代理开始;

静态代理实现

静态代理实现其实很简单,无非就是三部分:

  1. 被代理类的行为(接口)
  2. 被代理类or目标类(实现接口)
  3. 代理类(实现接口)

可以用简单用一张图表示它们三者间的关系:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nazIxI3F-1642867403606)(C:\Users\86185\Desktop\笔记\动态代理\三者关系.png)]
下面我将使用租房子的例子来实现静态代理;

租房子案例

假设一种情景:有一群房东想要出租房子,但是出租房子需要办手续、发布租赁信息什么的非常麻烦,所以他们就可以找到中介公司,让他们来负责房子的出租以及手续的办理,这样只要有人想要租房子,就需要通过中介公司来完成租房子了,所以租户没有办法直接去找房东租房子

在这里面就是一个典型的代理模式,房东就是目标类,中介公司是代理类,房东有租房子的行为,而中介公司也是办理的租房子的业务,所以他们两个的行为是相同的,都是租房子,这就对应上上图的三种关系了;

一定要分的清这几种关系,不要到代码里糊涂了;

代码实现

下面就用代码来实现一下(代码有详细注释):

租房子行为

RentHouse.java

// 代理类和被代理类都有的行为
// 被代理类:各种房东要出租房子
// 代理类:中介公司帮助房东出租房子,也相当于有出租房子这个行为
public interface RentHouse {
    // 出租房子方法
    void rent();
}

房东(这里就定了两个):实现RentHouse.java接口

HostJack.java

import com.yang.RentHouse;

// 一个名为Jack的房东要出租房子
public class HostJack implements RentHouse {
    @Override
    public void rent() {
        System.out.println("我Jack今天要出租房子!!!");
    }
}

HostBill.java

import com.yang.RentHouse;

// 一个名为Bill的房东要出租房子
public class HostBill implements RentHouse {
    @Override
    public void rent() {
        System.out.println("我Bill今天要出租房子!!!");
    }
}

中介公司:实现RentHouse.java接口

因为不同的房东的房子不同,所以他们不能使用相同的代理,即一个房东对应着一个代理,一个代理类对应着一个被代理类

ProxyCompanyForJack.java

import com.yang.RentHouse;
import com.yang.byproxy.HostJack;

// 中介公司为Jack服务
public class ProxyCompanyForJack implements RentHouse {
    // 声明被代理类对象(出租房子的人Jack)
    // 因为代理类存在的意义就是让‘租房子的人’可以通过‘代理类’联系到‘出租房子的人’
    HostJack jack;
    // 设置传入的房东jack
    public void setJack(HostJack jack) {
        this.jack = jack;
    }
    @Override
    public void rent() {
        // 调用被代理类(出租房子的人Jack)的rent方法
        jack.rent();
    }
}

ProxyCompanyForBill.java

import com.yang.RentHouse;
import com.yang.byproxy.HostBill;

// 中介公司为Bill服务
public class ProxyCompanyForBill implements RentHouse {
    // 同样声明被代理类对象(出租房子的人Bill)
    HostBill bill;
    // 设置传入的房东bill
    public void setBill(HostBill bill) {
        this.bill = bill;
    }
    @Override
    public void rent() {
        // 调用被代理类(出租房子的人bill)的rent方法
        bill.rent();
    }
}

可以看到代理类并没有真正实现接口RentHouse的rent方法,而是调用的目标类的rent方法,这就是代理的特点之一;

三者间的关系已经梳理完成,下面就可以测试一下了:

测试:

import com.yang.byproxy.HostBill;
import com.yang.byproxy.HostJack;
import com.yang.proxy.ProxyCompanyForBill;
import com.yang.proxy.ProxyCompanyForJack;

// 租房子测试
// 其实这就类似中介公司来了一个租房子的人要租房子
public class RentTest {
    public static void main(String[] args) {
        // 被代理类:房东Jack
        HostJack jack = new HostJack();
        // 创建代理类对象(假设租户这时要看Jack的房子,就需要Jack的代理)
        ProxyCompanyForJack forJack = new ProxyCompanyForJack();
        // 将jack房东传入代理类中,表示他要出租房子了
        forJack.setJack(jack);
        // 调用出租房子的方法
        // 表面上看的是调用的中介公司的rent方式,实际则是通过中介公司调用的Jack房东的rent方法
        forJack.rent();

        /
        // 同样房东Bill也是一样
        HostBill bill = new HostBill();
        ProxyCompanyForBill forBill = new ProxyCompanyForBill();
        forBill.setBill(bill);
        forBill.rent();
    }
}

输出结果:

我Jack今天要出租房子!!!
我Bill今天要出租房子!!!

这就是一个完整的静态代理的实现了,当然很简单,只是为了展示好他们之间的关系;

可能第一次接触代理类会感觉有点怪:在测试的时候明明创建了被代理类房东,直接用创建的房东对象调用rent方法不好吗?这不是多此一举吗?

如果你有这个疑问,一定要清楚代理模式的前提:用户无法直接调用被代理类的方法,也就是说房东你是接触不到的,但是你又想用房东的rent方法,那么就只能通过代理类来实现了,代理类就是一个媒介,就是你和房东的桥梁;

静态代理缺点

分析一下静态代理存在的问题:

如果在一个项目中,目标类(被代理类)和代理类很多时候,有以下的缺点:

  • 当目标类增加了, 代理类可能也需要成倍的增加,因为一个代理类对应一个目标类,会造成代理类数量过多,不易于管理,代码量增多;
  • 当你的接口中功能增加了, 或者修改了,会影响众多的实现类,厂家类,代理都需要修改,影响比较多,修改成本太高;

而动态代理就可以很好的解决上面的问题;

动态代理实现

在动态代理中即使目标类很多, 但是代理类数量可以很少,并且当你修改了接口中的方法时,不会影响代理类;

动态代理: 在程序执行过程中,使用jdk的反射机制,创建代理类对象, 并动态的指定要代理目标类;

它可以实现不用写代理类的实现就可以创建代理类对象;

jdk 动态代理是基于 Java 的反射机制实现的,使用 jdk 中接口和类实现代理对象的动态创建

jdk 动态代理要求目标对象(被代理类)必须实现接口,这是 java 设计上的要求,从 jdk1.3 以来,java 通过 java.lang.reflect 包提供三个类支持代理模式 ,分别是:Proxy, Method 和 InovcationHandler;

所以在写动态代理代码前需要了解一下这三个类;

InvocationHandler 接口

InvocationHandler 接口叫做调用处理器,负责完调用目标方法(就是被代理类中的方法),并增强功能;

通过代理类对象执行目标接口中的方法,会把方法的调用分派给调用处理器 (InvocationHandler)的实现类,执行 实现类中的 invoke() 方法,我们需要把在该invoke方法中实现调用目标类的目标方法;

(记住这里的实现类,在这个实现类中会调用目标类中的目标方法)

InvocationHandler接口:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BwVJk4UM-1642867403607)(C:\Users\86185\Desktop\笔记\动态代理\InvocationHandler接口.png)]

invoke方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XQYmn0vX-1642867403607)(C:\Users\86185\Desktop\笔记\动态代理\invoke方法(handler内的).png)]
在 invoke 方法中可以截取对目标方法的调用,调用方式是通过反射调用;

方法中的参数:

public Object invoke ( Object proxy, Method method, Object[] args)

  • proxy:生成的代理对象

  • method:目标方法

  • args:目标方法的参数

第一个参数 proxy 是 jdk 在运行时赋值的,在方法中直接使用,所以这个参数不需要我们管;

第二个参数method是实现调用目标方法的关键,只有通过它的invoke方法(这是Method对象的内置方法)才可以调用到被代理类的目标方法;

第三个参数是方法执行的参数, 这三个参数都是 jdk 运行时赋值的,无需程序员给出,所以也不需要我们管;

Method 类

这里就说说invoke中的第二个参数method,它是Method类的实例化对象,Method类也有一个方法叫invoke(),

该方法在反射中就是用来执行反射对象的方法的,虽然这两个invoke方法名字一样,但是没有一点关系;

public Object invoke ( Object obj, Object... args)

  • obj:表示目标对象

  • args:表示目标方法参数,就是其上一层 invoke 方法的第三个参数

该方法的作用是:调用执行 obj 对象所属类的方法,这个方法由其调用者 Method 对象确定;

就是因为method方法是InvocationHandler接口中的invoke方法的第二个参数,所以我们可以通过它实现对目标类的目标方法的调用;

从这可以联想到静态代理中的代理类中也是调用了目标类的目标方法;

Proxy 类

通过 JDK 的 java.lang.reflect.Proxy 类实现动态代理 ,使用其静态方法 newProxyInstance(),依据目标对象(被代理类的对象)、业务接口及调用处理器三者,自动生成一个动态代理对象

代理对象是由Proxy类来创建的,所以动态代理在使用上并没有那么难,因为最难的部分java已经帮你实现了,你只需要掌握使用它的方法就可以了;

public static newProxyInstance ( ClassLoader loader, Class[] interfaces, InvocationHandler handler)

  • loader:目标类的类加载器,通过目标对象的反射可获取
  • interfaces:目标类实现的接口数组,通过目标对象的反射可获取
  • handler:调用处理器

这几个参数一定要记好他们的功能,不然代码可能看不懂;

代码实现

下面我就用代码来简单实现一下,首先分析两个问题:

问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象(代理类如何创建)

问题二:当通过代理类的对象调用接口实现方法时,如何动态的去调用被代理类中的目标方法(如何通过代理类调用被代理类中的目标方法)

下面我就用代码实现动态代理,同样这两个问题的答案也在代码中;

还是租房子案例:

RentHouse.java

// 代理类和被代理类都有的行为
// 被代理类:各种房东要出租房子
// 代理类:中介公司帮助房东出租房子,也相当于有出租房子这个行为
public interface RentHouse {
    // 出租房子方法
    void rent();
}

房东(这里就定了两个):实现RentHouse.java接口

HostJack.java

import com.yang.RentHouse;

// 一个名为Jack的房东要出租房子
public class HostJack implements RentHouse {
    @Override
    public void rent() {
        System.out.println("我Jack今天要出租房子!!!");
    }
}

HostBill.java

import com.yang.RentHouse;

// 一个名为Bill的房东要出租房子
public class HostBill implements RentHouse {
    @Override
    public void rent() {
        System.out.println("我Bill今天要出租房子!!!");
    }
}

到目前为止,代码还没有任何不同,下面就是变化最大的地方;

动态代理的实现:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 这个类可以简单理解为一个代理工具类
// 通过该类可以创建任意代理
public class ProxyCompany {
    // 调用该方法返回一个对应的代理类对象
    // 因为无法判断返回的是什么类型的代理,所以返回值是Object
    // 传入参数obj是目标类的实列,通过该‘目标类’创建对应的‘代理类’对象
    // 同样无法判断传入的目标类是什么类型,所以只能使用Object
    public static Object getProxyInstance(Object obj) {
        // 创建一个handler对象,它的作用就是实现通过‘代理类’去调用‘目标类’的目标方法
        MyInvocationHandler handler = new MyInvocationHandler();
        // 设置当前的目标类对象
        handler.setObj(obj);
        // 调用lang包下自带的Proxy类的静态方法newProxyInstance()创建代理类
        // 这就是java已经帮你实现的一个创建代理类的方法,你只需要传入对应参数即可
        // loader:目标类的类加载器
		// interfaces:目标类实现的接口数组(这里就是RentHouse)
		// handler:调用处理器
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
    }
}
// 这个类也是一个类似工具类,它主要是为了实现重写InvocationHandler接口的invoke方法,
// 因为通过上面创建的‘代理类’会运行到该类中调用‘目标类’的方法
// 所以实际上这个类就是为了辅助上面的类ProxyCompany的
class MyInvocationHandler implements InvocationHandler {
    private Object obj; // 目标类对象

    // 设置目标类对象
    public void setObj(Object obj) {
        this.obj = obj;
    }
    // proxy:当前的代理类
    // method:当前‘代理类’对象调用的方法,该方法也就作为了‘目标类’对象要调用的方法(动态的无法确定是哪一个)
    // args:方法中的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 调用目标类中的方法
        // this.obj:目标类对象
        Object returnValue = method.invoke(this.obj, args); // 传入对象和方法参数
        //上述方法的返回值就作为当前类中的invoke()的返回值。
        return  returnValue;
    }
}

这部分代码就是核心部分,注释也写的很详细,仔细琢磨几遍就能理解,就是个用法并不难;

测试代码:

import com.yang.RentHouse;
import com.yang.byproxy.HostBill;
import com.yang.byproxy.HostJack;
import com.yang.proxy.ProxyCompany;

// 租房子测试
public class RentTest {
    public static void main(String[] args) {
        // 被代理类:房东Jack
        HostJack jack = new HostJack();
        // 获取代理对象
        RentHouse forJack = (RentHouse) ProxyCompany.getProxyInstance(jack);
        // 执行出租房子方法
        forJack.rent();

        
        // 房东Bill也是一样
        HostBill bill = new HostBill();
        RentHouse forBill = (RentHouse) ProxyCompany.getProxyInstance(bill);
        forBill.rent();
    }
}

可以看出来其实动态代理和静态代理还是很多地方是相同的;

输出结果:

我Jack今天要出租房子!!!
我Bill今天要出租房子!!!

关键的几步:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-atlWXaKM-1642867403608)(C:\Users\86185\Desktop\笔记\动态代理\调用流程.png)]
如果实在搞不清楚的话强烈建议把代码模仿写下来之后 debug 走几回,分析什么时候创建代理,什么时候执行方法,多走几遍就清楚整个流程了;

总结

实现动态代理基本的步骤:

  1. 创建接口,定义目标类要完成的功能
  2. 创建目标类实现接口
  3. 通过Proxy创建代理类对象;
  4. 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能;

这只是简单说了一下动态代理的用法,如果想对动态代理有更多了解,可以看看Proxy对应newProxyInstance()方法的底层源码(本人太菜还不到研究源码的地步😭)

  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YXXYX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值