本帖最后由 大蓝鲸Java 于 2017-12-28 22:38 编辑
各位程序猿小伙伴们在开发过程中应该都做过如下类似的需求,如给我们开发完成后的项目添加事务控制,添加性能监控等等。如果小伙伴们遇到这样需求,二话不说撸起袖子,在你已经写好的代码里面添加事务控制和性能监控相关的代码,那就太low啦。这个时候小伙伴们应该想到对于这样的需求,我们应该使用spring提供的AOP实现来完成我们的上述需求。但是我们今天要说的不是AOP,而是我们AOP底层的实现原理:动态代理。 代理模式:
方法调用中不直接调用目标对象的方法,而是调用我们的代理对象的方法。代理对象持有了目标对象,在代理对象的方法可直接调用目标对象的方法,在调用目标对象方法的前后,可以对我们的目标对象进行增强。 静态代理(装饰着模式)
如上图: 代理类ServiceProxy和目标类ServiceImpl实现了相同的接口IService,代理类中有一个私有属性private IService service指向目标类的一个实例对象。当我们调用代理类ServiceProxy实例对象的sayHello方法,该方法内部会帮助我们调用目标类ServiceImpl实例对象的sayHello。这样我们就实现了通过调用代理对象方法来调用我们目标对象方法,而且在代理对象调用目标对象前后可以进行代码的增强。 但是静态代理存在如下两方面的缺点: 1、每一个目标类我们都要为其创建一个Proxy代理类,如果你的项目中需要使用代理的目标类非常多的话,就会导致你的Proxy代理类也非常多,这样造成你的代码非常臃肿,难以维护。 2、代理类重写目标类的所有方法,并且需要在重写的方法中原封不动的调用我们的目标对象对应的方法。这样会导致大量重复代码的出现。
Java提供了动态代理技术,它可以帮助我们在程序运行的时候动态的创建我们的代理类,这样就避免了在你的代码中去编写大量的Proxy代理类了。同样的,我们的动态代理类也会实现或重写我们目标对象类中的所有方法,这些方法会统一的调用一个指定类的指定方法。我们要做的就在在这个指定类的指定方法中来编写我们的代码逻辑即可。
动态代理
JDK动态代理
如上图,我们的JDK动态代理是通过java.lang.reflect.Proxy类提供的静态方法newProxyInstance()创建的。该方法需要三个参数: 1、类加载器 可以通过Class字节码对象的getClassLoader()方法获取 作用:生成我们的动态代理类 2、Class对象数组 通过目标对象的Class字节码对象获取target.getClass().getInterfaces(); 作用:生成的动态代理类需要实现的所有接口 3、InvocationHandler接口的实现类的实例对象 需要自己编写一个类实现InvocationHandler接口,并重写里面的invoke方法 作用:代理类的所有方法实现中都会调用该实现类的invoke方法。也就是说代理类调用目标对象的方法和增强代码都是在invoke方法中实现的。 那么JDK生成的动态代理类代码到底是怎么写的呢?下面我们不妨一起来看看我们JDK为我们生成的动态代理类的源码吧!! 我们可以通过如下代码来生成我们JDK动态代理类.class字节码文件
[Java] 纯文本查看 复制代码 /**
* 生成动态代理类的字节码.class文件的工具类
* @param className 生成的.class文件的名称
* @param filePath 生成的.class文件存放的路径
* @param cls 生成的动态代理类需要实现的接口
*/
@SuppressWarnings("rawtypes")
public static void writeObjectToFile(String className,String filePath, Class[] cls) {
byte[] classFile = ProxyGenerator.generateProxyClass(className, cls);
FileOutputStream out = null;
try {
filePath = filePath + "/"+className + ".class";
out = new FileOutputStream(filePath);
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null)
out.close();
} catch (IOException e) {
}
}
}
通过上面的代码我们可以获取到JDK生成的动态代理类的class字节码文件,然后通过jd-gui.exe反编译工具来查看我们生成的字节码文件。
[Java] 纯文本查看 复制代码 import com.itcast.effctive.IService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
//$Proxy JDK生成的动态代理类,它继承了Proxy并实现了被代理对象实现的接口
public final class $Proxy extends Proxy implements IService{
// 代理类中需要实现的所有方法的Method实例对象
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
// 利用static静态代码块在类加载时就初始化这些Method实例对象
static
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("com.itcast.effctive.IService").getMethod("sayHello", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
}
// 该代理对象提供一个有参构造,参数是我们的InvocationHandler接口的实现类的实例对象
public $Proxy(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
/*
* 一下为动态代理类实现的所有方法,我们可以看到,在所有的方法内部都是调用的InvocationHandler接口实现类的实例对象的invoke方法。
*/
public final boolean equals(Object paramObject) {
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
public final void sayHello() {
this.h.invoke(this, m3, null);
return;
}
public final String toString() {
return (String)this.h.invoke(this, m2, null);
}
public final int hashCode() {
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
}
Cglib动态代理
如上图,我们的Cglib动态代理类对象是通过一个Enhancer类对象的create()方法创建的。 在调用create方法前,需要调用Enhancer类对象提供的set方法,为改对象的两个属性赋值: 1、enhancer.setSuperclass(target.getClass()); 设置动态代理对象需要继承的父类,也就是我们的别代理类。 2、enhancer.setCallback(MethodInterceptor接口的实现类); MethodInterceptor接口的实现类,提供了一个intercept方法,正如JDK动态代理中的InvocationHandler接口的invoke方法一样,我们的Cglib动态代理类会重写父类(被代理类)中的方法,在重写的方法中会统一调用MethodInterceptor接口的实现类的intercept方法,在intercept方法中调用被代理对象方法,并实现增强逻辑。
JDK动态代理 VS Cglib动态代理 首先JDK动态代理是基于接口实现生成的代理对象,而Cglib动态代理是基于继承生成的代理对象。二者实现原理的不同也就导致了它们的应用范围的不同。JDK动态代理类只能对实现了特定接口的类进行代理,而Cglib则没有这项要求。也就是说Cglib动态代理的应用范围比JDK动态代理要广。 另外,我们在对这两种动态代理的性能做一下比较。相信大家一定听说过这样的一句话:JDK动态代理创建代理对象快,但是执行方法慢;而我们的Cglib动态代理恰好相反,它是创建代理对象慢,但是执行方法比JDK动态代理快。
但是事实却是如此吗? 笔者特意对此写了测试代码来验证,通过测试方法我们的JDK动态代理对象的创建的确如上面那句话说的那样比Cglib要快很多。但是,JDK动态代理对象方法的执行效率并不比Cglib慢,反而比Cglib快那么一点点。那么是不是说我们上面的那句话是不对的呢。其实是这样的,笔者的测试环境是JDK1.8。对于在我们JDK1.6及以前的版本来说,我们的JDK动态代理对象方法的执行效率的确比Cglib慢,但是自JDK1.7及之后的版本,人家Oracle公司对JDK动态代理做了大量的优化,所以现在JDK动态代理的方法执行效率并不比Cglib慢,反而是比它快。也就是说无论从对象创建还是方法执行上来说,我们的JDK动态代理都是比Cglib动态代理要快。(但是也只是目前,也许哪天人家对Cglib也做优化呢!!)但是JDK动态代理相比较Cglib动态代理而言,有一个致命的缺陷,那就是它只能对实现了特定接口的类做代理。 最后做个总结吧。小伙伴们以后在开发中如果需要用到动态代理,具体是使用JDK还是Cglib动态代理,是要看情况的。如果是基于接口的当然推荐JDK,当时如果被代理的类没有实现任何接口,那就只能使用Cglib动态代理咯。但是,小伙伴们在开发过程中切忌,不要为了使用动态代理而使用动态代理(切莫装X),因为无论如何,动态代理对象的创建和方法执行效率都是无法和直接new对象以及直接调用对象方法比较的。直接new对象和直接调用对象方法效率一定是最快的。
|