前言
什么是代理?火车站的黄牛、明星的经纪人、链家卖房子的小哥。这些代理都有一个共同特点,那就是能够做一些被他们代理的人才能做的事,比如替客户买火车票、帮明星签电影、替房主卖房子。代理模式的好处在于代理们在行使雇主们权利的前后能够提供一些雇主们不具备的专业知识。代理模式是面向切面编程的基础,Java中有两种代理-静态代理和动态代理、动态代理又分为JDK原生代理和CGLIB代理,接下来我们就来说说这几种代理的原理。
一、静态代理
所谓静态代理就是指在代码运行之前就已经把代理写好了,想要给几个类做代理就得写几个代理类。
1、接口静态代理
这里以鹿晗和经纪人为例,鹿晗和经纪人同属SM公司,SM公司有一个签合同的方法,他们两个都需要实现
//签合同接口
public interface SMCompany {
public void signContract();
}
//鹿晗实现了签合同接口
public class Luhan implements SMCompany {
@Override
public void signContract() {
System.out.println("我是鹿晗,我与X公司签订了合同!");
}
}
//超级明星代理类,同样实现了签合同接口
public class SuperStarProxy implements SMCompany {
private SMCompany star;
public SuperStarProxy(SMCompany star) {
super();
this.star = star;
}
public SMCompany getStar() {
return star;
}
public void setStar(SMCompany star) {
this.star = star;
}
@Override
public void signContract() {
this.beforeSignContract();
star.signContract();
this.afterSignContract();
}
private void beforeSignContract() {
System.out.println("作为代理,签合同之前先审查下对方的资质。。。");
}
private void afterSignContract() {
System.out.println("签完合同,与对方探讨具体事宜。。。");
}
}
上面的实现有两个重点
- 首先、被代理人鹿晗必须实现一个签合同接口,这样他才能有代理类
- 然后、巨星代理类实现同一个接口,但内部调用的是被代理人鹿晗的方法,在方法前后执行了其他操作
现在Vivo公司想找鹿晗做代言,于是去和SM公司签约,SM公司派了一个鹿晗的代理去做这件事。看下具体调用:
public class VivoCompany {
public static void main(String[] args) {
SMCompany luhan = new SuperStarProxy(new Luhan());
luhan.signContract();
}
}
执行结果:
作为代理,签合同之前先审查下对方的资质。。。
我是鹿晗,我与X公司签订了合同!
签完合同,与对方探讨具体事宜。。。
上述代码用一句话概括就是代理类通过实现被代理类相同的接口内部组合被代理类,在执行被代理类方法前后添加其他操作。
二、JDK动态代理
动态代理在静态代理的基础上提供了在Java运行的时候生成代理类的功能,但仍然需要实现接口。
1、JDK动态代理实现
这里我们仍然以鹿晗和经纪人为例子,鹿晗的类不需要改变,但必须得实现SMCompany的接口。我们创建一个新的代理DynamicSuperStarProxy。
public class DynamicSuperStarProxy implements InvocationHandler {
private Object target;
public SuperStarProxy(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("签约之前审核一下对方资质。。。。");
method.invoke(target, args);
System.out.println("签约之后商讨具体事宜。。。。");
return target;
}
public static void main(String[] args) {
SuperStarProxy ssp = new SuperStarProxy(new Luhan());
SMCompany luhan = (SMCompany )Proxy.newProxyInstance(Luhan.class.getClassLoader(),
Luhan.class.getInterfaces(), ssp);
luhan.signContract();
}
}
上述代码中有三处重要信息:
- 首先、实现JDK动态代理接口InvocationHandler
- 然后、内部组合一个target作为被代理类的占位符
- 然后、实现InvocationHandler的方法invoke,该方法中proxy表示代理类,Method表示方法,args表示方法参数,值得注意的是程序员不用显示调用invoke方法,只需要在invoke方法前后做文章即可。
- 最后、调用的时候使用Proxy类生成一个代理类,调用这个代理类的方法即可
结果就不写了,与上面静态代理方法一致。
2、JDK动态代理源码解析
看完上面的实现是不是有种云里雾里的感觉?在解析源码之前先抛出两个问题
问题一:Proxy.newProxyInstance创建的代理类到底是个什么玩意?(可以先根据动态代理猜测一下)
问题二:我们定义的动态代理类的invoke方法到底在哪执行的?
首先、进入newProxyInstance源码,这个方法的参数分别是被代理类的加载器、被代理类实现的接口、代理类。这个方法首先对被代理类的所有接口进行了克隆,中间进行了检查操作,然后拿着被代理类的类加载器和接口拷贝调用getProxyClass0方法生成一个动态类、最后反射拿到这个动态代理类的构造器生成这个动态代理类。这里的关键就在于这个getProxyClass0方法。
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> 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() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
}
----异常处理
}
然后、点击进入getProxyClass0方法,内部用了一个proxyClassCache的缓存,这个缓存的key实际上是被代理类加载器和被代理类的所有接口,缓存内部使用ConcurrentHashMap嵌套形式存储了一个动态代理的生成类。
private static Class> getProxyClass0(ClassLoader loader,
Class>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
然后、越过缓存直接查看到方法的最底层,这里实际上调用了ProxyGenerator.generateProxyClass方法生成了一个字节码文件,然后通过defineClass0这个native方法把字节码加载到内存中形成一个类的。具体的生成方法都在generateProxyClass这个方法里面,这个方法做的就是按照JVM规范和类加载器要求拼接出一个类加载器能够加载的动态代理类。
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
}
再次、这里提供一个工具,用来查看上面generateProxyClass生成的字节码
public static void main(String[] args) {
SuperStarProxy ssp = new SuperStarProxy(new Luhan());
SMCompany luhan = (SMCompany ) Proxy.newProxyInstance(Luhan.class.getClassLoader(),
Luhan.class.getInterfaces(), ssp);
ProxyGeneratorUtils.writeProxyClassToHardDisk("E:\\$SuperStarProxy.class",luhan.getClass().getInterfaces());
luhan.signContract();
}
public class ProxyGeneratorUtils {
public static void writeProxyClassToHardDisk(String path, Class>[] intfs) {
byte[] classFile = sun.misc.ProxyGenerator.generateProxyClass("$SuperStarProxy",intfs);
FileOutputStream out = null;
try {
out = new FileOutputStream(path);
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
最后、执行了main方法后我们去硬盘里反编译这个生成的代理类。分析下这个代理类,首先m0-m4 4个静态变量在类加载的时候就被赋值,分别是equals方法、toString方法、hashCode方法和signContract方法。这其中的signContract方法就是我们最终调用的方法,可以看到其内部调用的就是SuperStarProxy的invoke方法。
public final class $SuperStarProxy extends Proxy implements SMCompany {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $SuperStarProxy(final InvocationHandler invocationHandler) {
super(invocationHandler);
}
public final boolean equals(final Object o) {
try {
return (boolean)super.h.invoke(this, $SuperStarProxy.m1, new Object[] { o });
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
public final void signContract() {
try {
super.h.invoke(this, $SuperStarProxy.m3, null);
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
public final String toString() {
try {
return (String)super.h.invoke(this, $SuperStarProxy.m2, null);
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
public final int hashCode() {
try {
return (int)super.h.invoke(this, $SuperStarProxy.m0, null);
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
static {
try {
$SuperStarProxy.m1 = Class.forName("java.lang.Object").getMethod("equals",
Class.forName("java.lang.Object"));
$SuperStarProxy.m3 = Class.forName("com.huo.demos.proxy.inter.SMCompany").getMethod("signContract",
(Class>[]) new Class[0]);
$SuperStarProxy.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class>[]) new Class[0]);
$SuperStarProxy.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class>[]) new Class[0]);
} catch (NoSuchMethodException ex) {
throw new NoSuchMethodError(ex.getMessage());
} catch (ClassNotFoundException ex2) {
throw new NoClassDefFoundError(ex2.getMessage());
}
}
}
三、CGLIB动态代理
有没有发现JDK代理每次都要求实现一个接口比较麻烦呢?在Spring中实际上还有一种叫做CGLIB的代理与JDK代理做切换的,即你实现了一个接口就用JDK,否则用CGLIB,下面我们来看下CGLIB动态代理是怎么使用的。
1、CGLIB动态代理使用
这里还是以明星为例,这次拿李易峰做例子,我们实现一个李易峰类,李易峰不用实现任何接口。
public class Liyifeng {
public void signContract() {
System.out.println("我是李易峰,我没有母公司,我与Vivo公司签合同!");
}
}
首先、实现一个李易峰的代理类,这个代理类里首先实现了一个方法拦截器类MethodInterceptor,然后实现了其内部的方法intercept,方法参数分别为被代理类对象,被代理类方法,被代理类方法参数,代理类。
public class StarProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("签约之前审核对方资质。。。。");
proxy.invokeSuper(obj, args);
System.out.println("签约之后商讨具体事宜。。。。");
return obj;
}
}
然后、看下调用代码,调用主要用到了Enhancer类,该类用于生成代理类,setSuperClass实际上是把李易峰类作为父类,认李易峰做爸爸,然后方法回调类设置成了代理类。这里同样提供一个工具类用于查看内存中生成的字节码System.setProperty。
public class Test {
public static void main(String[] args) {
StarProxy starProxy = new StarProxy();
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\\\class");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Liyifeng.class);
enhancer.setCallback(starProxy);
Liyifeng liyifeng = (Liyifeng) enhancer.create();
liyifeng.signContract();
}
}
最后、执行结果
签约之前审核对方资质。。。。
我是李易峰,我没有母公司,我与Vivo公司签合同!
签约之后商讨具体事宜。。。。
2、CGLIB原理分析
这里我们以Enhancer.create为切入点,这个与Proxy.getProxyInstance有异曲同工之妙。
首先、调用链是这样的Enhancer.create--》Enhancer.createHelper--》AbstractClassGenerate.create-->Enhancer.nextInstance-->Enhancer.EnhancerFactory.newInstance-->ReflectionUtils.newInstance--》Constructor.newInstance。调用链比较长,在调用AbstractClassGenerate.create方法的时候在底层使用ASM机制生成了字节码,并通过我们熟悉的defineClass加载到内存中成为类,这里就不详细解析这个字节码生成过程了,直接看生成的字节码是什么。这个字节码反编译之后简直太长了,我给其他的省略了,只留一个代理类调用的方法。我们看这个方法,如果cglib$CALLBACK_0不为空(一般都不为空)就调用代理类里面的intercept方法,当执行到invokeSuper时,invokeSuper内部调用的实际上是代理类的CGLIB$signContract$0方法,该方法实际上调用的就是其父类也就是Liyifeng类的方法。为什么称为Fast呢?首先就是根据方法签名通过hash快速找到该方法,然后调用的时候又是直接调用的父类方法而不是反射,这两点保证了Fast。
public class Liyifeng$$EnhancerByCGLIB$$13ef049e extends Liyifeng implements Factory {
final void CGLIB$signContract$0() {
super.signContract();
}
public final void signContract() {
MethodInterceptor cglib$CALLBACK_2;
MethodInterceptor cglib$CALLBACK_0;
if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
CGLIB$BIND_CALLBACKS(this);
cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
}
if (cglib$CALLBACK_0 != null) {
cglib$CALLBACK_2.intercept((Object) this, Liyifeng$$EnhancerByCGLIB$$13ef049e.CGLIB$signContract$0$Method,
Liyifeng$$EnhancerByCGLIB$$13ef049e.CGLIB$emptyArgs,
Liyifeng$$EnhancerByCGLIB$$13ef049e.CGLIB$signContract$0$Proxy);
return;
}
super.signContract();
}
}
总结:
1、静态代理和动态代理都需要被代理类实现一个接口。
2、JDK动态代理实际上就是把静态代理里面代理类由编码写死变成虚拟机运行时生成了,CGLIB动态代理实际上相当于继承了被代理类,然后super调用了代理类的方法。
3、JDK动态代理底层是通过反射调用的被代理类方法,CGLIB则直接调用被代理类方法而不使用反射。
4、CGLIB通过给被代理类的方法签名设置索引从而让JVM快速的查到被调用 的方法,外加不使用反射,效率要比JDK动态代理高很多。