首页 课程 师资 教程 报名

Java代理模式和动态代理详细信息

  • 2022-06-27 10:55:18
  • 1172次 动力节点

Java的动态代理模式在实践中的使用场景非常广泛,比如大部分场景的Spring AOP、Java注解的获取、日志记录、用户认证等等。动力节点小编带你了解代理模式、静态代理、原生动态基于JDK的代理。

代理模式

无论你是学习静态代理还是动态代理,我们都需要先了解代理模型。

先看百度百科的定义:

代理模式的定义:为其他对象提供一个代理来控制对这个对象的访问。在某些情况下,一个对象不合适或不能直接引用另一个对象,而代理对象可以充当客户端和目标对象之间的中介。

直接看定义可能有些难以理解,下面就用生活中的具体例子来说明吧。

我们都去超市买过货,超市从厂家买后卖给我们。我们通常不知道货物要经过多少道工序才能到达超市。

在这个过程中,意味着制造商“委托”超市销售商品,而我们(实物)是看不见的。超市(代理对象)充当制造商的“代理人”与我们互动。

同时,超市还可以根据具体的销售情况进行折扣处理,丰富代理商的功能。

使用代理模型,我们可以做两件事:

1.隐藏委托类的实现。

2. 将客户端与委托类解耦,在不改变委托类代码的情况下增加一些额外的功能(日志、权限)。

代理模式角色定义

在编程过程中,我们可以定义三种类型的对象:

Subject(抽象主题角色):定义代理类和真实主题的公共外部方法,也是代理类代理真实主题的一种方式。例如:广告、销售等。

RealSubject:真正实现业务逻辑的类。例如Vendor,实现广告、销售等方法。

代理:用于代理和封装真实的主题。例如广告和销售等超时也实现。

上述三个角色的类图如下:

静态代理实例

静态代理是指在程序运行之前已经存在代理类,这种情况下代理类通常是在Java代码中定义的。

让我们用一个具体的例子来演示一个静态代理。

首先定义一组接口Sells,提供广告、销售等功能。然后提供Vendor类(制造商,代理对象)和Shop(超市,代理类),分别实现Sell接口。

Sell 接口定义如下:

/**
 * 委托和代理类都实现了 Sell 接口
 * @作者秒
 * @版本 1.0
 * @日期 2020/3/21 上午 9:30
 **/
公共接口卖{
    /**
     * 卖
     */
    无效出售();
    /**
     * 广告
     */
    无效广告();
}

Vendor 类定义如下:

/**
 * 供应商
 * @作者秒
 * @版本 1.0
 * @日期 2020/3/21 上午 9:30
 **/
公共类供应商实现 Sell{
    @覆盖
    公共无效出售(){
        System.out.println("店铺卖货");
    }
    @覆盖
    公共无效广告(){
        System.out.println("店铺广告商品");
    }
}

Shop 类定义如下:

/**
 * 超市、代理商
 * @作者秒
 * @版本 1.0
 * @日期 2020/3/21 上午 9:30
 **/
公共类 Shop 实现 Sell{
    私人出售出售;
    公共商店(卖卖){
        this.sell = 卖;
    }
    @覆盖
    公共无效出售(){
        System.out.println("代理类店铺,处理销售");
        出售.sell();
    }
    @覆盖
    公共无效广告(){
        System.out.println("代理类店铺,处理广告");
        sell.ad();
    }
}

代理类Shop通过聚合持有对代理类Vendor的引用,并在对应的方法中调用对应的Vendor方法。我们可以在hop类中增加一些额外的处理,比如过滤购买用户、记录日志等。

让我们看看代理类是如何在客户端中使用的。

/**
 * 静态代理类测试方法
 * @作者秒
 * @版本 1.0
 * @日期 2020 年 3 月 21 日上午 9 点 33 分
 **/
