本文介绍JDK中的动态代理,动态代理基于Java中的反射机制。通过本篇文章需要掌握什么是动态代理?知道动态代理能做什么?为后续的MyBatis、Spring框架做准备。
1. 动态代理概述
动态代理分为动态和代理两方面,首先需要明白什么是代理,然后既然动态是修饰词,那么就肯定有静态代理,需要明白动态和静态的区别。
1.1 代理概述
代理顾名思义就是代替办理事情。最直观的代理就是中介,代替你找工作,代替你找房子等等;还有翻墙代理软件,代替你访问外网,然后通过代理为你提供服务等等。
代理和自己要做的事情是一致的,事情就是目标,自己是客户。
那么为什么要找代理呢?
- 代理是专业的,比较方便。
- 客户不能或者不方便访问目标,或者目标不允许个人访问。(比如官方总厂家不接受个人买卖,只能批发,这时候用户只能通过超市来购买)
在开发中也会有这样的情况,比如A类(客户类)通过调用C类(目标类)中的方法来实现某个功能,但是C类不允许A类调用方法。此时可以创建一个代理类B类(代理类),通过B类调用C类中的方法,然后A类再访问B类中的方法即可。
通过代理模式,主要有以下两个作用:
- 功能增强:在原有必须实现的功能上,增加了额外的功能。
- 控制访问:代理类不让用户访问目标,必须通过代理类来访问目标类。
实现代理的方式主要有两种:静态代理和动态代理。
- 静态代理
- 代理类是自己手工实现的。
- 所代理的目标类是固定的。
- 动态代理
- 代理类不需要手动实现,通过反射机制来创建类对象。
- 代理的目标类可以在运行过程中动态指定,不需要代码写死。
1.2 静态代理
模拟:用户购买U盘。其中:
- 用户是客户端类。
- 商家为代理类,代理某个品牌的U盘。作用是卖U盘。
- 品牌厂家为目标类。作用是卖U盘。
实现步骤:
- 创建一个接口,定义卖U盘的方法,表示厂家和商家做的事情。
- 创建厂家类,也就是目标类,实现1步骤的接口。
- 创建商家类,也就是代理类,实现1步骤的接口。
- 创建客户端类,调用商家的方法买一个U盘。
方法接口:
1 | // 定义接口,表示厂家和商家要完成的功能 |
金士顿厂家(目标类)
1 | // 目标类,金士顿厂家,不接受用户单独购买 |
淘宝商家(代理类)
1 | // 商家类,淘宝,代理金士顿U盘的销售 |
微商商家(代理类)
1 | public class Weishang implements UsbSell { |
测试类
1 | public class clientTest01 { |
通过观察,可以看到上述静态代理的缺点:
- 代理的厂家是固定的,后续如果想要代理其他厂家的产品,需要修改代码(即一个代理类代理一个目标类)重新部署。
- 接口中的方法是固定的,如果想要增加几个方法,那么实现类都要重新实现增加的方法,也需要重新部署。
因此可以采用动态代理,即使要代理的目标类有很多,我们也可以仅仅使用较少的代理类来实现;另外即使修改接口中的方法,也不会影响代理类的代码。
1.3 动态代理
动态代理即代理的类不是固定写死的,并且在程序执行过程中,使用JDK的反射机制,创建代理类对象(不需要写代理类Java类),并动态的指定要代理的目标类。
动态代理其实有两种方式可以实现:JDK(需要有接口,例如UsbSell)和CGLIB(无需接口,了解,这种主要是框架中使用),只需要掌握JDK动态代理即可。
JDK动态代理主要用到三个类和接口:
java.lang.reflect.InvocationHandler
(调用处理器)是一个接口,该接口只有一个方法
1
2Object inovke(Object proxy, Method method, Object[] args);
// Processes a method invocation on a proxy instance and returnsthe result.该方法,表示代理类要完成的功能(目标类的方法调用+功能增强),即代理对象要执行的功能代码。感觉这个接口的实现类实际上就是代理类。
参数:
Object proxy
:JDK创建的代理对象,无需赋值,JDK自动提供。- Method method:目标类中的方法,无需赋值,JDK自动提供。
- Object[] args:目标类中的方法的参数,无需赋值,JDK自动提供。
既然方法的参数都由JDK提供,那么我们只需要实现该接口该方法(调用目标类方法+功能增强)即可。也就是说,这个实现类的作用实际上就是通过代理对象调用目标类中的方法为客户提供服务。
java.lang.reflect.Method
是一个类,用于表示目标类中的方法,和反射中的调用对象方法一样,用于执行目标类中的方法。
java.lang.reflect.Proxy
动态代理的核心类,用于创建代理对象。核心方法如下:
1
2
3public static Object newProxyINstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){}
// Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocationhandler.参数:
- ClassLoader loader:目标类的类加载器,负责向内存中加载对象。采用
目标对象.getClass().getClassLoader()
来获得目标类的类加载器。 - Class<?>[] interfaces:目标对象实现的接口。采用
目标对象.getClass().getInterfaces()
获得。 - InvocationHandler h:调用代理器对象,即代理类要完成的功能。
通过这个类的这个函数,可以获取到一个代理对象。然后将该对象(Object)强转了接口类型,调用接口中的方法(该方法会跳转到InvocationHandler中的invoke方法)为用户提供服务。
大致看了一下IDEA反编译后的该方法源码,本质上就是根据上述三个参数利用反射机制创建一个“代理”对象。因为静态代理类就是根据目标类以及扩展方法来定义的,所以当然也可以根据这几个参数利用proxy类来创建代理类对象。
源码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48private static Constructor<?> getProxyConstructor(Class<?> caller, ClassLoader loader, Class<?>... interfaces) {
if (interfaces.length == 1) {
Class<?> intf = interfaces[0];
if (caller != null) {
checkProxyAccess(caller, loader, intf);
}
return (Constructor)proxyCache.sub(intf).computeIfAbsent(loader, (ld, clv) -> {
return (new Proxy.ProxyBuilder(ld, (Class)clv.key())).build();
});
} else {
Class<?>[] intfsArray = (Class[])interfaces.clone();
if (caller != null) {
checkProxyAccess(caller, loader, intfsArray);
}
List<Class<?>> intfs = Arrays.asList(intfsArray);
return (Constructor)proxyCache.sub(intfs).computeIfAbsent(loader, (ld, clv) -> {
return (new Proxy.ProxyBuilder(ld, (List)clv.key())).build();
});
}
}
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
Objects.requireNonNull(h);
Class<?> caller = System.getSecurityManager() == null ? null : Reflection.getCallerClass();
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}
private static Object newProxyInstance(Class<?> caller, Constructor<?> cons, InvocationHandler h) {
try {
if (caller != null) {
checkNewProxyPermission(caller, cons.getDeclaringClass());
}
return cons.newInstance(h);
} catch (InstantiationException | IllegalAccessException var5) {
throw new InternalError(var5.toString(), var5);
} catch (InvocationTargetException var6) {
Throwable t = var6.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException)t;
} else {
throw new InternalError(t.toString(), t);
}
}
}- ClassLoader loader:目标类的类加载器,负责向内存中加载对象。采用
2. JDK动态代理实现
步骤如下所示:
- 创建接口,定义目标类要完成的功能。
- 创建目标类实现接口。
- 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能:
- 调用目标方法;
- 增强功能;
- 使用Proxy类的静态方法,创建代理对象,并把返回值(代理对象)转为接口类型,这个接口是步骤1中的接口。这是可以转的,因为目标对象实现了该接口,相当于向上转型。
- 调用代理对象中的方法为客户提供服务即可。
可以看到,动态代理只是对代理进行了动态处理,而代理的目标:即最基本的接口方法以及目标类这些仍然需要自己写。代理的动态体现在代理类以及代理对象的创建,也就是上述的3、4步骤。源码如下所示:
接口和目标类保持不变,调用代理器类如下所示:
1 | // 创建调用代理器,实现该接口,完成代理类的功能(调用目标类的方法+功能增强) |
Proxy类方法创建代理对象如下所示:
1 | public class clientTest01 { |
3. 动态代理的作用
一个主要的作用就是可以在不改变原来目标方法功能(相当于目标类方法)的前提下,可以在代理中编写自己的功能增强代码(相当于功能增强)。
比如在项目开发过程中,有一个功能是其他部分的人编写的,但是没有源码,只有class文件,这时候就可以采用动态代理,在invoke方法中,除了调用原有的功能,也可以自己添加功能增强代码。
总结:
动态代理的核心是
Proxy.newProxyInstance()
方法,该方法返回的类对象:
- 继承了Proxy类;
- 实现了接口(即目标类提供服务的方法,注意此时和目标类没关系)
- 实现了接口的方法,方法内部通过反射机制调用InvocationHandler的invoke()方法。
- 而
invoke
方法调用了目标类的方法,并且实现了功能增强。这样完成了调用目标类的方法以及功能增强。
这样,实现动态代理的步骤:
- 接口方法
- 目标类
- 调用处理器InvocationHandler的实现类(相当于代理类),核心是
invoke()
方法,调用目标类方法以及功能增强。Proxy.newProxyInstance()
创建代理对象(Object类型)。- 将代理对象向下强转为特定的接口类型,调用接口方法即可。
执行过程:
测试类中,程序在创建出代理对象后,调用对应接口的方法a(传参A);该方法通过反射机制调用InvocationHandler的invoke()方法,并把参数A传入,以及方法a的Method反射对象等参数;invoke方法通过反射机制调用目标类方法a,传参A。然后对结果处理,比如功能增强。
这里要注意,invoke方法中调用目标类方法的返回值类型,需要考虑接口中的所有方法。
JDK动态代理的优点:
- 代理处理器类InvocationHandler可以动态的代理不同的目标类,创建对象的时候传参即可(invoke方法对结果的处理需要重点考虑一下)。
- 因为
InvocationHandler.invoke()
方法需要的参数是反射相关参数,并且Proxy.newProxyInstance()
会自动创建对应类型的对象。那么即使接口中新增了方法,在InvocationHandler代理处理器类中也不需要修改,只需要在目标类中实现对应的方法即可。并且在测试类中调用方法即可。 - 那么这样说,接口中新增方法相当于目标类多了一些业务,在目标类做修改,这是必须的,现实生活中也是这样;另外,客户调用新增的业务方法也是需要手动调用的,这也是必须的。而JDK动态代理,代理处理器类不需要做任何改动(只需要注意invoke方法调用目标类方法时的返回值即可),在这方法比静态代理类修改代理类调用方法简单地多了。
这里引入AOP(Aspect Oriented Programming,面向切面编程)的概念,我们知道有面向过程编程、面向对象编程,分别是不同的编程思想,面向过程就是将事务过程化(流程化),将步骤一个个独立出来;面向对象则是从另一个方面考虑,将事务对象化,将步骤一个个抽象成对象,对象之间合作完成事务。
而面向切面编程相当于面向对象编程的一个补充,即这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。
可以看到上面的动态代理,就是将动态地代理目标类为客户提供服务(这里要将商家代理这个概念摒弃掉,这里的动态代理的代理对象就是相当于切面,目标类和用户之间就是切入点)。
本质上就是目标类和用户之间进行交互,为了方便提供一些服务,我们在二者之间(切入点)切入了一些功能(切面),并且这个切入过程是动态的,这就回到了上面的定义:这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
案例如下:
为了简单起见,接口中新增的方法没有返回值,这样invoke方法中的新增方法的功能增强部分就可以不同考虑返回值的问题了。
接口新增一个方法:
1 | // 定义接口,表示厂家和商家要完成的功能 |
目标类新增实现方法
1 | // 目标类,金士顿厂家,不接受用户单独购买 |
代理处理器类不做任何修改:
1 | // 创建调用代理器,实现该接口,完成代理类的功能(调用目标类的方法+功能增强) |
测试类如下所示,只需要调用方法即可:
1 | public class clientTest01 { |
4. 备注
参考B站《动力节点》。( Java JDK 动态代理(AOP)使用及实现原理分析_衣舞晨风-CSDN博客_jdk动态代理。什么是面向切面编程AOP? - 知乎 (zhihu.com)