概述
ASM 是java字節碼操作框架。
由於ASM性能好的原因,所以在動態編譯上往往比Javassist上使用的更加廣泛。
之前已經寫過了Javassist實現動態編譯的demo,對動態編譯不瞭解的讀者可以看下:動態編譯入門(gradle Transform Demo)
本文在前面demo的基礎上,將Javassist的實現改爲了ASM。
因此對於gradle 插件等重複的點就不多加描述了,本文主要講解下ASM的使用。
Demo 概述
本demo通過ASM,實現在方法中動態插入代碼的功能。
主要代碼如下:
public class PluginTestClass {
public void init() {
System.out.println("PluginTestClass init");
//此處將會使用動態編譯插入代碼
//PluginTestClass.testPrint();
}
public static void testPrint(String s) {
System.out.println(s);
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PluginTestClass pluginTestClass=new PluginTestClass();
pluginTestClass.init();
}
}
Demo結果
2020-04-05 10:26:53.708 11336-11336/com.example.transformtest I/System.out: PluginTestClass init
2020-04-05 10:26:53.708 11336-11336/com.example.transformtest I/System.out: 我是插入的代碼
“代碼插入”實現流程
1、自己實現了TestFileUtils.java這個類來遞歸遍歷文件夾下的所有文件。
2、遍歷所有文件找到PluginTestClass這個類。
3、使用ClassVisitor 和MethodVisitor 分別來找到init方法和修改init方法。
MethodVisitor中的visitMaxs方法用於返回最大的操作數棧和局部變量表,這兩項往往會根據插入的代碼而改變。(此demo中插入的代碼不會導致這兩者改變,因此此demo中沒有修改)
筆者自己單獨寫了一篇文章用於講解着兩個概念,對其有興趣的讀者可以看下:
完全理解 java操作數棧和局部變量表
TestTransform.java
public class TestTransform extends Transform {
//用於指明本Transform的名字,也是代表該Transform的task的名字
@Override public String getName() {
return "TestTransform";
}
//用於指明Transform的輸入類型,可以作爲輸入過濾的手段。
@Override public Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS;
}
//用於指明Transform的作用域
@Override public Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT;
}
//是否增量編譯
@Override public boolean isIncremental() {
return false;
}
@Override public void transform(TransformInvocation invocation) {
System.out.println("TestTransform transform");
for (TransformInput input : invocation.getInputs()) {
//遍歷jar文件 對jar不操作,但是要輸出到out路徑
input.getJarInputs().parallelStream().forEach(jarInput -> {
File src = jarInput.getFile();
System.out.println("input.getJarInputs fielName:" + src.getName());
File dst = invocation.getOutputProvider().getContentLocation(
jarInput.getName(), jarInput.getContentTypes(), jarInput.getScopes(),
Format.JAR);
try {
FileUtils.copyFile(src, dst);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
//遍歷文件,在遍歷過程中
input.getDirectoryInputs().parallelStream().forEach(directoryInput -> {
File src = directoryInput.getFile();
System.out.println("input.getDirectoryInputs fielName:" + src.getName());
File dst = invocation.getOutputProvider().getContentLocation(
directoryInput.getName(), directoryInput.getContentTypes(),
directoryInput.getScopes(), Format.DIRECTORY);
try {
scanFilesAndInsertCode(src);
FileUtils.copyDirectory(src, dst);
} catch (Exception e) {
System.out.println(e.getMessage());
}
});
}
}
private void scanFilesAndInsertCode(File file) throws Exception {
TestFileUtils.scanFileInDir(file,
new TestFileUtils.ScanFileCallback() {
@Override public void action(File file) {
if (file.getAbsolutePath().contains("PluginTestClass")) {
insertTestCode(file);
}
}
});
}
private void insertTestCode(File file) {
try {
//讀取class文件並且插入代碼
InputStream inputStream = new FileInputStream(file);
ClassReader cr = new ClassReader(inputStream);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5, cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES);
//生成新的class文件
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(cw.toByteArray());
fileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static class MyClassVisitor extends ClassVisitor {
MyClassVisitor(int api, ClassVisitor cv) {
super(api, cv);
}
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
//generate code into this method
if (name.equals("init")) {
mv = new MyMethodVisitor(Opcodes.ASM5, mv);
}
return mv;
}
}
private static class MyMethodVisitor extends MethodVisitor {
MyMethodVisitor(int api, MethodVisitor mv) {
super(api, mv);
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
//添加方法
mv.visitLdcInsn("我是插入的代碼");
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/example/testplugin/PluginTestClass",
"testPrint",
"(Ljava/lang/String;)V", false);
}
super.visitInsn(opcode);
}
@Override public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack, maxLocals);
}
}
}
TestFileUtils.java
public class TestFileUtils {
public static void scanFileInDir(File rootFile, ScanFileCallback callback) {
if (rootFile == null) {
return;
}
if (rootFile.isDirectory()) {
if (rootFile.listFiles() == null) {
return;
}
for (File file : rootFile.listFiles()) {
scanFileInDir(file, callback);
}
} else {
callback.action(rootFile);
}
}
public interface ScanFileCallback {
void action(File file);
}
}