十分钟理解 Java 中的动态代理:从微商代理到编程实践

本篇内容对“Java 中的动态代理是什么”的相关知识进行了介绍。在实际案例的操作期间,很多人都会遭遇到这样的困境。接下来,就让小编引领大家去学习一下该怎样处理这些情况吧!希望大家能够认真阅读,从而学有所得!

十分钟理解 Java 中的动态代理:从微商代理到编程实践

十分钟理解 Java 中的动态代理

一、概述

1. 什么是代理

我们都知晓微商代理。其简单来讲,就是替厂家售卖商品,厂家会“委托”代理来帮其销售商品。对于微商代理,其一,我们从他们那里购买东西时,通常不清楚背后的厂家到底是谁,也就是“委托者”对于我们而言是看不到的;其二,微商代理主要将朋友圈里的人当作目标客户,这就如同为厂家对客户群体进行了一次“过滤”。我们对微商代理和厂家进行了进一步的抽象。微商代理可被抽象为代理类,厂家可被抽象为委托类(被代理类)。使用代理通常具有两个优点,并且这两个优点能够分别与微商代理的两个特点相对应。

优点一:可以隐藏委托类的实现;

优点二:能够让客户与委托类之间实现解耦。这样在无需修改委托类代码的情况下,还能够进行一些额外的处理。

2. 静态代理

如果代理类在程序运行之前就已经存在了,那么这种代理方式就叫做静态代理。在这种情况下,代理类通常是我们在 Java 代码中定义的。一般来说,静态代理中的代理类和委托类会实现同一个接口或者派生自相同的父类。下面我们用 Vendor 类去代表生产厂家,用 BusinessAgent 类去代表微商代理,以此来介绍静态代理的简单实现。委托类和代理类都实现了 Sell 接口,Sell 接口的定义情况如下:


公共接口 Sell 包含两个方法,一个是用于销售的 sell 方法,另一个是用于做广告的 ad 方法。

Vendor类的定义如下:


公共类 Vendor 实现了 Sell 接口。它包含一个 sell 方法,该方法的作用是进行销售相关的操作。

