1、织入入口,配置
1.1、织入入口
kilim.tools.Weaver是织入的主类,通过程序参数设置要织入的代码路径,可以是class文件、jar包、其他(是什么)、目录(目录中可以是jar包、class文件)。
如果传入的是class文件,会直接织入;调用kilim.tools.Weaver.weaveFile(String, InputStream, Detector)织入
如果是jar包,会解析jar文件,for循环时候,jdk会把for编译成iterator接口实现,对于jar文件,实际提取文件内容的是kilim.analysis.JarIterator,从java.util.jar.JarFile中直接获取所有entries,for循环中返回的每一项(class文件或者目录)都是FileLister.Entry的子类JEntry,可以获取文件名、文件流和文件大小。然后循环jar文件中每一项,如果是class文件就织入,调用接口同传入class文件一样。
如果是目录,则会递归目录,但是也只会处理目录下的class文件,不会处理目录下的jar包。处理方式类似jar包,会调用迭代器DirIterator处理,迭代器没有用递归实现,而是自己放入了个Stack。迭代时候每次返回FileLister.Entry的子类DirEntry。jar包验证,处理.
1.2、ant配置
<java classname="kilim.tools.Weaver" fork ="yes">
<classpath refid="kilim.classpath"/>
<assertions>
<enable/>
</assertions>
<arg value="-x" />
<!-- Skip classes that match ExInvalid. These are negative tests for the weaver. Also skip tests for this pass-->
<arg value="ExInvalid|test" />
<arg value="-d" />
<arg value="./classes" />
<arg line="./classes" />
</java>
2、可织入分析 && 织入文件
2.1、织入接口kilim.tools.Weaver.weaveFile(String, InputStream, Detector)
在1中的织入入口会调用到,包括织入代码,把织入后的代码写到磁盘:
static void weaveFile(String name, InputStream is, Detector detector) throws IOException {
try {
ClassWeaver cw = new ClassWeaver(is, detector);
cw.weave();//织入
writeClasses(cw);//把织入后的代码字节数据从ClassInfo写入到class文件
} catch (KilimException ke) {
System.err.println("***** Error weaving " + name + ". " + ke.getMessage());
// ke.printStackTrace();
err = 1;
} catch (RuntimeException re) {
System.err.println("***** Error weaving " + name + ". " + re.getMessage());
re.printStackTrace();
err = 1;
} catch (IOException ioe) {
err = 1;
System.err.println("***** Unable to find/process '" + name + "'\n" + ioe.getMessage());
}
}
创建ClassWeaver的时候,传入的decetor是Detector.DEFAULT,一个默认的实现,这个类主要用来判断类是否需要织入、织入状态灯。
ClassWeaver封装了ClassFlow,ClassFlow是继承了ClassNode的,ClassNode是asm拿着类文件中所有内容构造的一棵树。在构造ClassWeaver的时候,就会构造一个ClassFlow对象:
public ClassWeaver(InputStream is, Detector detector) throws IOException {
classFlow = new ClassFlow(is, detector);
}
cw.weave()[kilim.analysis.ClassWeaver.weave()]是织入的地方,包括对字节码的分析、通过ClassWriter织入、把植入后的字节码转换成ClassInfo对象:
public void weave() throws KilimException {
classFlow.analyze(false);//分析
if (needsWeaving() && classFlow.isPausable()) {
ClassWriter cw = new ClassWriter(false);
accept(cw);//对需要进行织入的方法进行织入
addClassInfo(new ClassInfo(classFlow.getClassName(), cw.toByteArray()));//把需要植入后的字节码从ClassWriter放入到ClassInfo
}
}
ClassFlow.analyze(false),对一个方法是否需要织入的分析,方法isPausable为true的就是可织入的:
public ArrayList<MethodFlow> analyze(boolean forceAnalysis) throws KilimException {
Detector save = Detector.setDetector(detector);
try {
cr.accept(this, false);//把当前ClassFlow实例传入到ClassReader中,会把当前类信息填充到ClassFlow中
for (Object o : this.fields) {
FieldNode fn = (FieldNode) o;
if (fn.name.equals(Constants.WOVEN_FIELD)) {//织入的时候会写入的一个变量,如果已经有了,则表明已经织入过
isWoven = true;
break;
}
}
if (isWoven && !forceAnalysis)
return new ArrayList<MethodFlow>(); // This is a hack. 如果织入过并且不强制织入,就返回。强制呢?
cr = null; // We don't need this any more.
classDesc = TypeDesc.getInterned("L" + name + ';');//类型描述符
ArrayList<MethodFlow> flows = new ArrayList<MethodFlow>(methods.size());
String msg = "";
for (Object o : methods) {
try {
MethodFlow mf = (MethodFlow) o;
if (mf.isBridge()) {//方法access_flag包含bridge的方法,方法由编译器产生
MethodFlow mmf = getOrigWithSameSig(mf);//获取bridge方法对应的原始方法
if (mmf != null)
mf.setPausable(mmf.isPausable());//把birdge方法的pausable属性设置给原始方法
}
mf.verifyPausables();//验证可织入性,如果已经织入或者不需要织入是直接返回了的
if (mf.isPausable())
isPausable = true;
if ((mf.isPausable() || forceAnalysis) && (!mf.isAbstract())) {
mf.analyze();
}
flows.add(mf);
} catch (KilimException ke) {
msg = msg + ke.getMessage() + "\n-------------------------------------------------\n";
}
}
if (msg.length() > 0) {
throw new KilimException(msg);
}
methodFlows = flows;
return flows;
} finally {
Detector.setDetector(save);
}
}