公共类静态代理 {
    公共静态无效主要(字符串[]参数){
        // 供应商 - 代理类
        供应商 vendor = new Vendor();
        // 创建供应商的代理类 Shop
        卖卖=新店(供应商);
        // 客户端正在使用代理类 Shop。
        sell.ad();
        出售.sell();
    }
}

在上面的代码中,客户看到的是Sell接口提供的功能,这个功能是Shop提供的。我们可以在不影响代理类Vendor的情况下,对hop进行修改或添加一些东西。

静态代理的缺点

静态代理实现简单,不会侵入原始代码,但是当场景复杂时,静态代理有以下缺点:

1.当需要代理多个类时,代理对象实现与目标对象一致的接口。或者,只维护一个代理类来实现多个接口,但这会导致代理类变得太大。或者,创建多个新的代理类,但这会导致代理类过多。

2.当一个接口需要增加、删除或修改方法时,目标对象和代理类都需要同时修改,不易维护。

因此,动态代理就派上用场了。

动态代理

动态代理是指在程序运行时创建代理类的方式。这种情况下,代理类不是在Java代码中定义的,而是在运行时根据Java代码中的指令动态生成的。

动态代理相对于静态代理的优势在于,很容易统一代理类的功能,而不需要修改各个代理类的功能。

基于JDK的原生动态代理的实现

动态代理的实现通常有两种方式:JDK原生动态代理和CGLIB动态代理。这里我们以JDK的原生动态代理为例。

JDK动态代理主要涉及两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。

InvocationHandler 接口定义了以下方法:

/**
 * 呼叫处理程序
 */
公共接口 InvocationHandler {
    对象调用(对象代理,方法方法,对象[] args);
}

顾名思义,实现这个接口的中介类被用作“调用处理器”。当一个代理类对象的方法被调用时,这个“调用”被转发给invoke方法。代理类对象作为代理参数传入。参数method标识调用了代理类的哪个方法,args是方法的参数。这样对代理类中所有方法的调用都变成invoke调用,可以给invoke方法(或不同的方法)添加统一的处理逻辑代理类方法可以根据方法参数进行不同的处理)。

Proxy 类用于获取与指定代理对象关联的调用处理程序。

下面以添加日志为例演示动态代理。

导入 java.lang.reflect.InvocationHandler;
导入java.lang.reflect.Method;
导入 java.util.Date;
公共类 LogHandler 实现 InvocationHandler {
    对象目标;// 代理对象,实际方法执行者
    公共日志处理程序(对象目标){
        this.target = 目标;
    }
    @覆盖
    公共对象调用(对象代理,方法方法,对象 [] args)抛出 Throwable {
        前();
        对象结果 = method.invoke(target, args); // 调用目标的methodmethod方法
        后();
        返回结果;// 返回方法的执行结果
    }
    // 在调用方法调用之前执行
    私人无效之前(){
        System.out.println(String.format("日志开始时间[%s]", new Date()));
    }
    //调用invoke方法后执行
    私人无效后(){
        System.out.println(String.format("日志结束时间[%s]", new Date()));
    }
}

客户端编写者使用动态代理代码如下:

导入 java.lang.reflect.Proxy;
/**
 * 动态代理测试
 *
 * @作者秒
 * @版本 1.0
 * @日期 2020/3/21 上午 10:40
 **/
公共类 DynamicProxyMain {
    公共静态无效主要(字符串[]参数){
        // 创建中介类的实例
        LogHandler logHandler = new LogHandler(new Vendor());
        // 设置此变量以默认名称保存动态代理类 $Proxy0.class
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 获取代理类实例 Sell
        Sell sell = (Sell) (Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, logHandler));
        // 通过代理类对象调用代理类方法实际上是去invoke方法调用
        出售.sell();
        sell.ad();
    }
}

执行后,打印日志如下:

方法sell的调用日志处理

店铺卖货

方法销售的记录

日志处理调用方法广告

店铺广告商品

调用方法广告的日志处理

