有时候调试一些程序会牵涉到第三方的类库,但由于它们都是以class或者jar形式发布的,不大可能改变其行为,所以不是特别方便。这个时候有两个选择,其一就是进行二进制class文件修改(参看链接Java Binary Translation),这个对不熟悉class格式文件的人来说门槛有点高。第二种方法相对较为简单,就是利用AOP的技术,常用的实现库就是AspectJ(如果集成spring框架的AOP,参看链接Spring
AOP)
首先是几个概念:
1.aspect(层面) 2.pointcut(切入点)
3.advice(建议)4.weave(织入)5.LTW(加载期织入 load time weave)
按照aspectj的语法规则,一个aspect就是很多pointcut和advice的集合,也就是一个*.aj的文件
一个pointcut就是对target class的切入点定义,类似Java class定义中的field
一个advice就是对target class的行为改变,类似Java class中的method
weave就是aspectj runtime库把aspect织入到target class的行为。
LTW就是指运行期间动态织入aspect的行为,它是相对静态织入行为(包括对源文件、二进制文件的修改)。
一般来讲,从运行速度上来说,静态织入比动态织入要快些。因为LTW需要使用aspectj本身的classloader,
它的效率要低于jdk的classloader,因此当需要load的class非常多时,就会很慢的。
举个例子来说明aspectj的使用:
scenario: Example工程需要使用一个类Line存在于第三方库Line.jar中,但是Line本身没有实现Serializable接口,并且其toString方法输出也不完善。因此这两点都需要修改。
Line的实现:
package bean;
public class Line {
protected int x1 = 0;
protected
int x2 = 0;
public int
getX1(){
return x1;
}
public int
getX2(){
return x2;
}
public void
setLength(int newX, int newY){
setX1(newX);
setX2(newY);
}
public void
setX1(int newX) {
x1 = newX;
}
public void
setX2(int newY) {
x2 = newY;
}
public
String toString(){
return "(" + getX1() + ", " + getX2() + ")" ;
}
}
Main entry :
public class MyExample {
private Line line = null;
public MyExample() {
line = new Line();
System.err.println("Line
implement serializable interface : "
+
(line instanceof Serializable));
}
public void showMe() {
System.out.println("Show all
about me ...");
System.out.println(line.toString());
}
public static void main(String[] args) {
MyExample demo = new
MyExample();
// i want to change the action
of show me, but i cannot get line source.
// so i will trying load-time
weaving
demo.showMe();
}
}
output :
Line implement serializable interface : true
Show all about me ...
(0, 0)
定义一个aspect :由于1.5之后可以直接支持annotation,所以对于不复杂的aspect定义可以直接使用标签表示。但是目前aspectj支持的标签相对其语法来讲功能要弱些,因此可以根据实际情况选择
@Aspect
public class DynamicPrinter {
@Pointcut("call(void
example.aop.weaver.MyExample.showMe())")
void printTitle() {}; //此处定义一个切入点--发生调用MyExample的showMe()方法的时刻
@Before("printTitle()")//此处定义了一个advice,其直接引用到前面定义的切入点
public void printStartMessage() {
System.out.println("***************************************");
}
@After("printTitle()")//此处定义了一个advice,其直接引用到前面定义的切入点
public void printOverMessage() {
printStartMessage();
}
@Around("call(String bean.Line.toString())")
//此处定义了一个替换方法的advice,由于没有已定义的切入点可以引用,因此写出切入点原始信息call(String
bean.line.toString())
public String redirectMessage() {
return "This is a hack
action!!!";
}
@DeclareParents("bean.Line")
private Serializable i;
}
编译aj和源文件
由于aspectj提供了ant的task,所以使用比较方便
注意一点是,目前iajc只支持最高1.5 level的文件编译,如果使用高于1.5的jdk的话,需要指定编译级别到1.5
sourceRoots, sourceRootsRef
(Path)
Directories containing source files
(ending with .java or .aj) to compile.
inpath, inpathRef (Path)
Read .class files for bytecode weaving
from directories or zip files (like classpath).
classpath, classpathRef (Path)
The classpath used by the sources being
compiled. When compiling aspects, include the same version of the
aspectjrt.jar.
destDir
The directory in which to place the
generated class files. Only one of destDir and
outJar may be set.
new output :
Line implement serializable interface : true
***************************************
Show all about me ...
This is a hack action!!!
***************************************
后记 :
把iajc编译后的源文件反编译之后,就可以参考静态织入的源码道理了
public class MyExample
{
private
Line line;
private static final
org.aspectj.lang.JoinPoint.StaticPart ajc$tjp_0;
public
MyExample()
{
line = null;
line = new Line();
System.err.println((new StringBuilder("Line implement serializable
interface : ")).append(line instanceof
Serializable).toString());
}
public
void showMe()
{
System.out.println("Show all about me ...");
Line line1;
System.out.println(toString_aroundBody1$advice(this,
line1 = line, DynamicPrinter.aspectOf()));
}
public
static void main(String args[])
{
MyExample demo = new MyExample();
demo;
DynamicPrinter.aspectOf().printStartMessage();
showMe();
break MISSING_BLOCK_LABEL_30;
Throwable throwable;
throwable;
DynamicPrinter.aspectOf().printOverMessage();
throw throwable;
DynamicPrinter.aspectOf().printOverMessage(); return;
}
private
static final String toString_aroundBody0(MyExample myexample, Line
line1)
{
return line1.toString();
}
private
static final String toString_aroundBody1$advice(DynamicPrinter
this, Line line1, DynamicPrinter dynamicprinter)
{
return "This is a hack
action!!!";
}
static
{
Factory factory = new Factory("MyExample.java",
Class.forName("example.aop.weaver.MyExample"));
ajc$tjp_0 = factory.makeSJP("method-call",
factory.makeMethodSig("1", "toString", "bean.Line", "", "", "",
"java.lang.String"), 26);
}
}
public class DynamicPrinter
{
private Serializable i;
private
static Throwable ajc$initFailureCause;
public
static final DynamicPrinter ajc$perSingletonInstance;
public
DynamicPrinter()
{
}
void printTitle()
{
}
public
void printStartMessage()
{
System.out.println("***************************************");
}
public
void printOverMessage()
{
printStartMessage();
}
public String redirectMessage()
{
return "This is a hack action!!!";
}
public
static DynamicPrinter aspectOf()
{
if(ajc$perSingletonInstance == null)
throw new NoAspectBoundException("example.aop.hook.DynamicPrinter",
ajc$initFailureCause);
else
return ajc$perSingletonInstance;
}
public
static boolean hasAspect()
{
return ajc$perSingletonInstance != null;
}
private
static void ajc$postClinit()
{
ajc$perSingletonInstance = new DynamicPrinter();
}
static
{
try
{
ajc$postClinit();
}
catch(Throwable throwable)
{
ajc$initFailureCause = throwable;
}
}
}
但是反编译jar中Line.class的时候,发现其结构没有变化,不知为何能够在运行时刻将serializable接口和toString方发织入进去的?