使用ASM包進行Class File修改真是很方便,不過可惜的是ASM不提供現成的工具,那我們就利用它提供的強大的位元組碼操作能力,自己來做一個吧:
基本思路如下:假設操作類為A, 假設要加的屬性為PA,我們為了操作上方便,構造類B,將類B的PA屬性加到A上就可以了,有了這個思路即可快速實現如下代碼:
---------------------------------------------------------------------------------------------------------------
首先構造Visitor用於新增成員變數PA:
public class BytecodeClassFieldAdder extends ClassAdapter {
private final List<FieldNode> fieldNodesToAppend;
/**
* construct for current class
*
* @param cv
* @param fieldNode
*/
public BytecodeClassFieldAdder(ClassVisitor cv, List<FieldNode> fieldNodes) {
super(cv);
this.fieldNodesToAppend = fieldNodes;
}
/**
* visit to the end for current class, append to the vistor class
*
*/
public void visitEnd() {
for (FieldNode fn : this.fieldNodesToAppend) {
fn.accept(cv);
}
super.visitEnd();
}
}
---------------------------------------------------------------------------------------------------------------
然後構造從另外一個類B中抽取成員的操作類:
public class BytecodeClassFilterUtil implements IBytecodeContainer{
private ClassNode classNode = null;
/**
* bytecode class filter utility construction
*
* @param classFile
* @param op
* @throws IOException
*/
public BytecodeClassFilterUtil(final String classFile) throws IOException {
FileInputStream fis = new FileInputStream(classFile);
ClassReader cr = new ClassReader(fis);
BytecodeClassFilter ca = new BytecodeClassFilter(null);
cr.accept(ca, ClassReader.EXPAND_FRAMES);
if (fis != null) {
fis.close();
}
}
/**
* bytecode class filter utility construction
*
* @param classFile
* @param op
* @throws IOException
*/
public BytecodeClassFilterUtil(File classFile) throws IOException {
FileInputStream fis = new FileInputStream(classFile);
ClassReader cr = new ClassReader(fis);
BytecodeClassFilter ca = new BytecodeClassFilter(null);
cr.accept(ca, ClassReader.EXPAND_FRAMES);
if (fis != null) {
fis.close();
}
}
/**
* get a specified class node instance for current bytecode class filter utility
*
* @return
*/
public ClassNode getClassNode() {
return this.classNode;
}
/**
* get a specified field node by a specified name pattern and description pattern
*
* @param name
* @return
*/
@SuppressWarnings("unchecked")
public List<FieldNode> getFieldNode(String namePattern, String descPattern) {
List<FieldNode> returnNodes = new ArrayList<FieldNode>();
List fields = this.classNode.fields;
if (fields != null) {
for (Object ofield : fields) {
FieldNode field = (FieldNode) ofield;
boolean blnNameMatch = true;
boolean blnDescMatch = true;
if (namePattern != null) {
blnNameMatch = Pattern.matches(namePattern, field.name);
}
if (descPattern != null) {
blnDescMatch = Pattern.matches(descPattern, field.desc);
}
if (blnNameMatch && blnDescMatch) {
returnNodes.add(field);
}
}
}
return returnNodes;
}
/**
* get a specified method name or a list of them.
*
* @param name
* @param description
* @return
*/
@SuppressWarnings("unchecked")
public List<MethodNode> getMethodNode(String namePattern, String descPattern) {
List<MethodNode> returnNodes = new ArrayList<MethodNode>();
List methods = this.classNode.methods;
if (methods != null) {
for (Object omethod : methods) {
MethodNode method = (MethodNode) omethod;
boolean blnNameMatch = true;
boolean blnDescMatch = true;
if (namePattern != null) {
blnNameMatch = Pattern.matches(namePattern, method.name);
}
if (descPattern != null) {
blnDescMatch = Pattern.matches(descPattern, method.desc);
}
if (blnNameMatch && blnDescMatch) {
returnNodes.add(method);
}
}
}
return returnNodes;
}
/**
* get all of the field descriptions for a specified class
*
* @return
*/
@SuppressWarnings("unchecked")
public List<String> getFieldDescription() {
List<String> descList = new ArrayList<String>();
List fields = this.classNode.fields;
if (fields != null) {
for (Object ofield : fields) {
FieldNode field = (FieldNode) ofield;
StringBuilder sb = new StringBuilder();
sb.append(field.name).append(":").append(field.desc);
descList.add(sb.toString());
}
}
return descList;
}
/**
* get all of the method list for a specified class
*
* @return
*/
@SuppressWarnings("unchecked")
public List<String> getMethodDescription() {
List<String> descList = new ArrayList<String>();
List methods = this.classNode.methods;
if (methods != null) {
for (Object omethod : methods) {
MethodNode method = (MethodNode) omethod;
StringBuilder sb = new StringBuilder();
sb.append(method.name).append(":").append(method.desc);
descList.add(sb.toString());
}
}
return descList;
}
/**
* bytecode class filter extend from class adpater class.
*
*/
class BytecodeClassFilter extends ClassAdapter {
// construction call for current class
public BytecodeClassFilter(final ClassVisitor cv) {
super(new ClassNode() {
public void visitEnd() {
if (cv != null) {
accept(cv);
}
}
});
}
// execute the next operation after this visit ending
public void visitEnd() {
classNode = (ClassNode) cv;
}
}
}
------------------------------------------------------------------------------------------------
構造調用函數,實現屬性“轉移”功能:
public void addFieldToClass(String src, String des, String combine, String nameFilter, String descFilter)
throws IOException {
BytecodeClassFilterUtil util = new BytecodeClassFilterUtil(src);
List<FieldNode> fields = util.getFieldNode(nameFilter, descFilter);
// visitor current class
if (fields.size() == 0) {
System.out.println("ERROR: No field is chosen out by the filter.");
} else {
ClassWriter cw = new ClassWriter(0);
BytecodeClassFieldAdder adder = new BytecodeClassFieldAdder(cw, fields);
FileInputStream fis = new FileInputStream(des);
ClassReader cr = new ClassReader(fis);
cr.accept(adder, ClassReader.EXPAND_FRAMES); // need to expand frames for current end user
if (fis != null) {
fis.close();
}
// convert the specified method into current class
byte[] bytearray = cw.toByteArray();
FileOutputStream fos = new FileOutputStream(combine);
fos.write(bytearray);
fos.flush();
fos.close();
}
}
-------------------------------------------------------------------
最後掛接操作介面:
addFieldToClass(sourceFile, targetFile, destFile, nameFilter, descFilter);
需要注意的是: sourceFile = B.class;
targetFile = A.class;
destFile = 合成後的A.class
nameFilter,descFilter 支援Regex,並且可以為NULL,都為NULL時表示添加所有Fields;
寫好後一個小問題:
如果是靜態變數並且由賦值的時候發現只copy了聲明部分,並未拷貝賦值部分,這個是啥原因呢?
原來是忘記拷貝了cinit()函數而引起的,不過這個屬於下一部分要講的內容了。