转自 http://blog.sina.com.cn/s/blog_46d0362d0100mmzy.html
有时候调试一些程序会牵涉到第三方的类库,但由于它们都是以class或者jar形式发布的,不大可能改变其行为,所以不是特别方便。这个时候唯一可以利用的就是AOP的技术,常用的就是AspectJ
首先是几个概念:
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,所以使用比较方便
<taskdef resource="org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties">
<classpath>
<path refid="My Aop Example.classpath">
</path>
</classpath>
</taskdef>
<target name="iajc">
<iajc sourceroots="${basedir}/src" inpath="${basedir}/lib/Line.jar" destdir="${basedir}/bin" classpathref="My Aop Example.classpath" source="1.5" />
</target>
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方发织入进去的?