System 输出了 "Insellmethod" 。

}publicvoidad(){

System 把 "admethod" 进行了打印。

代理类BusinessAgent的定义如下:


公共类 Vendor 实现了 Sell 接口。publicvoidsell(){

System.out.println("Insellmethod");

}publicvoidad(){

System,out.println("admethod")

从 BusinessAgent 类的定义中,我们能够得知,静态代理能够通过聚合的方式来实现。具体来说,就是让代理类持有一个委托类的引用就可以了。

下面我们考虑这个需求:给 Vendor 类增添一个过滤功能,仅把货卖给大学生。借助静态代理,我们不用去修改 Vendor 类的代码就能达成目的,只需在 BusinessAgent 类的 sell 方法里添加一个判断就行,如下所示:

这对应着我们上面提到的使用代理的第二个优点:能够实现客户与委托类之间的解耦,并且在不修改委托类代码的情况下,还可以进行一些额外的处理。静态代理存在局限,即运行前必须编写好代理类,接下来我们将重点介绍运行时生成代理类的动态代理方式。

二、动态代理

1. 什么是动态代理

程序运行时创建的代理方式被称为动态代理,这种代理方式属于代理类。也就是说,在这种情况下,代理类并非是在 Java 代码中定义的,而是在运行时依据我们在 Java 代码中的“指示”而动态生成的。与静态代理相比,动态代理的优势在于能够非常便捷地对代理类的函数进行统一处理,并且无需修改每个代理类的函数。这样能更直观地了解动态代理的这个优势是如何体现的。

现在假设要达成这样一个需求:在执行委托类里的方法之前输出“before”,在执行完毕后输出“after”。我们依旧以之前例子中的 Vendor 类当作委托类,BusinessAgent 类当作代理类来进行讲解。首先我们用静态代理来实现这一需求,相关代码如下:


公共类 BusinessAgent 实现了 Sell 接口。

privateVendormVendor;

公共业务代理(为 Vendor 供应商而设立)。

this.mVendor=vendor;

publicvoidsell(){

System.out.println("before");

mVendor.sell();

System.out.println("after");

publicvoidad(){

System.out.println("before");

mVendor.ad();

System.out.println("after");

从以上代码能了解到,利用静态代理来实现需求的话,得在每个方法里添加相应逻辑。这里仅有两个方法,所以工作量不算大。要是 Sell 接口中包含上百个方法呢?那此时使用静态代理就会写出很多冗余代码。我们可以通过使用动态代理来做出一个“统一指示”,以此对所有代理类的方法进行统一处理,而无需逐一去修改每个方法。接下来我们具体介绍一下如何利用动态代理方式来实现我们的需求。

2. 使用动态代理

(1)InvocationHandler接口

使用动态代理时,我们得定义一个处于代理类和委托类之间的中介类。这个中介类需要去实现 InvocationHandler 接口,该接口的定义是这样的:


公共接口 InvocationHandler 是这样的:它定义了一个方法,用于在代理对象调用目标方法时进行拦截和处理。这个方法接收三个参数,分别是代理对象、目标方法的方法对象以及目标方法的参数数组。通过在这个方法中编写自定义的逻辑,可以实现对代理对象方法调用的控制和扩展。

调用对象的方法。传入代理对象、要调用的方法以及方法的参数数组。即通过代理对象来调用指定的方法,并传递相应的参数。

从“InvocationHandler”这个名称,我们能够得知,实现了该接口的中介类被用作“调用处理器”。当我们去调用代理类对象的方法时,这个“调用”会被转交到“invoke”方法里,并且代理类对象会作为“proxy”参数被传入,参数“method”标识出我们具体调用的是代理类的哪个方法,“args”则是这个方法的参数。这样一来,我们对代理类中的所有方法进行调用时,都会将其变为对 invoke 的调用。这样做的好处是,我们能够在 invoke 方法中添加统一的处理逻辑,同时也可以根据 method 参数对不同的代理类方法进行不同的处理。所以我们仅需在中介类的 invoke 方法实现里输出“before”,接着调用委托类的 invoke 方法,之后再输出“after”。接下来我们逐步具体地实现它。

(2)委托类的定义

在动态代理的方式中,需要委托类去实现某个特定的接口,而我们在此处实现的是 Sell 接口。委托类 Vendor 类的定义情况如下:


publicclassVendorimplementsSell{

publicvoidsell(){

System.out.println("Insellmethod");

publicvoidad(){

System,out.println("admethod")

(3)中介类

我们之前提到过,中介类需要去实现 InvocationHandler 接口,以此来充当调用处理器,对代理类的方法调用进行“拦截”。中介类的定义情况如下:


一个名为 DynamicProxy 的类实现了 InvocationHandler 接口。

privateObjectobj;//obj为委托类对象;

创建一个 publicDynamicProxy 类,该类接收一个 Object 类型的参数 obj 。

this.obj=obj;

@Override

在给定的方法中,当调用该方法时,会传入三个参数,分别是代理对象、要调用的方法以及方法的参数数组,然后该方法会抛出一个可抛出的异常并返回一个公共对象。

System.out.println("before");

调用 method 方法,并将 obj 和 args 作为参数传递给它,然后将返回的结果赋值给 Objectresult 。

System.out.println("after");

returnresult;

从以上代码中我们能看到,中介类拥有一个委托类对象的引用。在 invoke 方法里,调用了委托类对象的相应方法(位于第 11 行)。看到此处,是否觉得似曾相识呢?通过聚合这种方式持有委托类对象的引用,能将外部对 invoke 的调用最终转化为对委托类对象的调用。这是我们上面介绍的静态代理的一种实现方式吧?实际上,中介类和委托类形成了静态代理关系,在此关系中,中介类为代理类,委托类便是委托类;代理类与中介类也构成一个静态代理关系,在这个关系里,中介类是委托类,代理类是代理类。也就是说,动态代理关系由两组静态代理关系所构成,这就是动态代理的原理。下面我们来介绍一下如何”指示“以动态生成代理类。

(4)动态生成代理类

动态生成代理类的相关代码如下:


publicclassMain{

公共静态 void 主方法(字符串数组参数){

//创建中介类实例

DynamicProxy 被创建出来,并且这个新创建的 DynamicProxy 实例被赋值给了变量 inter,同时这个 DynamicProxy 的创建是基于一个新的 Vendor 实例。

会产生一个$Proxy0.class文件,此文件是动态生成的代理类文件。

System 的 getProperties 方法让 "sun.misc.ProxyGenerator.saveGeneratedFiles" 这个属性的值为 "true" 。

//获取代理类实例sell

Sellsell 等于 Sell 以及通过 Proxy.newProxyInstance 方法创建的代理实例,该方法的参数包括 Sell 类的类加载器、一个包含 Sell 类的数组以及一个 inter 对象。

通过代理类对象来调用代理类的方法,在实际操作中会转而去调用 invoke 方法。

sell.sell();

sell.ad();

在上述代码里,我们运用 Proxy 类的 newProxyInstance 方法去获取一个代理类的实例。此代理类把我们所指定的接口给实现了,同时会把方法调用分发到指定的调用处理器那里。该方法的声明是这样的:


通过类加载器 `loader`、接口数组 `interfaces` 和调用处理器 `h` 来创建新的代理实例,此操作可能会抛出 `IllegalArgumentException` 异常。

方法的三个参数含义分别如下:

loader:定义了代理类的ClassLoder;

interfaces:代理类实现的接口列表

调用处理器,这个处理器是我们上面所定义的,并且实现了 InvocationHandler 接口的类实例。

我们运行一下,来查看我们的动态代理能否正常工作。我在此处运行后得到的输出是:

说明我们的动态代理确实奏效了。

我们之前简单提及过动态代理的原理,现在再次简单总结如下:首先借助 newProxyInstance 方法来获取代理类实例。接着,我们能够利用这个代理类实例去调用代理类的方法。对代理类方法的调用实际上会调用中介类(调用处理器)的 invoke 方法。在 invoke 方法里,我们会调用委托类的相应方法,同时还可以添加自身的处理逻辑。

先看一下代理模式,这个应该是设计模式中最简单的一个了,类图

代理模式的最大特点在于,代理类与实际业务类会实现同一个接口(或者继承同一父类)。代理对象会持有一个实际对象的引用。在外部进行调用时,操作的是代理对象。而在代理对象的内部实现过程中,又会去调用实际对象的操作。

Java 动态代理在内部是借助 Java 反射机制来达成的。也就是说有一个已知的对象,接着在运行的过程中能够动态地调用这个对象的方法。通过这样的方式,就可以在调用该对象的方法前后进行一些相应的处理。这样的描述比较宽泛,下面举一个简单的例子。

我们在应用中存在这样一种需求,即在调用某个类的某一个方法之前要进行一次日志操作,并且在调用该方法之后也要进行一次日志操作。

一个普通的接口

[java]view plaincopy

publicinterfaceAppService{

创建应用程序需要一个名称,该方法用于创建名为指定名称的应用程序,并且返回一个布尔值来表示创建操作是否成功。如果创建成功,返回 true;如果创建失败,返回 false。此方法名为 createApp,它接受一个字符串类型的参数 name,表示要创建的应用程序的名称。

该接口的默认实现类

[java]view plaincopy

公共类 AppServiceImpl 实现了 AppService 接口。

判断是否创建了名为 name 的应用程序。如果创建成功则返回 true,否则返回 false。即检查是否成功创建了指定名称的应用程序,并根据结果返回相应的布尔值。需要确定创建应用程序的相关操作和条件,以准确判断是否创建成功。

System.out.println("应用["+name+"]已经被创建。");

returntrue;

日志处理器

[java]view plaincopy

公共类 LoggerInterceptor 实现了 InvocationHandler 接口。需注意要实现这个 Handler 接口。

目标对象的引用为 privateObject target ,将其设计为 Object 类型是为了更具通用性。

公共日志拦截器针对目标对象进行操作。它会对目标对象的相关行为进行拦截和记录日志。通过这种方式,可以获取到目标对象在特定过程中的详细信息,以便进行后续的分析和处理。

this.target=target;

在给定的方法中,会传入三个参数,分别是代理对象 proxy、方法 method 以及参数数组 arg,然后会调用该方法并返回一个对象。具体来说,就是在特定的上下文中,通过代理对象来执行指定的方法,并将传入的参数传递给该方法,最后返回执行结果。

throwsThrowable{

System.out.println("进入了" + target.getClass().getName() + " - " + method.getName() + ",参数为{" + arg[0] + "}");

调用目标对象的方法,其结果为 Objectresult,通过 method.invoke(target, arg) 实现

System.out.println("在返回之前:" + result);

returnresult;

外部调用

[java]view plaincopy

publicclassMain{

publicstaticvoidmain(String[]args){

生成目标对象,即 AppService target = new AppServiceImpl();

//接下来创建代理对象

AppServiceproxy 等于 (AppService)通过 Proxy 的 newProxyInstance 方法创建的实例。

获取目标对象的类所对应的类加载器。 类加载器用于加载类的二进制数据。 它在 Java 运行时环境中起着重要的作用。 不同的类加载器可以加载不同来源的类。 例如,系统类加载器加载 Java 平台的核心类。 自定义类加载器可以加载特定的类或从特定的位置加载类。 类加载器的层次结构决定了类的加载顺序。 父类加载器先尝试加载类,子类加载器再尝试加载。 这样可以避免类的重复加载和冲突。 每个类都有一个对应的类加载器与之关联。 通过类加载器可以获取类的定义和资源。 类加载器是 Java 动态性的重要组成部分。 它使得 Java 程序可以在运行时加载和使用新的类。 这为 Java 开发带来了很大的灵活性。

获取 target 的类所实现的接口,并使用 newLoggerInterceptor(target) 进行相关操作。

proxy.createApp("KevinTest");

Java 中的动态代理相关内容已介绍完毕。感谢大家的阅读。若想了解更多行业知识,可关注亿速云网站,小编会为大家输出更多高质量实用文章。

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

27阅读

java中冒号,java双冒号

2026年09月06日

26阅读

java中的->,java中的是什么意思

2026年09月06日

24阅读

java中的super用法,Java中super的说明

2026年09月06日

28阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

26阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

26阅读