前言
Mybatis
的源码实现中,使用到了动态代理的设计思想,为了搞明白Mybatis
中的动态代理,本篇文章会结合实例和源码对JDK
动态代理进行学习,并会在最后总结JDK
动态代理与CGLIB
动态代理的区别,以帮助更好的理解动态代理,为Mybatis
源码的学习打下基础。
正文
一. 代理模式
在学习动态代理之前,先回顾一下设计模式中的代理模式。代理模式定义为,给被代理对象提供一个代理对象以控制对被代理对象的访问,即访问对象不适合或者不能直接引用被代理对象时,代理对象作为访问对象和被代理对象之间的中介。代理模式中,有三种角色,分别为抽象主题(AbstractSubject
),真实主题(RealSubject
)和代理(Proxy
),三种角色含义如下表所示。
角色 | 含义 |
---|---|
抽象主题(AbstractSubject ) | 通过接口或者抽象类声明真实主题和代理需要实现的业务方法。 |
真实主题(RealSubject ) | 实现了抽象主题中的业务方法,是代理所代表的真实对象,是最终要引用的对象。 |
代理(Proxy ) | 实现了抽象主题,提供了与真实主题相同的方法,其内部含有对真实主题的引用,可以访问,控制或扩展真实主题的功能。 |
代理模式的三种角色的关系用类图表示如下。
二. 静态代理
根据代理模式中的代理类的字节码文件的创建时机,可以将代理分为静态代理和动态代理:静态代理在程序运行前,代理类的字节码文件就已经存在,而动态代理则是在程序运行期间JVM
通过反射机制为代理类生成字节码文件。本小节以一个例子对静态代理进行学习。
定义抽象主题,如下所示。
public interface TestServiceA {
void executeTestA();
void submitTestA();
}
public interface TestServiceB {
void executeTestB();
void submitTestB();
}
上述定义了两个接口作为抽象主题,下面定义一个真实主题来实现抽象主题,如下所示。
public class RealObject implements TestServiceA, TestServiceB {
@Override
public void executeTestA() {
System.out.println("Test A execute.");
}
@Override
public void submitTestA() {
System.out.println("Test A submit.");
}
@Override
public void executeTestB() {
System.out.println("Test B execute.");
}
@Override
public void submitTestB() {
System.out.println("Test B submit.");
}
}
再定义一个代理类,如下所示。
public class ProxyObject implements TestServiceA, TestServiceB {
private RealObject realObject;
public ProxyObject(RealObject realObject) {
this.realObject = realObject;
}
@Override
public void executeTestA() {
before();
realObject.executeTestA();
after();
}
@Override
public void submitTestA() {
before();
realObject.submitTestA();
after();
}
@Override
public void executeTestB() {
before();
realObject.executeTestB();
after();
}
@Override
public void submitTestB() {
before();
realObject.submitTestB();
after();
}
private void before() {
System.out.println("Begin to do.");
}
private void after() {
System.out.println("Finish to do.");
}
}
可以看到,真实主题RealObject
和代理ProxyObject
均实现了抽象主题,同时代理ProxyObject
还持有真实主题RealObject
的引用,因此需要通过ProxyObject
才能访问到RealObject
,同时ProxyObject
在执行RealObject
的方法时,还可以执行一些额外的逻辑来扩展RealObject
的功能。编写一个客户端程序,如下所示。
public class ClientOne {
public static void main(String[] args) {
RealObject realObject = new RealObject();
ProxyObject proxyObject = new ProxyObject(realObject);
proxyObject.executeTestA();
proxyObject.submitTestA();
proxyObject.executeTestB();
proxyObject.submitTestB();
}
}
运行结果如下所示。
三. JDK动态代理
思考一下,在第二小节中的静态代理,在实际使用中,存在什么不足?这里归纳如下。
- 如果让一个代理类代理多个被代理类,那么会导致代理类变得过大;
- 如果每个被代理类都对应一个代理类,那么会导致代理类变得过多;
- 由于被代理类和代理类都需要实现相同的接口,当接口定义的方法增加或减少时,被代理类和代理类需要一起修改,不易于代码维护。
上述静态代理存在的问题,可以由动态代理来解决,即在程序运行期间,才决定代理类的生成。下面先根据一个基于JDK
动态代理的例子来说明动态代理的使用方法,然后再基于源码分析JDK
动态代理的实现机制以及为什么可以动态的生成代理类。
JDK
动态代理主要是基于两个类:java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
,所有基于JDK
动态代理生成的代理类均会继承于Proxy
,同时代理类会持有InvocationHandler
的引用,而InvocationHandler
中会持有被代理类的引用,因此可以将InvocationHandler
理解为代理类与被代理类的中介。首先创建一个类实现InvocationHandler
接口,如下所示。
public class TestInvocationHandler implements InvocationHandler {
private Object realObject;
public TestInvocationHandler(Object realObject) {
this.realObject = realObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object invokeResult = method.invoke(realObject, args);
after();
return invokeResult;
}
private void before() {
System.out.println("Begin to do.");
}
private void after() {
System.out.println("Finish to do.");
}
}
如上所示,TestInvocationHandler
实现了InvocationHandler
接口,同时TestInvocationHandler
中有一个名为realObject的成员变量,该变量就是被代理类,当代理类执行代理方法时,就会通过TestInvocationHandler
来调用被代理类的方法,同时TestInvocationHandler
中还可以自己定义一些方法来实现功能扩展,在上面例子中就定义了before()
和after()
两个方法,分别用于在被代理类方法执行前和执行后做一些事情。
创建好了TestInvocationHandler
之后,就可以开始创建动态代理类了,其中被代理类还是沿用第二小节中的RealObject
,创建动态代理类的逻辑如下所示。
public class ClientTwo {
public static void main(String[] args) {
// 保存动态代理类的字节码文件
System.getProperties().setProperty(
"sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 创建被代理类
RealObject realObject = new RealObject();
// 获取被代理类的类加载器
ClassLoader classLoader = realObject.getClass()
.getClassLoader();
// 获取被代理类实现的接口的Class对象
Class<?>[] interfaces = realObject.getClass()
.getInterfaces();
// 以被代理类作为入参创建InvocationHandler
InvocationHandler invocationHandler
= new TestInvocationHandler(realObject);
// 通过调用Proxy的newProxyInstance()方法创建动态代理对象
Object proxyInstance = Proxy.newProxyInstance(
classLoader, interfaces, invocationHandler);
((TestServiceA) proxyInstance).executeTestA();
((TestServiceA) proxyInstance).submitTestA();
((TestServiceB) proxyInstance).executeTestB();
((TestServiceB) proxyInstance).submitTestB();
}
}
运行上述程序,执行结果如下所示。
在工程目录/com/sun/proxy下查看生成的代理类的字节码文件,反编译如下所示。
public final class $Proxy0 extends Proxy implements TestServiceA, TestServiceB {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m6;
private static Method m5;
private static Method m0;
private static Method m4;
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 void executeTestA() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
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 executeTestB() throws {
try {
super.h.invoke(this, m6, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void submitTestB() throws {
try {
super.h.invoke(this, m5, (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);
}
}
public final void submitTestA() throws {
try {
super.h.invoke(this, m4, (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"));
m3 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceA").getMethod("executeTestA");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m6 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceB").getMethod("executeTestB");
m5 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceB").getMethod("submitTestB");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m4 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceA").getMethod("submitTestA");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到,生成的代理类,继承于Proxy
,同时也实现了被代理类实现的接口,当代理类执行代理方法时,会通过其继承于Proxy
的InvocationHandler
来调用到被代理类的真实方法,至此,JDK
动态代理的一个例子就介绍到这里。现在看一下Proxy.newProxyInstance()
方法做了哪些事情,来搞明白为什么可以动态的生成代理类,方法源码如下所示。
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 生成代理类的Class对象
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 获取代理类的构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 生成代理对象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
getProxyClass0()
方法会生成代理类的Class
对象,生成过的代理类的Class
对象会缓存在Proxy
的类变量proxyClassCache中,所以getProxyClass0()
方法会先在proxyClassCache中获取代理类Class
对象,如果获取不到,则会通过ProxyClassFactory
来生成代理类Class
对象。
ProxyClassFactory
是Proxy
的静态内部类,其主要完成两件事情。
- 生成代理类的字节码文件;
- 调用native方法
defineClass0()
来解析代理类的字节码文件并生成代理类的Class
对象。
ProxyClassFactory
中生成代理类的字节码文件时,是调用的ProxyGenerator
的generateProxyClass()
方法,并且在生成字节码文件前,会将Object
的hashCode()
,equals()
和toString()
方法以及被代理类实现的接口所定义的方法添加到代理类的方法中。
至此,可以对JDK
动态代理如何动态生成代理类进行如下的图示归纳。
四. CGLIB动态代理
在JDK
动态代理中,要求被代理类需要实现接口,这一点限制了JDK
动态代理的使用,当被代理类未实现接口时,想要动态生成代理类,可以使用CGLIB
动态代理,使用CGLIB
生成的代理类是被代理类的子类,本小节将结合例子对CGLIB
的使用进行说明。
首先创建一个被代理类,如下所示。
public class RealService {
public void execute(String flag) {
System.out.println("Test " + flag + " execute.");
}
public void submit(String flag) {
System.out.println("Test " + flag + " submit.");
}
}
然后创建一个方法拦截器,方法拦截器需要继承于MethodInterceptor
,用于在代理对象执行方法时进行拦截,如下所示。
public class TestInterceptor implements MethodInterceptor {
/**
* @param o 代理对象
* @param method 被代理对象的方法
* @param objects 被代理对象的方法参数类型
* @param methodProxy 被代理对象的方法的代理
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o, objects);
after();
return result;
}
private void before() {
System.out.println("Begin to do.");
}
private void after() {
System.out.println("Finish to do.");
}
}
上述方法拦截器会在每一个代理对象的方法执行时进行拦截,然后依次执行before()
方法,被代理对象的方法和after()
方法,以达到对被代理对象的方法的增强效果,同时intercept()
方法的第一个参数是代理对象,所以想要执行被代理对象的方法需要使用invokeSuper()
。
最后创建客户端程序来测试效果,如下所示。
public class ClientThree {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealService.class);
enhancer.setCallback(new TestInterceptor());
Object proxyObject = enhancer.create();
((RealService) proxyObject).execute("cglib");
((RealService) proxyObject).submit("cglib");
}
}
运行结果如下所示。
CGLIB
动态代理也能保存代理类的字节码文件,只需要做如下设置。
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, 保存路径);
现在通过IDEA反编译字节码文件之后,可以查看生成的代理类,下面截取一部分来说明代理方法的调用和增强,如下所示。
public class RealService$$EnhancerByCGLIB$$64276695 extends RealService implements Factory {
......
static void CGLIB$STATICHOOK1() {
......
CGLIB$execute$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "execute", "CGLIB$execute$0");
......
}
......
public final void execute(String var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$execute$0$Method, new Object[]{var1}, CGLIB$execute$0$Proxy);
} else {
super.execute(var1);
}
}
......
}
查看反编译得到的代理类可以知道,CGLIB
动态代理生成的代理类是被代理类的子类,以及当代理类调用方法时,会通过MethodInterceptor
来调用被代理类的方法和增强方法。
至此,CGLIB
动态代理的例子介绍完毕,相较于JDK
动态代理,CGLIB
动态代理是通过字节码处理框架ASM
来动态生成代理类的字节码文件并加载到JVM
中,下面是JDK
动态代理和CGLIB
动态代理的一个简单对比。
JDK
动态代理
JDK
动态代理中,代理类调用被代理类的方法依赖InvocationHandler
接口;JDK
动态代理要求被代理类需要实现一个或多个接口;JDK
动态代理是基于反射来动态生成代理类的字节码文件。
CGLIB
动态代理
CGLIB
动态代理中,代理类调用被代理类的方法依赖MethodInterceptor
接口;CGLIB
动态代理要求被代理类不能为final,但不要求被代理类需要实现接口;CGLIB
动态代理无法为被代理类中的final方法进行代理;CGLIB
动态代理是基于ASM
框架来动态生成代理类的字节码文件。
总结
本篇文章对代理设计模式,静态代理,JDK
动态代理和CGLIB
动态代理进行了讨论。静态代理实现最为简单,在程序编译完成之后,代理类的字节码文件已经生成,可以直接被JVM
加载到内存中,使用效率高,省去了动态代理中的生成字节码文件的时间,但是缺点就是静态代理中通常一个代理类只代理一个被代理类,如果被代理类过多,会导致代理类也过多。动态代理可以解决代理类过多的问题,其中JDK
动态代理可以在程序运行期间基于反射来动态生成代理类的字节码文件,但是要求被代理类实现接口,这限制了JDK
动态代理的使用场景,而CGLIB
动态代理不要求被代理类实现接口,其底层是基于ASM
框架来动态生成代理类的字节码文件,CGLIB
创建的代理类是被代理类的子类,所以CGLIB
动态代理要求被代理类不能是final的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。