前提思想
研究AspectJ的实现,首先必须要明白一个大前提,那就是是其实对于jvm虚拟机来说,它只认符合class文件结构的文件,无论这个文件是从java语言还是AspectJ语言、或者其它什么语言编译的,最终,只要生成的文件符合class文件结构,就能被jvm所识别并加载。
同时,我们还要了解一个前提,那就是Aspectj织入功能,其实可以分别在四个阶段进行织入:
第一个阶段:编译前。所谓的编译前,就是我们可以使用ajc指令直接对.java文件进行织入,这一个阶段会生成新的class文件结构,并且会回写到磁盘上,替换原class文件。
第二个阶段:编译后。所谓的编译后,就是指的,我们可以使用ajc指令,直接对.class文件进行织入。这一个阶段会生成新的class文件结构,并且会回写到磁盘上,替换原class文件。
第三个阶段:加载时。所谓的加载时,就是在class文件被classloader加载时,我们可以通过特殊的技术手段,比如自己实现classloader或者使用Java SE 6 新特性新特性提供的Instrumentation 新功能来实现,这一个阶段会生成新的class文件结构,但是并不需要将新生成的文件写回到磁盘上,主要此时新生成的class文件结构也完全不同于上面的两种,内容太深,如果想细究,可在后面将二进制流打印出来,并使用javap反编译看一下(只有用javaP反编译才能看出来)。
第四个阶段:加载后。所谓的加载后,就是指class文件已经被jvm加载,我们通过jdk6新特性提供的Instrumentation 新功能来实现对内存中的class文件强制替换的功能来实现,其实AspectJ官方并没有介绍这个阶段,因为这个阶段,aspectj主要提供的功能,还是像第三阶段一样,重构原class文件结构,生成新的class文件结构二进制流,而替换的工作主要是由Java SE 6 新特性新特性提供的Instrumentation 新功能来实现。
编译前、编译后的代码实现
该功能实现可以使用自定义的classloader来实现,也可以使用Java SE 6 新特性新特性提供的Instrumentation 新功能来实现,至于新特性实现我就不讲了,推荐看这篇文章(https://www.ibm.com/developerworks/cn/java/j-lo-jse61/index.html),我只讲如何将 切面织入编译前和编译后的代码中。
两种方式:
第一种,使用ajc指令,ajc指令其实就相当于java指令,我们可以使用
Runtime.getRuntime().exec(ajc指令)的方式来实现。
这里主要注意两点:
第一:使用Runtime.getRuntime().exec执行ajc指令,需要在ajc指令前加上cmd /c;
第二点:ajc指令的核心几个要素,
-classpath:要在路径下指定aspectjrt的jar包
-aspectpath:在该要素下指定切面所在路径。
-inpath:在该要素下,指定想要被切面修饰的类。
第二种,调用ajc的代码方式实现:
Main main = new Main(); MessageHandler messageHandler=new MessageHandler(); main.run(args,messageHandler);
这里的args是一个数组,其实就是将前面所讲的ajc指令的核心要素,一个个分成数组的元素,另外该方法执行需要指定一个指令要素
-cp:该要素指定java运行环境所需的jar包路径。
加载时和加载后的代码实现
同样推荐先看推荐看这篇文章(https://www.ibm.com/developerworks/cn/java/j-lo-jse61/index.html)这篇文章,了解如何在加载前和加载后实现java的class对象拦截。
而加载时和加载后的代码后的代码织入则要求有如下格式:
第一个,在Meta-INF里建一个Aop的xml文件(文件怎么建‘以及文件作用请参考https://www.eclipse.org/aspectj/doc/released/devguide/ltw-configuration.html)
第二步,使用Aj对象,对代码进行重新织入,核心代码如下三行:
Aj aj=new Aj(); aj.initialize();
byte[] bytes1=aj.preProcess(className,bytes,this.getClass().getClassLoader(),null);
Aj对象是AspectJ提供的,preProcess的几个参数分别如下:
className:被修饰对象的类名,该类名必须是译\或/来修饰的,比如com/alibaba/test/Test这样的
bytes:被修饰对象的字节流。
this.getClass().getClassLoader():很容易看明白。
bytes1:就是使用Aspectj织入代码后的二进制流了。
整个工作的核心代码,至此讲完。