经过上面的验证,我们发现我们已经成功的为我们的代理类在方法执行前后添加了日志。

为了查看上面示例中生成的动态代理类的代码,我们添加了以下属性设置(需要在生产环境中删除)。

// 设置此变量以默认名称保存动态代理类 $Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

所以,我们在执行main方法之后,也生成了一个名为$Proxy0.class的类文件。通过反编译,可以看到如下代码:

包 com.sun.proxy;
进口com.choupangxia.proxy.Sell;
导入 java.lang.reflect.InvocationHandler;
导入java.lang.reflect.Method;
导入 java.lang.reflect.Proxy;
导入 java.lang.reflect.UndeclaredThrowableException;
公共最终类 $Proxy0 扩展代理实现 Sell {
    私有静态方法 m1;
    私有静态方法 m2;
    私有静态方法 m4;
    私有静态方法 m3;
    私有静态方法 m0;
    公共 $Proxy0(InvocationHandler var1) 抛出 {
        超级(var1);
    }
    public final boolean equals(Object var1) throws {
        尝试 {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } 捕捉(运行时异常 | 错误 var3){
            抛出 var3;
        } 捕捉(Throwable var4){
            抛出新的 UndeclaredThrowableException(var4);
        }
    }
    公共最终字符串 toString() 抛出 {
        尝试 {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } 捕捉(运行时异常 | 错误 var2){
            抛出 var2;
        } 捕捉(Throwable var3){
            抛出新的 UndeclaredThrowableException(var3);
        }
    }
    公共最终无效广告()抛出{
        尝试 {
            super.h.invoke(this, m4, (Object[])null);
        } 捕捉(运行时异常 | 错误 var2){
            抛出 var2;
        } 捕捉(Throwable var3){
            抛出新的 UndeclaredThrowableException(var3);
        }
    }
    公共最终无效出售()抛出{
        尝试 {
            super.h.invoke(this, m3, (Object[])null);
        } 捕捉(运行时异常 | 错误 var2){
            抛出 var2;
        } 捕捉(Throwable var3){
            抛出新的 UndeclaredThrowableException(var3);
        }
    }
    公共最终 int hashCode() 抛出 {
        尝试 {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } 捕捉(运行时异常 | 错误 var2){
            抛出 var2;
        } 捕捉(Throwable var3){
            抛出新的 UndeclaredThrowableException(var3);
        }
    }
    静止的 {
        尝试 {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.choupangxia.proxy.Sell").getMethod("ad");
            m3 = Class.forName("com.choupangxia.proxy.Sell").getMethod("sell");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } 捕捉(NoSuchMethodException var2){
            抛出新的 NoSuchMethodError(var2.getMessage());
        } 捕捉(ClassNotFoundException var3){
            抛出新的 NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到$Proxy0(代理类)继承了Proxy类,实现了所有被代理的接口,以及equals、hashCode、toString等方法。

由于动态代理类继承了 Proxy 类,因此每个代理类都与一个 InvocationHandler 方法调用处理器相关联。

类和所有方法都用public final修饰,所以只能使用代理类,不能再继承。

每个方法都有一个 Method 对象,该对象在静态代码块中创建并以“m+number”格式命名。

通过 super.h.invoke(this,m1,(Object[])null); 调用该方法 而super.h.invoke其实是一个LogHandler对象,在创建代理时传递给Proxy.newProxyInstance。它继承 InvocationHandler 类并负责实际的调用处理逻辑。

以上就是关于“Java代理模式和动态代理详细信息”的介绍,如果大家对此比较感兴趣,想了解更多相关知识,可以关注一下动力节点的Java设计模式,里面有更丰富的知识等着大家去学习,希望对大家能够有所帮助哦。

选你想看

你适合学Java吗?4大专业测评方法

代码逻辑 吸收能力 技术学习能力 综合素质

先测评确定适合在学习

在线申请免费测试名额
价值1998元实验班免费学
姓名
手机
提交