上篇文章的结尾我们介绍了普通的jdk实现动态代理的主要不足在于:它只能代理实现了接口的类,如果一个类没有继承于任何的接口,那么就不能代理该类,原因是我们动态生成的所有代理类都必须继承Proxy这个类,正是因为Java的单继承,所以注定会抛弃原类型的父类。而我们的cglib通过扫描该类以及其父类中所有的public非final修饰的方法,通过asm定义该类的子类字节码,其中该子类重写了父类所有的方法,然后返回该子类的实例作为代理类。也就是说我们的cglib是用该类的子类作为代理类来实现代理操作的。当然cglib的缺点也是呼之欲出,对于被代理类中的非public或者final修饰的方法,不能实现代理。

     在详细介绍cglib之前,我们先简单介绍下ASM框架,这是一个小而快的字节码处理框架,它负责生成从被代理类中扫描出的方法的字节码并将这些方法生成字节码暂存在内存中。然后我们通过方法将这些字节码转换成class类型,最后利用反射创建代理类的实例返回。下面看一个完整的实例,稍后从源代码的角度分析这个实例:

//定义一个接口
public interface MyInterface {
public void sayHello();
}
//定义一个ClassB类
public class ClassB {
public void welcome(){
System.out.println("welcom walker");
}
} //模拟被代理的类,继承了ClassB和接口MyInterface
public class ClassA extends ClassB implements MyInterface {
public void sayHello(){
System.out.println("hello walker");
}
}
//定义一个回调实例,稍后解释
public class MyMethod implements MethodInterceptor { public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable{
proxy.invokeSuper(obj, args);
return null;
}
}
public class Test {
public static void main(String[] args) throws Exception { ClassA ca = new ClassA();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ClassA.class);
enhancer.setCallback(new MyMethod());
ClassA my = (ClassA)enhancer.create();
my.welcome();
}
}
输出结果:welcom walker

我们看到,此处我们获取了ClassA的代理对象,然后调用了ClassA父类中的welcome方法。这也间接证明了我们通过cglib代理了ClassA的父类中的方法,这一点使用jdk实现的动态处理是做不到的。下面我们解释原理。

在这之前,由于cglib是第三方库,所以我们需要下载相应的jar文件,主要包含两个文件,一个是cglib的jar,还有一个是cglib依赖的ASM框架的jar文件,注意这两个jar的版本不能冲突。

我们从main方法的主体代码中可以看出,Enhancer 类是创建代理实例的核心类。没错,该类负责整个代理对象的生命周期,它就像是一个工具一样,提供了很多方法帮助我们创建代理实例。首先我们调用了setSuperclass方法设置父类型,其实也就是将被代理对象传入,因为我们之前说过cglib创建的代理类是原对象的子类型,所以这里称原类型实例为父类也是合理的。

跟进去,我们看到:

public void setSuperclass(Class superclass)
{
if ((superclass != null) && (superclass.isInterface())) {
setInterfaces(new Class[] { superclass });
} else if ((superclass != null) && (superclass.equals(Object.class))) {
this.superclass = null;
} else {
this.superclass = superclass;
}
}

这段代码的主要意思是:如果传入的类型是接口的话,保存在专门用于保存接口类型的变量中。

private Class[] interfaces;

如果传入的类型是Object类型的话,将用于保存普通类类型的变量赋值为null,否则保存该传入的参数的值在该变量中。这些操作过程中保存的一些数值是为了在最后create的时候提供帮助。

接下来是setCallback方法,该方法设置了回调。也就是将来对我们代理中方法的访问会转发到该回调中,所有自定义的回调类必须继承MethodInterceptor接口并实现其intercept方法,这一点和jdk的InvocationHandler类似。这里的intercept有几个参数:

  • Object obj:被代理的原对象
  • Method method:被调用的当前方法
  • Object[] args:该方法的参数集合
  • MethodProxy proxy:被调用方法的代理,它可以和method完成同样的事情,但是它使用FastClass机制非反射执行方法,效率高

我们对于所有调用代理方法的请求,转发到invokeSuper方法中,该方法源码如下:

//fastclassinfo类
private static class FastClassInfo
{
FastClass f1;
FastClass f2;
int i1;
int i2; private FastClassInfo() {} FastClassInfo(MethodProxy.1 x0)
{
this();
}
} public Object invokeSuper(Object obj, Object[] args)
throws Throwable
{
try
{
init();
FastClassInfo fci = this.fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
}
catch (InvocationTargetException e)
{
throw e.getTargetException();
}
}

其中fastclassinfo类中,几个参数的意思解释下,f1指向被代理对象,f2指向代理类对象,i1和i2分别是代理类中的该方法的两个索引。也就是这种fastclass机制并不是通过反射找到指定的方法的,而是在创建代理类的时候为其中的方法建立hash索引,这样调用的时候通过索引调用提高了效率。最后调用了代理类的方法,也就是重写了父类的方法。

最后也是最核心的一步是create方法的调用,这个方法才是实际创建代理实例的方法,我们看源码:

  public Object create()
{
this.classOnly = false;
this.argumentTypes = null;
return createHelper();
}

该方法主要设置了两个参数配置,指定将要创建的对象不仅仅是一个类,指定参数为空。至于这两个参数有何作用,还需要往下追,我们看createHelper类:

  private Object createHelper()
{
validate();
if (this.superclass != null) {
setNamePrefix(this.superclass.getName());
} else if (this.interfaces != null) {
setNamePrefix(this.interfaces[ReflectUtils.findPackageProtected(this.interfaces)].getName());
}
return super.create(KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter, this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID));
}

validate()方法主要对于一些参数进行校验,如果不符合创建实例的标准将抛出异常,我们可以简单的看一眼:

private void validate()
{
if ((this.classOnly ^ this.callbacks == null))
{
if (this.classOnly) {
throw new IllegalStateException("createClass does not accept callbacks");
}
throw new IllegalStateException("Callbacks are required");
}
if ((this.classOnly) && (this.callbackTypes == null)) {
throw new IllegalStateException("Callback types are required");
}
if ((this.callbacks != null) && (this.callbackTypes != null))
{
if (this.callbacks.length != this.callbackTypes.length) {
throw new IllegalStateException("Lengths of callback and callback types array must be the same");
..........
..........
.........
}

主要还是判断回调是否指定,类型是否正确等,如果不符合创建条件就抛出异常。我们回去,接着就做了两个判断,用于指定被创建的代理类的名称,我们暂时不管他。又到了一个核心的方法,该方法将创建代理类并返回该类实例。首先我们看参数都是是什么意思,就一个参数,该参数是由KEY_FACTORY.newInstance方法返回的一个Object类型,我们看到在该方法的传入参数中,包括了父类类名或者接口名,回调类型,版本号等。该方法实际上返回了一个该代理类的一个唯一标识,这还不是关键,最关键的方法是这个create方法:

  protected Object create(Object key)
{
try
{
Class gen = null;
synchronized (this.source)
{
ClassLoader loader = getClassLoader();
Map cache2 = null;
cache2 = (Map)this.source.cache.get(loader);
if (cache2 == null)
{
cache2 = new HashMap();
cache2.put(NAME_KEY, new HashSet());
this.source.cache.put(loader, cache2);
}
else if (this.useCache)
{
Reference ref = (Reference)cache2.get(key);
gen = (Class)(ref == null ? null : ref.get());
}
if (gen == null)
{
Object save = CURRENT.get();
CURRENT.set(this);
try
{
this.key = key;
if (this.attemptLoad) {
try
{
gen = loader.loadClass(getClassName());
}
catch (ClassNotFoundException e) {}
}
if (gen == null)
{
b = this.strategy.generate(this);
String className = ClassNameReader.getClassName(new ClassReader(b));
getClassNameCache(loader).add(className);
gen = ReflectUtils.defineClass(className, b, loader);
}
if (this.useCache) {
cache2.put(key, new WeakReference(gen));
}
byte[] b = firstInstance(gen); CURRENT.set(save);return b;
}
finally
{
CURRENT.set(save);
}
}
}
return firstInstance(gen);
//省去了异常捕获的代码块

如果usecache为为true表明该代理类已经在cache中了,直接返回引用即可。否则通过 this.strategy.generate(this);方法生成该代理类的字节码,然后通过通过类加载器加载该字节码生成class类型,最后通过firstInstance方法生成代理类的实例返回。

最后我们看一眼刚才生成的代理的源码:

//代码很多,此处贴出部分
public class ClassA$$EnhancerByCGLIB$$64984e8e extends ClassA
implements Factory
{
final void CGLIB$sayHello$0()
{
super.sayHello();
}
public final void sayHello()
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
JVM INSTR pop ;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVM INSTR dup ;
JVM INSTR ifnull 37;
goto _L3 _L4
_L3:
break MISSING_BLOCK_LABEL_21;
_L4:
break MISSING_BLOCK_LABEL_37;
this;
CGLIB$sayHello$0$Method;
CGLIB$emptyArgs;
CGLIB$sayHello$0$Proxy;
intercept();
return;
super.sayHello();
return;
} final void CGLIB$welcome$1()
{
super.welcome();
} public final void welcome()
{
CGLIB$CALLBACK_0;
if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
JVM INSTR pop ;
CGLIB$BIND_CALLBACKS(this);
CGLIB$CALLBACK_0;
_L2:
JVM INSTR dup ;
JVM INSTR ifnull 37;
goto _L3 _L4
_L3:
break MISSING_BLOCK_LABEL_21;
_L4:
break MISSING_BLOCK_LABEL_37;
this;
CGLIB$welcome$1$Method;
CGLIB$emptyArgs;
CGLIB$welcome$1$Proxy;
intercept();
return;
super.welcome();
return;
}
....
....
}

从中我们看到,该类ClassA$$EnhancerByCGLIB$$64984e8e继承自ClassA,实现了接口factory。并且在其中我们看到不仅是父类ClassA中的方法sayHello在其中被重写了之外,ClassA的父类ClassB中的welcome方法也被重写了。足以见得,cglib利用继承的方式动态创建了被代理类的子类,通过ASM生成父类中所有public非final修饰的方法,实现了代理。

最后稍微小结下,cglib的实现代理的逻辑。首先我们通过Enhancer实例设置被代理类,然后设置该代理类的回调,也就是在访问代理类方法的时候会首先转向该回调,在回调中我们调用invokeSuper方法以fastclass这种非反射机制快速的调用到代理类中的方法,其中代理类中方法又调用原类型的对应方法。

由于cglib已经停止维护好多年,导致参考文档很少,学习难度很大,此篇文章也是作者研读jdk和网上优秀博文总结,不当之处,望大家指出,学习 !学习!

详解Java动态代理机制(二)----cglib实现动态代理的更多相关文章

  1. 异常处理器详解 Java多线程异常处理机制 多线程中篇(四)

    在Thread中有异常处理器相关的方法 在ThreadGroup中也有相关的异常处理方法 示例 未检查异常 对于未检查异常,将会直接宕掉,主线程则继续运行,程序会继续运行 在主线程中能不能捕获呢? 我 ...

  2. Java Garbage Collection基础详解------Java 垃圾回收机制技术详解

    最近还是在找工作,在面试某移动互联网公司之前认为自己对Java的GC机制已经相当了解,其他面试官问的时候也不存在问题,直到那天该公司一个做搜索的面试官问了我GC的问题,具体就是:老年代使用的是哪中垃圾 ...

  3. 详解java中CAS机制所导致的问题以及解决——内存顺序冲突

    [CAS机制] 指的是CompareAndSwap或CompareAndSet,是一个原子操作,实现此机制的原子类记录着当前值的在内存中存储的偏移地址,将内存中的真实值V与旧的预期值A做比较,如果不一 ...

  4. 详解java动态代理机制以及使用场景

    详解java动态代理机制以及使用场景 https://blog.csdn.net/u011784767/article/details/78281384 深入理解java动态代理的实现机制 https ...

  5. 详解Java动态代理机制

    之前介绍的反射和注解都是Java中的动态特性,还有即将介绍的动态代理也是Java中的一个动态特性.这些动态特性使得我们的程序很灵活.动态代理是面向AOP编程的基础.通过动态代理,我们可以在运行时动态创 ...

  6. 【转帖】windows命令行中java和javac、javap使用详解(java编译命令)

    windows命令行中java和javac.javap使用详解(java编译命令) 更新时间:2014年03月23日 11:53:15   作者:    我要评论 http://www.jb51.ne ...

  7. 详解Java GC的工作原理+Minor GC、FullGC

    详解Java GC的工作原理+Minor GC.FullGC 引用地址:http://www.blogjava.net/ldwblog/archive/2013/07/24/401919.html J ...

  8. Protocol Buffer技术详解(Java实例)

    Protocol Buffer技术详解(Java实例) 该篇Blog和上一篇(C++实例)基本相同,只是面向于我们团队中的Java工程师,毕竟我们项目的前端部分是基于Android开发的,而且我们研发 ...

  9. java基础(十五)----- Java 最全异常详解 ——Java高级开发必须懂的

    本文将详解java中的异常和异常处理机制 异常简介 什么是异常? 程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常. Java异常的分类和类结构图 1.Java中的所 ...

  10. 第三节:带你详解Java的操作符,控制流程以及数组

    前言 大家好,给大家带来带你详解Java的操作符,控制流程以及数组的概述,希望你们喜欢 操作符 算数操作符 一般的 +,-,*,/,还有两个自增 自减 ,以及一个取模 % 操作符. 这里的操作算法,一 ...

随机推荐

  1. SpringMVC搭建+实例

    想做一点自己喜欢的东西,研究了一下springMVC,所以就自己搭建一个小demo,可供大家吐槽. 先建一个WEB工程,这个相信大家都会,这里不在多说. 去网上下载spring jar包,然后在WEB ...

  2. 进入效果 neon

    @-webkit-keyframes neon { 0% { opacity: .3; -webkit-transform: scale(2); transform: scale(2); } 100% ...

  3. 小程序新能力-个人开发者尝鲜微信小程序

    个人开发者的福利 微信小程序,刚听到这个新名词的时候,我就兴冲冲的去找入口,看看自己能不能搞个微信小程序的HelloWorld,毕竟能在微信上把自己写的一些小工具跑起来还是满炫酷的. 没想,网上一查, ...

  4. [.NET] 一步步打造一个简单的 MVC 电商网站 - BooksStore(二)

    一步步打造一个简单的 MVC 电商网站 - BooksStore(二) 本系列的 GitHub地址:https://github.com/liqingwen2015/Wen.BooksStore 前: ...

  5. 2017-3-28 javaScript DOM 操作

    一.DOM的基本概念:DOM是文档对象模型,这种模型为树模型:文档是指标签文档:对象是指文档中每个元素:模型是指抽象化得东西. 二.Windows  对象操作:1.属性和方法:属性(值或者子对象):o ...

  6. String 类的实现(2)深度拷贝详解

    我们已经知道了浅拷贝存在的问题,即多次析构同一空间.这个问题是类的成员函数引起的,就是前面浅拷贝里相当于编译器自动合成的函数,确切的说,浅拷贝里的问题是由隐士拷贝构造函数和隐士赋值运算符引起的. 拷贝 ...

  7. XJOI1571爱心蜗牛【树形动规】

    爱心蜗牛 猫猫把嘴伸进池子里,正准备"吸"鱼吃,却听到门铃响了.猫猫擦了擦脸上的水,打开门一看,那人正是她的好朋友--川川.川川手里拿着一辆玩具汽车,对猫猫说:"这是我的 ...

  8. JavaWeb之HTTP协议

    一.概念 协议是指计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则,超文本传输协议(HTTP)是一种通信协议,它允许将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器. ...

  9. POPTEST老李谈JVM、JRE、JDK、java ee sdk with jdk区别

    POPTEST老李谈JVM.JRE.JDK.java ee sdk with jdk区别   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等 ...

  10. python selenium2示例 - 生成 HTMLTestRunner 测试报告

    前言 在python selenium2自动化测试过程中,一个合适的报告是必须的,而HTMLTestRunner模块为我们提供了一个很好的报告生成功能. 什么是HTMLTestRunner HTMLT ...