GluonJ
GluonJ是一个简单的面向切面编程(AOP)的java工具.GluonJ针对面向对象的语言(OOP)结构提供了一个极其简单的扩展方式实现了AOP的特性,非常独特:不同于其他流行的AOP框架,没有采用pointcut-advice,而是采用了revisers 和 within methods.
Gluonj是较高级别的API,底层使用javassist进行java类文件处理.
有意思的项目名称:Gluon+J,J指的是java,Gluon翻译后是胶子(物理名词),gluonJ的特性与胶子还真是有几分相似之处.项目的作者对物理也有研究?
Gluonj的HelloWold
原始类:Person | 扩展类:SayHello |
package test; |
package sample; |
#编译类文件
java -jar GluonJCompiler.jar test/Person.java sample/SayHello.java
#织入代码
java -jar gluonj.jar test/Person.class sample/SayHello.class#运行
java test.Person
#输出:
Hello!
- reviser:中文翻译为修订者或校对者
扩展类,类似java继承.该类中使用revises( within、requires、using等 )语句对原始类(target)进行扩展。 - target class:原始类,编译后target class的字节码将被修改.
- compilation&post-compile transformation
gluonJ编译包含两步:source-to-bytecode translation 和 bytecode-to-bytecode translation,我们称之为compilation 和 post-compile transformation.
compilation 过程使用GluonJCompiler.jar完成源码(文本文件)到字节码的转换
过程类似javac,参数(option)与javac也基本兼容.
post-compile 过程使用gluonj.jar完成字节码修改,以实现代码织入
该过程接受三个选项:-debug(开启调试模式),-d(输出文件夹),-cp(class path)。并注意参数是.class文件不是.java文件.
代码织入并非将reviser中代码放入原始类中,而是在原始类中加入reviser的执行句柄.
反编译后的Person.class文件 反编译后的SayHello.class文件 package test;
import java.io.PrintStream;
import sample.SayHello;
public class Person
{
public void greet()
throws
{
System.out.println("Hi!");
}
public static void main(String[] args) throws {
new SayHello().greet();
}
}package sample;
import java.io.PrintStream;
import javassist.gluonj.Reviser;
import test.Person;
@Reviser
public class SayHello extends Person
{
public void greet()
throws
{
System.out.println("Hello!");
}
private void _init()
throws
{
}
}
功能&特性
- reviser与子类(subclass)很像(但不是).可基本(出去下文中的限制部分)按照子类的方式实现reviser类.
- 可扩展的行为(这些行为都将附加到原始类中)包括:
添加新方法、重写方法(可通过super调用原方法),实现新的接口 - reviser的安全性遵从java,可访问target class可见成员,不能访问私有(private)成员,不能重写final方法.
- 可以重写static方法
限制
- target class中的每个构造函数都需要在reviser中声明(可以理解为overriding).
- reviser不能重载(overloading)或添加构造函数
reviser不能声明构造函数,当你需要一个新的构造函数时,只能在reviser中使用工厂方法(factory method) - reviser不能声明多个within 方法
可以通过使用多个reviser(revise相同类)的方式实现此目的。 - reviser不能被实例化.
- 不能定义reviser的子类(subclass)
各种详细用法:
Load-time weaving without a Java agent |
Ant task
GluonJ程序的编译过程(当前只有post-compile)可以配置为一个ant task.示例如下
<?xml version="1.0"?>
<project name="hello" basedir=".">
<taskdef name="weave" classname="javassist.gluonj.ant.taskdefs.Weave">
<classpath>
<pathelement location="./gluonj.jar"/>
</classpath>
</taskdef>
<target name="weave">
<weave destdir="./out" debug="false" >
<classpath>
<pathelement path="./classes"/>
</classpath>
<fileset dir="./classes" includes="test/**/*" />
</weave>
</target>
</project>
熟悉ant的童鞋一看就明白了,在此就不多介绍了.(debug参数同post-compile中debug参数).
load-time weaving 加载时织入
post-compile可以在加载时在执行.采用此方式时,需要在jvm启动参数中添加-javaagent.示例如下:
java -javaagent:gluonj.jar=sample.SayHello test.Person
- "gluonj.jar=”后是reviser的名字(注意没有后缀).多个riviser时,使用英文逗号进行分割.
- -javaagent:…是一个VM参数,在使用ecilpse或其他IDE启动时需要注意进行相应配置.
- 如果需要查看详细的日志信息,需要使用debug参数,类似:
java -javaagent:gluonj.jar=debug:sample.SayHello test.Person
注意debug的位置.
Load-time weaving without a Java agent 不使用java agent在加载时织入
不适用VM参数-javaagent 而在加载时织入的办法是,写类似如下的启动类:
import javassist.gluonj.util.Loader;编译及运行:
public class Runner {
public static void main(String[] args) throws Throwable {
Class mainClass = test.Person.class;
Class[] revisers = { sample.SayHello.class };
Loader.run(mainClass, args, revisers);
}
}
javac -cp .:gluonj.jar Runner.java
java -cp gluonj.jar Runner
Override static methods 重写static方法
不同于普通class,reviser可重写target class的static方法,例如:
target clas |
reviser |
package test; |
package sample; |
编译在反编译后:
Fact.class | FactLogger.class |
package test; |
package sample; |
可以看到Fact.fact方法被修改了.细心的童鞋会发现这里可能会有个疑问:main方法中的Fact.fact的调用也被改成了FactLogger.fact,如果有一个类比如TestA也调用了Fact.fact方法,由于编译的时候没有将TestA.class列入到参数列表中,那么TestA中的Fact.fact(5)调用不会被修改,那么结果将产生4条输出.log还好,那如果FactLogger在return时做了 -1操作,那就会有问题了.读者可做个测试.在实际应用中要注意此问题.(当然如果只是log不会有啥大问题).
Requires
gluonJ允许在同一个target class上有多个reviser,在这种情况下,需要通过requires语句指定revisers的顺序.
package sample;
public class GoodDay requires SayHello revises test.Person {
public void greet() {
super.greet();
System.out.println("It's a good day today.");
}
}
java -jar gluonj.jar test/Person.class sample/SayHello.class sample/GoodDay.class
java -javaagent:gluonj.jar=sample.GoodDay test.Person
java -javaagent:gluonj.jar=sample.GoodDay,sample.SayHello test.Person
- Reviser R可以访问 R requires的其他revisers添加的可见成员.例如:
LinkNode:
package test;
public class LinkNode {
protected LinkNode next = null;
public static void main(String[] args) {
LinkNode n = new LinkNode();
System.out.println(n);
}
}IntNode:package sample;
import test.LinkNode;
public class IntNode revises LinkNode {
public int value;
public int get() { return value; }
}PrintableNode:package sample;
import test.LinkNode;
public class PrintableNode requires IntNode revises LinkNode {
public String toString() { return "Node:" + get() + " " + value; }
}PrintableNode requires IntNode,就可以访问IntNode中的get方法. 通过前面的分析可知,PrintableNode在编译后继承了IntNode,当然就可以调用IntNode中的get方法了. - Normal class 可通过Using声明访问Reviser成员.
package test;
using sample.IntNode;
public class NodeTest {
public static void main(String[] args) {
LinkNode n = new LinkNode();
System.out.println(n.value);
}
}using语句类似import语句,声明了要访问的reviser.编译(编译后class及反编译下载)&执行语句:java -jar GluonJCompiler.jar test/LinkNode.java sample/IntNode.java sample/PrintableNode.java test/NodeTest.java
java -jar gluonj.jar test/LinkNode.class sample/IntNode.class sample/PrintableNode.class test/NodeTest.class
java -cp . test/NodeTest结果:0
反编译后的程序还是挺有意思的.试想如果在编译的时候去掉printableNode,会是什么结果呢?
package test;
public class Position {
public int x;
public void rmove(int dx) { setX(x + dx); }
public void setX(int newX) {
System.out.println("setX");
x = newX;
}
}
package test;PosLgger类
public class PosTest {
public static void main(String[] args) {
Position p = new Position();
p.setX(5);
p.rmove(11);
}
}
package sample;
public class PosLogger revises test.Position {
public void setX(int newX) within test.PosTest.main(String[]) {
System.out.println("x: " + x + ", newX: " + newX);
super.setX(newX);
}
}
java -jar GluonJCompiler.jar test/Position.java sample/PosLogger.java test/PosTest.java
java -jar gluonj.jar test/Position.class sample/PosLogger.class test/PosTest.class
java -cp . test/PosTest
输出:
x: 0, newX: 5
setX
setX
package sample;
public class PosLogger revises test.Position {
public void setX(int newX) within test.PosTest {
System.out.println("x: " + x + ", newX: " + newX);
super.setX(newX);
}
}
Using a reviser in Java 在java中使用reviser
package sample;
import javassist.gluonj.Reviser;
@Reviser public class SayHello extends test.Person {
public void greet() {
System.out.println("Hello!");
}
}
javac -cp .:gluonj.jar test/Person.java sample/SayHello.java
java -jar gluonj.jar test/Person.class sample/SayHello.class
java test.Person
javac -cp .:gluonj.jar test/Person.java sample/SayHello.java
java -javaagent:gluonj.jar=sample.SayHello test.Person
package sample;
import javassist.gluonj.Reviser;
import javassist.gluonj.Require;
@Reviser @Require(SayHello.class)
public class GoodDay extends test.Person {
public void greet() {
super.greet();
System.out.println("It's a good day today.");
}
}
@Require({SayHello.class, SayChao.class})
package sample;
import javassist.gluonj.Reviser;
import javassist.gluonj.Within;
import javassist.gluonj.Code;
@Reviser public class PosLogger extends test.Position {
@Within(test.PosTest.class) @Code("main(java.lang.String[])")
public void setX(int newX) {
System.out.println("x: " + x + ", newX: " + newX);
super.setX(newX);
}
}
package sample;
import javassist.gluonj.Reviser;
@Reviser public class Say {
@Reviser public static class SayHello extends test.Person {
public void greet() {
System.out.println("Hello!");
}
}
@Reviser public static class GoodDay extends test.Person {
public void greet() {
super.greet();
System.out.println("It's a good day today.");
}
}
}
javac -cp .:gluonj.jar test/Person.java sample/Say.java
java -javaagent:gluonj.jar=sample.Say test.Person
package sample;
import test.LinkNode;
import javassist.gluonj.Reviser;
import javassist.gluonj.Require;
import static javassist.gluonj.GluonJ.revise;
@Reviser @Require(IntNode.class)
public class PrintableNode extends LinkNode {
public String toString() {
IntNode in = (IntNode)revise(this);
return "Node:" + in.get() + " " + in.value;
}
}
public String toString() {
IntNode in = (IntNode)(Object)this;
return "Node:" + in.get() + " " + in.value;
}
- @Require注解仍然是需要的,以描述reviser的优先顺序.如果不指定IntNode与PrintableNode的顺序,将会报错.
- using语句没有对应的java注解,using只是控制reviser添加成员的可见性.
--Constructors of an @Reviser class
在上文reviser的限制中介绍了关于构造函数的限制:不能有子类,不能实例化,构造函数必须实现target class中所有的构造函数等.下面用实例来说明:
target class Counter | reviser:Incrementable |
package test; |
package sample; |
package sample;
import javassist.gluonj.Reviser;
@Reviser public class Incrementable extends test.Counter {
private int delta;
public Incrementable() {
super(0);
delta = 1;
}
public void increment() {
counter += delta;
}
}
public Counter(int c) {
counter = c;
delta = 1; // copied from the @Reviser class
}
javac -cp .:GluonJCompiler.jar:gluonj.jar test/Counter.java sample/Incrementable.java
java -jar gluonj.jar test/Counter.class sample/Incrementable.class
Counter.class | Incrementable.class |
package test; |
package sample; |
- Counter构造函数没有发生变化,调用构造函数的地方被修改成了调用reviser的构造函数
- Reviser中的无参构造函数改成了有参构造函数.
童鞋们在使用的时候一定要注意此处,特别是有其他代码调用构造函数的情况.