本节课要点:ASM代码插入
- 预期实现效果
- 需要安装字节码插件
- 实现字节码插桩
- 遇到问题及效果
- ASM插件使用
- 运行App看效果
预期实现效果
- 未ASM代码注入时代码样子
/**
* @author billy.qi
* @since 17/9/20 16:56
*/
public class CategoryManager {
private static HashMap<String, ICategory> CATEGORIES = new HashMap<>();
static void register(ICategory category) {
if (category != null) {
CATEGORIES.put(category.getName(), category);
}
}
public static Set<String> getCategoryNames() {
return CATEGORIES.keySet();
}
}
- 实现ASM代码注入后代码样子
/**
* @author billy.qi
* @since 17/9/20 16:56
*/
public class CategoryManager {
private static HashMap<String, ICategory> CATEGORIES = new HashMap<>();
static { //静态代码注入
CategoryManager.register(new CategoryD());
}
static void register(ICategory category) {
if (category != null) {
CATEGORIES.put(category.getName(), category);
}
}
public static Set<String> getCategoryNames() {
return CATEGORIES.keySet();
}
}
需要安装字节码插件
实现字节码插桩
- 所需代码
private void generateCodeIntoJarFile(File jarFile){
def optJar = new File(jarFile.getParent(), jarFile.name + ".opt")
if (optJar.exists())
optJar.delete()
def file = new JarFile(jarFile)
Enumeration enumeration = file.entries()
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(optJar))
while (enumeration.hasMoreElements()) {
JarEntry jarEntry = (JarEntry) enumeration.nextElement()
String entryName = jarEntry.getName()
ZipEntry zipEntry = new ZipEntry(entryName)
InputStream inputStream = file.getInputStream(jarEntry)
jarOutputStream.putNextEntry(zipEntry)
if (isNeedInsertClass(entryName)) {
project.logger.error('generate code into:--- '+entryName)
def bytes = doGenerateCode(inputStream)
jarOutputStream.write(bytes)
} else {
jarOutputStream.write(IOUtils.toByteArray(inputStream))
}
inputStream.close()
jarOutputStream.closeEntry()
}
jarOutputStream.close()
file.close()
if (jarFile.exists()) {
jarFile.delete()
}
optJar.renameTo(jarFile)
}
boolean isNeedInsertClass(String entryName) {
if (entryName == null || !entryName.endsWith(".class"))
return false
if (needInsertClassNameLeft) {
entryName = entryName.substring(0, entryName.lastIndexOf('.'))
return needInsertClassNameLeft == entryName
}
return false
}
- 实现字节码插桩关键代码
/**
* 就这里利用ASM生成新的字节码
* @param inputStream
* @return
*/
private byte[] doGenerateCode(InputStream inputStream) {
//套路和扫描哪里一样
ClassReader cr = new ClassReader(inputStream)
ClassWriter cw = new ClassWriter(cr, 0)
//这里是扫描的类访问器,需要需要新定义一个代码插入的类访问器
ClassVisitor cv = new MyTestClassVisitor(project,Opcodes.ASM5, cw)
cr.accept(cv, ClassReader.EXPAND_FRAMES)
return cw.toByteArray()
}
遇到问题及效果
-
问题1
需要复制产物文件,解决问题
-
问题2
问题解决,visitMethod方法需要返回结果
-
效果1,方法ASM码
ASM插件使用
1、查看Class文件 ASM码
2、Class源文件写入要插入的代码
3、查看上面两部分不同
4、复制不同内容,简单修改
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(14, label1);
methodVisitor.visitTypeInsn(NEW, "com/billy/app_lib_interface/CategoryD");
methodVisitor.visitInsn(DUP);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "com/billy/app_lib_interface/CategoryD", "<init>", "()V", false);
methodVisitor.visitMethodInsn(INVOKESTATIC, "com/billy/app_lib_interface/CategoryManager", "register", "(Lcom/billy/app_lib_interface/ICategory;)V", false);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(15, label2);
运行App看效果
1、原有效果
2、运行后效果
好了这节课就到这里,我们小节课程,简单分析下AutoRegister源码,再见