当前位置:首页 > CN2资讯 > 正文内容

Java代理模式及动态代理详解java 动态代理

1天前CN2资讯

Java的动态代理在实践中有着广泛的使用场景,比如最场景的Spring AOP、Java注解的获取、日志、用户鉴权等。本篇文章带大家了解一下代理模式、静态代理以及基于JDK原生动态代理。

代理模式

无论学习静态代理或动态代理,我们都要先了解一下代理模式。

先看百度百科的定义:

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

直接看定义可能有些难以理解,我们就以生活中具体的实例来说明一下。

我们都去过超市购买过物品,超市从厂商那里购买货物之后出售给我们,我们通常并不知道货物从哪里经过多少流程才到超市。

在这个过程中,等于是厂商“委托”超市出售货物,对我们来说是厂商(真实对象)是不可见的。而超市(代理对象)呢,作为厂商的“代理者”来与我们进行交互。

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

通过代理模式,我们可以做到两点:

1、隐藏委托类的具体实现。

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

代理模式角色定义

在上述的过程中在编程的过程中我们可以定义为三类对象:

  • Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法。比如:广告、出售等。

  • RealSubject(真实主题角色):真正实现业务逻辑的类。比如实现了广告、出售等方法的厂家(Vendor)。

  • Proxy(代理主题角色):用来代理和封装真实主题。比如,同样实现了广告、出售等方法的超时(Shop)。

以上三个角色对应的类图如下:

静态代理实例

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

下面我们就以具体的实例来演示一下静态代理。

首先定义一组接口Sell,用来提供广告和销售等功能。然后提供Vendor类(厂商,被代理对象)和Shop(超市,代理类),它们分别实现了Sell接口。

Sell接口定义如下:

* 委托类和代理类都实现了Sell接口 * @author sec * @version 1.0 * @date 2020/3/21 9:30 AM **/public interface Sell {
/** * 出售 */ void sell();
/** * 广告 */ void ad();}

Vendor类定义如下

  •  
  •  
  •  
  •  
/** * 超市,代理类 * @author sec * @version 1.0 * @date 2020/3/21 9:30 AM **/public class Shop implements Sell{
private Sell sell;
public Shop(Sell sell){ this.sell = sell; }
@Override public void sell() { System.out.println("代理类Shop,处理sell"); sell.sell(); }
@Override public void ad() { System.out.println("代理类Shop,处理ad"); (); }}

其中代理类Shop通过聚合的方式持有了被代理类Vendor的引用,并在对应的方法中调用Vendor对应的方法。在Shop类中我们可以新增一些额外的处理,比如筛选购买用户、记录日志等操作。

