设计模式–代理模式
代理模式是常用的java设计模式,它允许你通过创建一个代理对象来控制对另一个对象(即目标对象)的访问,代理类与委托类有同样的接口。
代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
代理对象充当了目标对象的接口,客户端通过代理对象与目标对象进行交互,而不是直接访问目标对象。
代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。代理模式可以在不改变目标对象代码的前提下,对目标对象的功能进行增强或扩展。


可以发现,其中有
代理对象
被代理的行为
被代理的对象
行为的完全控制
静态代理
静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
简单实现
首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。
1 | /** |
Student类实现Person接口。Student可以具体实施上交班费的动作。
1 | public class Student implements Person { |
StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为
1 | // 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为 |
测试主类 1
2
3
4
5
6
7
8
9
10
11
12public class StaticProxyTest {
public static void main(String[] args) {
//被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
Person zhangsan = new Student("张三");
//生成代理对象,并将张三传给代理对象
Person monitor = new StudentsProxy(zhangsan);
//班长代理上交班费
monitor.giveMoney();
}
}
这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。
代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。
代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。
这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以很方便的加上一些其他用途
1 | public class StudentsProxy implements Person{ |
可以看到,只需要在代理类中帮张三上交班费之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。
动态代理
介绍
代理类在程序运行时创建的代理方式被成为动态代理。
我们上面静态代理的例子中,代理类(studentProxy)
是自己定义好的,在程序运行之前就已经编译完成。
然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。
相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 比如说,想要在每个代理的方法前都加上一个处理方法:
1 | public void giveMoney() { |
这里只有一个giveMoney方法,就写一次beforeMethod方法,但是如果出了giveMonney还有很多其他的方法,那就需要写很多次beforeMethod方法,麻烦。那看看下面动态代理如何实现。
简单实现
在java的java.lang.reflect
包下提供了一个Proxy
类和一个InvocationHandler
接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
相关方法:
创建一个InvocationHandler
对象
1 | //创建一个与代理对象相关联的InvocationHandler |
InvocationHandler
接口定义了一个invoke方法,当通过代理对象调用目标方法时,invoke
方法会被调用。invoke
方法接收三个参数:代理对象、被调用的方法对象以及方法的参数数组。在
invoke
方法中,我们可以编写额外的逻辑,然后调用目标方法并返回结果。
例如
1 | import java.lang.reflect.InvocationHandler; |
使用Proxy类
的getProxyClass
静态方法生成一个动态代理类stuProxyClass
1 | Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class}); |
该类提供了静态方法用于创建动态代理类和代理对象。
其中,newProxyInstance
方法是创建代理对象的关键方法,它接收三个参数:类加载器、目标对象实现的接口数组以及
InvocationHandler
实例。
例如
1 | import java.lang.reflect.Proxy; |
获得stuProxyClass
中一个带InvocationHandler
参数的构造器constructor
1 | Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class); |
在 Java 反射机制中,Class
类提供了一系列方法来获取类的构造器信息。这里通过
PersonProxy.getConstructor(InvocationHandler.class)
来获取
PersonProxy
类中,参数为
InvocationHandler.class
的构造器。getConstructor
方法会返回一个
Constructor
对象,它代表了对应的构造器。
通过构造器constructor
来创建一个动态实例stuProxy
1 | Person stuProxy = (Person) cons.newInstance(stuHandler); |
Constructor
类的 newInstance
方法用于根据构造器来创建类的实例。这里传入了 stuHandler
作为参数,stuHandler
应该是实现了
InvocationHandler
接口的对象实例。它会调用之前获取到的构造器(要求参数为
InvocationHandler
类型 )来初始化一个
PersonProxy
实例,并将其强制转换为 Person
类型(前提是 PersonProxy
类实现了 Person
接口
)
一个动态代理对象就创建完毕,当然,上面四个步骤可以通过Proxy
类的newProxyInstances
方法来简化
1 | //创建一个与代理对象相关联的InvocationHandler |
完整的动态代理例子
定义一个Person接口,里面定义一个抽象方法giveMoney();
1 | public interface Person { |
创建需要被代理的实际类 Student类,实现了Person接口,是被代理的目标对象:
1 | public class Student implements Person { |
再定义一个检测方法执行时间的工具类,在任何方法执行前先调用start
方法,执行后调用finsh
方法,就可以计算出该方法的运行时间,这也是一个最简单的方法执行时间检测工具。
1 | public class MonitorUtil { |
创建StuInvocationHandler
类,实现InvocationHandler
接口,这个类中持有一个被代理对象的实例target
。InvocationHandler
中有一个invoke
方法,所有执行代理对象的方法都会被替换成执行invoke
方法。再在invoke方法中执行被代理对象target的相应方法。
所以说InvocationHandler
负责处理代理对象方法的调用,当调用代理对象的方法时,实际上会调用
InvocationHandler
的 invoke
方法。
当然,在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。这也是Spring中的AOP实现的主要原理,这里还涉及到一个很重要的关于java反射方面的基础知识。
1 | public class StuInvocationHandler<T> implements InvocationHandler { |
我们使用简化的方式创建动态代理对象:
1 | public class ProxyTest { |
过程讲解:
stuHandler
为代理对象相关联的InvocationHandler
,将目标对象
张三 传递给 StuInvocationHandler
的构造函数。InvocationHandler
的作用是处理代理对象方法的调用,当调用代理对象的方法时,会自动调用
InvocationHandler
的 invoke
方法
使用 Proxy.newProxyInstance
方法创建一个代理对象
stuProxy
,需要传入类加载器,实现的接口,指定处理代理对象方法调用的InvocationHandle
对象,代理对象的每个执行方法都会替换执行Invocation
中的invoke
方法
之后调用代理对象的 giveMoney
方法,实际上会调用
StuInvocationHandler
的 invoke
方法,在
invoke
方法中会先调用 MonitorUtil.start()
记录开始时间,然后调用目标对象的 giveMoney
方法,最后调用
MonitorUtil.finish()
计算并打印方法执行的耗时。


动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
是因为所有被代理执行的方法,都是通过在InvocationHandler
中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。
例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而只做了很少的代码量。
动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。
因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过InvocationHandler
来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过InvocationHandler
中的invoke方法来执行,这就涉及到java动态代理的原理了
动态代理原理分析
Java动态代理创建出来的动态代理类
上面我们利用Proxy
类的newProxyInstance
方法创建了一个动态代理对象,查看该方法的源码,发现它只是封装了创建动态代理类的步骤(红色标准部分):
1 | /** |
1 | Class<?> cl = getProxyClass0(loader, intfs); |
loader
:这是一个ClassLoader
对象,它指定了代理类应该使用哪个类加载器来加载。类加载器负责将类的字节码文件加载到 JVM 中,不同的类加载器可能会从不同的位置(如文件系统、网络等)加载类。通常,我们会使用被代理接口的类加载器,这样可以确保代理类和被代理接口在同一个类加载器的命名空间中,从而避免类加载的冲突。intfs
:这是一个Class[]
数组,包含了代理类需要实现的接口列表。代理类会实现这些接口中定义的所有方法,当调用代理对象的这些方法时,实际上会将调用转发到InvocationHandler
的invoke
方法中进行处理。
其实,我们最应该关注的是 Class<?> cl = getProxyClass0(loader, intfs);这句,这里产生了代理类,后面代码中的构造器也是通过这里产生的类来获得,可以看出,这个类的产生就是整个动态代理的关键,由于是动态生成的类文件,这个类文件时缓存在java虚拟机中的
- 在 Java 动态代理中,
Proxy.newProxyInstance
方法的核心目标是创建一个实现了指定接口的代理对象。而getProxyClass0
方法就是用来生成这个代理对象所对应的代理类的。这个代理类是在运行时动态生成的,并且会被加载到 Java 虚拟机(JVM)中,后续的代理对象实例就是基于这个动态生成的类来创建的。
那么是如何动态生成代理类的:
getProxyClass0
方法会根据传入的类加载器和接口数组,在运行时动态生成一个代理类的字节码。这个代理类会实现intfs
数组中指定的所有接口,并且会重写这些接口中的方法。在重写的方法中,会调用InvocationHandler
的invoke
方法,从而实现对目标方法的增强。- 这其中有一个缓存机制,为了提高性能,Java
虚拟机对动态生成的代理类采用了缓存机制。也就是说,如果已经为相同的类加载器和接口列表生成过代理类,那么
getProxyClass0
方法会直接从缓存中获取这个代理类,而不是再次生成。这样可以避免重复生成相同的代理类,减少开销。
之后对这个class文件反编译
1 | import java.lang.reflect.InvocationHandler; |
jdk为我们的生成了一个叫$Proxy0
(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。
我们可以对InvocationHandler
看做一个中介类,中介类持有一个被代理对象,在invoke
方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。
引用