下面看看在客户端中如何使用代理类。

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
/** * 静态代理类测试方法 * @author sec * @version 1.0 * @date 2020/3/21 9:33 AM **/public class StaticProxy {
public static void main(String[] args) {
// 供应商---被代理类 Vendor vendor = new Vendor();
// 创建供应商的代理类Shop Sell sell = new Shop(vendor);
// 客户端使用时面向的是代理类Shop。 (); sell.sell(); }}

在上述代码中,针对客户看到的是Sell接口提供了功能,而功能又是由Shop提供的。我们可以在Shop中修改或新增一些内容,而不影响被代理类Vendor。

静态代理的缺点

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

1、当需要代理多个类时,代理对象要实现与目标对象一致的接口。要么,只维护一个代理类来实现多个接口,但这样会导致代理类过于庞大。要么,新建多个代理类,但这样会产生过多的代理类。

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

于是,动态代理便派上用场了。

动态代理

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

相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。

基于JDK原生动态代理实现

实现动态代理通常有两种方式:JDK原生动态代理和CGLIB动态代理。这里,我们以JDK原生动态代理为例来进行讲解。

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

InvocationHandler接口定义了如下方法:

  •  
  •  
  •  
  •  
  •  
  •  
/** * 调用处理程序 */public interface InvocationHandler { Object invoke(Object proxy, Method method, Object[] args); }

顾名思义,实现了该接口的中介类用做“调用处理器”。当调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了具体调用的是代理类的哪个方法,args为该方法的参数。这样对代理类中的所有方法的调用都会变为对invoke的调用,可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。

Proxy类用于获取指定代理对象所关联的调用处理器。

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

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.util.Date;
public class LogHandler implements InvocationHandler { Object target; // 被代理的对象,实际的方法执行者
public LogHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(target, args); // 调用 target 的 method 方法 after(); return result; // 返回方法的执行结果 } // 调用invoke方法之前执行 private void before() { System.out.println(String.format("log start time [%s] ", new Date())); } // 调用invoke方法之后执行 private void after() { System.out.println(String.format("log end time [%s] ", new Date())); }}

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

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
import java.lang.reflect.Proxy;
/** * 动态代理测试 * * @author sec * @version 1.0 * @date 2020/3/21 10:40 AM **/public class DynamicProxyMain {
public static void main(String[] args) { // 创建中介类实例 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(); (); }}

执行之后,打印日志如下:

调用方法sell之【前】的日志处理
Shop sell goods
调用方法sell之【后】的日志处理
调用方法ad之【前】的日志处理
Shop advert goods
调用方法ad之【后】的日志处理

经过上述验证,我们发现已经成功为我们的被代理类统一添加了执行方法之前和执行方法之后的日志。

在上述实例中为了看一下生成的动态代理类的代码,我们添加了下面的属性设置(在生产环境中需要去掉该属性)。

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

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

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
package com.sun.proxy;
import com.choupangxia.proxy.Sell;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Sell { private static Method m1; private static Method m2; private static Method m4; private static Method m3; private static Method m0;
public $Proxy0(InvocationHandler var1) throws { super(var1); }
public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } }
public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
public final void ad() throws { try { super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
public final void sell() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
static { try { 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"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } }}

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

由于动态代理类继承了Proxy类,所以每个代理类都会关联一个InvocationHandler方法调用处理器。

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

每个方法都有一个Method对象来描述,Method对象在static静态代码块中创建,以“m+数字”的格式命名。

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

小结

关于代理和动态代理相关的内容,我们就讲这么多。了解了代理模式可以让我们的系统设计的更加具有可扩展性。而动态代理的应用就更广了,各类框架及业务场景都在使用。有了两个基础,就能够更好的学习其他框架。

    你可能想看:

    扫描二维码推送至手机访问。

    版权声明:本文由皇冠云发布,如需转载请注明出处。

    本文链接:https://www.idchg.com/info/24096.html

    分享给朋友:

    “Java代理模式及动态代理详解java 动态代理” 的相关文章

    中国电信cn2线路连接方法视频教程下载:轻松掌握网络优化技巧

    在当今数字化时代,网络已经成为我们生活中不可或缺的一部分。无论是工作还是娱乐,一个稳定、快速的网络连接都是至关重要的。而中国电信的cn2线路以其高带宽、低延迟的特点,成为许多用户的首选。对于首次使用中国电信cn2线路的用户来说,连接过程可能会显得有些复杂。如何快速掌握中国电信cn2线路的连接方法呢?...

    全球主机论坛:交流与学习的技术社区

    在现代社会,全球主机论坛的出现为我们提供了一个交流和学习的平台。这个论坛主要聚焦于主机领域,用户可以自由讨论主机的各种话题,分享个人经验,并获取最新的行业信息。对我而言,这样的论坛不仅是一个获取知识的地方,更是一个与全球主机用户互动的社区。 全球主机论坛的重要性毋庸置疑。它为主机使用者提供了一个集中...

    CN2 GIA VPS推荐:最佳虚拟专用服务器选择

    CN2 GIA VPS概述 在当今数字时代,寻找一个稳定且高效的虚拟专用服务器(VPS)成为很多企业和个人用户的需求。而CN2 GIA VPS凭借其卓越的性能和稳定的连接,受到了越来越多的关注。简单来说,CN2 GIA是一种中国电信提供的高质量网络传输线路,可以确保数据的快速和安全传输。 我曾尝试过...

    BuyVM色情网站托管服务解析 - 提升成人内容运营安全与效率

    什么是BuyVM色情服务 当我提到BuyVM的时候,可能很多人对这个名字还不太熟悉。简单来说,BuyVM是一家提供虚拟专用服务器(VPS)和网站托管服务的公司,专注于各种类型的内容,包括成人色情内容。它的历史可以追溯到多个年份前,BuyVM在业内逐渐赢得了声誉,成为不少成人网站的首选托管平台。以其可...

    IPv6 测速方法与工具:提升网络体验的技巧

    IPv6 测速的基本概念 IPv6,作为互联网协议的最新版本,让我们在网络世界中畅游无阻。它的推出旨在解决IPv4所面临的地址枯竭问题,推进更加广泛的设备连接。简单来说,IPv6提供了更大的地址空间,能够支持越来越多的设备上线。因为万物互联的时代已经来临,我们的手机、电脑、家居设备甚至汽车都需要连接...

    反向代理的工作原理与应用场景详解

    反向代理概述 反向代理,听起来似乎有点复杂。实际上,这是一个位于客户端和服务器之间的“中间人”,它对客户端是透明的。换句话说,客户端并不知道有这个反向代理的存在。它的主要任务是接收客户端的请求,然后把这些请求转发给后端的服务器,再把服务器的响应返回给客户端。想象一下,反向代理就像一个邮递员,负责将信...