javassist用法&asm用法以及区别
======javassit 用法======
在看dubbo源码和mybatis源码的时候发现代理用的是javassist, 简单研究下。可以动态的修改一个类,也可以动态的创建类,也可以实现代理(可以基于继承和接口两种)。
pom如下;
<dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.28.0-GA</version> </dependency>
1. 基本用法
1. 实现动态的创建类和增加字段和方法
package org.example.javassit; import javassist.*; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URISyntaxException; public class Test2 { public static void main(String[] args) throws Exception { test2(); } // 动态的添加字段信息 private static void test2() throws Exception { //创建类,这是一个单例对象 ClassPool cp = ClassPool.getDefault(); //我们需要构建的类 CtClass ctClass = cp.get("cn.qz.Person"); //创建字段,指定了字段类型、字段名称、字段所属的类 CtField field = new CtField(cp.get("java.lang.Integer"), "age", ctClass); //指定该字段使用private修饰 field.setModifiers(Modifier.PRIVATE); //设置age字段的getter/setter方法 ctClass.addMethod(CtNewMethod.setter("getAge", field)); ctClass.addMethod(CtNewMethod.getter("setAge", field)); //当前工程的target目录 final String targetClassPath = Thread.currentThread().getContextClassLoader().getResource("").toURI().getPath(); //生成.class文件 ctClass.writeFile(targetClassPath); } // 创建类信息 private static void test1() throws CannotCompileException, NotFoundException, URISyntaxException, IOException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { //创建类,这是一个单例对象 ClassPool pool = ClassPool.getDefault(); //我们需要构建的类 CtClass ctClass = pool.makeClass("cn.qz.Person"); //新增字段 CtField field$name = new CtField(pool.get("java.lang.String"), "name", ctClass); //设置访问级别 field$name.setModifiers(Modifier.PRIVATE); //也可以给个初始值 ctClass.addField(field$name, CtField.Initializer.constant("qz-default")); //生成get/set方法 ctClass.addMethod(CtNewMethod.setter("setName", field$name)); ctClass.addMethod(CtNewMethod.getter("getName", field$name)); //新增构造函数 //无参构造函数 CtConstructor cons$noParams = new CtConstructor(new CtClass[]{}, ctClass); cons$noParams.setBody("{name = \"qz\";}"); ctClass.addConstructor(cons$noParams); //有参构造函数 CtConstructor cons$oneParams = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, ctClass); // $0=this $1,$2,$3... 代表方法参数 cons$oneParams.setBody("{$0.name = $1;}"); ctClass.addConstructor(cons$oneParams); // 创建一个名为 print 的方法,无参数,无返回值,输出name值 CtMethod ctMethod = new CtMethod(CtClass.voidType, "print", new CtClass[]{}, ctClass); ctMethod.setModifiers(Modifier.PUBLIC); ctMethod.setBody("{System.out.println(name);}"); ctClass.addMethod(ctMethod); //当前工程的target目录 final String targetClassPath = Thread.currentThread().getContextClassLoader().getResource("").toURI().getPath(); //生成.class文件 ctClass.writeFile(targetClassPath); // 获取Class 对象的两种方式 // 1. 直接转 Class aClass = ctClass.toClass(); // 2. 调用类加载获取 class 信息 // Class<?> aClass = ClassLoader.getSystemClassLoader().loadClass("cn.qz.Person"); Object o = aClass.newInstance(); Method method = o.getClass().getMethod("print"); method.invoke(o); } }
2. 通过创建代理类实现增强:
(1) 接口:
package org.example.javassit.proxy; public interface IHelloService { String sayHello(String name); }
(2) 代理接口
package org.example.javassit.proxy; public interface IProxy { void setProxy(Object t); }
(3) 测试类:
package org.example.javassit.proxy; import javassist.*; import java.util.Arrays; /** * @author 乔利强 * @date 2021/8/31 10:45 * @description */ public class ProxyTest { public static void main(String[] args) throws Exception { //创建类,这是一个单例对象 ClassPool pool = ClassPool.getDefault(); pool.appendClassPath(Thread.currentThread().getContextClassLoader().getResource("").toURI().getPath()); //我们需要构建的类 CtClass ctClass = pool.makeClass("org.example.javassit.proxy.HelloServiceJavassistProxy"); //这个类实现了哪些接口 ctClass.setInterfaces(new CtClass[]{ pool.getCtClass("org.example.javassit.proxy.IHelloService"), pool.getCtClass("org.example.javassit.proxy.IProxy")}); //新增字段 CtField field$name = new CtField(pool.get("org.example.javassit.proxy.IHelloService"), "helloService", ctClass); //设置访问级别 field$name.setModifiers(Modifier.PRIVATE); ctClass.addField(field$name); //新增构造函数 //无参构造函数 CtConstructor cons$noParams = new CtConstructor(new CtClass[]{}, ctClass); cons$noParams.setBody("{}"); ctClass.addConstructor(cons$noParams); //重写sayHello方方法,可以通过构造字符串的形式 CtMethod m = CtNewMethod.make(buildSayHello(), ctClass); ctClass.addMethod(m); // 创建一个名为 setProxy 的方法 CtMethod ctMethod = new CtMethod(CtClass.voidType, "setProxy", new CtClass[]{pool.getCtClass("java.lang.Object")}, ctClass); ctMethod.setModifiers(Modifier.PUBLIC); // // $0=this $1,$2,$3... 代表方法参数 ctMethod.setBody("{$0.helloService = $1;}"); ctClass.addMethod(ctMethod); // 写到本地 ctClass.writeFile(Thread.currentThread().getContextClassLoader().getResource("").toURI().getPath()); //获取实例对象 final Object instance = ctClass.toClass().newInstance(); System.out.println(Arrays.toString(instance.getClass().getDeclaredMethods())); //设置目标方法 if (instance instanceof IProxy) { IProxy proxy = (IProxy) instance; proxy.setProxy(new IHelloService() { @Override public String sayHello(String name) { System.out.println("目标接口实现:name=" + name); return "name:" + name; } }); } if (instance instanceof IHelloService) { IHelloService service = (IHelloService) instance; service.sayHello("qz"); } } private static String buildSayHello() { String methodString = " public String sayHello(String name) {\n" + " System.out.println(\"静态代理前 ..\");\n" + " helloService.sayHello(name);\n" + " System.out.println(\"静态代理后 ..\");\n" + " return name;\n" + " }"; return methodString; } }
结果:
[public java.lang.String org.example.javassit.proxy.HelloServiceJavassistProxy.sayHello(java.lang.String), public void org.example.javassit.proxy.HelloServiceJavassistProxy.setProxy(java.lang.Object)] 静态代理前 .. 目标接口实现:name=qz 静态代理后 ..
(4) 查看生成的类:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.example.javassit.proxy; public class HelloServiceJavassistProxy implements IHelloService, IProxy { private IHelloService helloService; public HelloServiceJavassistProxy() { } public String sayHello(String var1) { System.out.println("静态代理前 .."); this.helloService.sayHello(var1); System.out.println("静态代理后 .."); return var1; } public void setProxy(Object var1) { this.helloService = (IHelloService)var1; } }
2. 实现代理
1. 基于继承实现
1. 需要增强的类:
package org.example.javassit.proxy2; public class UserDao { public void saveUser() { System.out.println("saveUser ======-"); } }
2. ProxyFactory 实现增强(基于继承实现增强)
package org.example.javassit.proxy2; import javassist.util.proxy.MethodFilter; import javassist.util.proxy.MethodHandler; import javassist.util.proxy.ProxyFactory; import java.lang.reflect.Method; public class ProxyTest { public static void main(String[] args) throws Exception { ProxyFactory factory = new ProxyFactory(); // 设置写出的目录会导出到具体的目录 // factory.writeDirectory = "D:/proxy"; // 指定父类,ProxyFactory会动态生成继承该父类的子类 factory.setSuperclass(UserDao.class); // 设定接口,接口可以继承多个,所以用数组 // factory.setInterfaces(new Class[]{}); //设置过滤器,判断哪些方法调用需要被拦截 factory.setFilter(new MethodFilter() { @Override public boolean isHandled(Method method) { if (method.getName().equals("saveUser")) { return true; } return false; } }); //设置拦截处理 factory.setHandler(new MethodHandler() { @Override public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { System.out.println("前置处理"); Object result = proceed.invoke(self, args); System.out.println("执行结果:" + result); System.out.println("后置处理"); return result; } }); // 创建 UserDao 代理类,并创建代理对象 Class<?> c = factory.createClass(); UserDao javassistTest = (UserDao) c.newInstance(); // saveUser方法,会被拦截 javassistTest.saveUser(); System.out.println(javassistTest.toString()); } }
结果:
前置处理 saveUser ======- 执行结果:null 后置处理 org.example.javassit.proxy2.UserDao_$$_jvst840_0@1593948d
3. 反编译查看类
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.example.javassit.proxy2; import java.io.ObjectStreamException; import java.lang.reflect.Method; import javassist.util.proxy.MethodHandler; import javassist.util.proxy.ProxyObject; import javassist.util.proxy.RuntimeSupport; public class UserDao_$$_jvst840_0 extends UserDao implements ProxyObject { public static MethodHandler default_interceptor; private MethodHandler handler; public static byte[] _filter_signature; public static final long serialVersionUID; private static Method[] _methods_; public UserDao_$$_jvst840_0() { this.handler = default_interceptor; if (default_interceptor == null) { this.handler = RuntimeSupport.default_interceptor; } super(); } public final void _d7saveUser() { super.saveUser(); } public final void saveUser() { Method[] var1 = _methods_; this.handler.invoke(this, var1[14], var1[15], new Object[0]); } static { Method[] var0 = new Method[24]; Class var1 = Class.forName("org.example.javassit.proxy2.UserDao_$$_jvst840_0"); RuntimeSupport.find2Methods(var1, "saveUser", "_d7saveUser", 14, "()V", var0); _methods_ = var0; serialVersionUID = -1L; } public void setHandler(MethodHandler var1) { this.handler = var1; } public MethodHandler getHandler() { return this.handler; } Object writeReplace() throws ObjectStreamException { return RuntimeSupport.makeSerializedProxy(this); } }
2. 基于接口
(1) UserDao 接口
package org.example.javassit.proxy2; public interface UserDao { void saveUser(); }
(2) 测试类:
package org.example.javassit.proxy2; import javassist.util.proxy.MethodFilter; import javassist.util.proxy.MethodHandler; import javassist.util.proxy.ProxyFactory; import java.lang.reflect.Method; import java.util.Arrays; public class ProxyTest { public static void main(String[] args) throws Exception { ProxyFactory factory = new ProxyFactory(); // 设置写出的目录会导出到具体的目录 factory.writeDirectory = "D:/proxy"; // 设定接口,接口可以继承多个,所以用数组 factory.setInterfaces(new Class[]{UserDao.class}); //设置过滤器,判断哪些方法调用需要被拦截 factory.setFilter(new MethodFilter() { @Override public boolean isHandled(Method method) { if (method.getName().equals("saveUser")) { return true; } return false; } }); //设置拦截处理 factory.setHandler(new MethodHandler() { @Override public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { System.out.println("前置处理"); System.out.println("self: " + self + "\tthisMethod: " + thisMethod + "\tproceed: " + proceed + "\targs: " + Arrays.toString(args)); System.out.println("后置处理"); return ""; } }); // 创建 UserDao 代理类,并创建代理对象 Class<?> c = factory.createClass(); UserDao javassistTest = (UserDao) c.newInstance(); // saveUser方法,会被拦截 javassistTest.saveUser(); System.out.println(javassistTest.toString()); } }
(3) 结果;
前置处理 self: org.example.javassit.proxy2.UserDao_$$_jvst840_0@1b604f19 thisMethod: public abstract void org.example.javassit.proxy2.UserDao.saveUser() proceed: null args: [] 后置处理 org.example.javassit.proxy2.UserDao_$$_jvst840_0@1b604f19
(4) 反编译查看类
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.example.javassit.proxy2; import java.io.ObjectStreamException; import java.lang.reflect.Method; import javassist.util.proxy.MethodHandler; import javassist.util.proxy.ProxyObject; import javassist.util.proxy.RuntimeSupport; public class UserDao_$$_jvst840_0 implements UserDao, ProxyObject { public static MethodHandler default_interceptor; private MethodHandler handler; public static byte[] _filter_signature; public static final long serialVersionUID; private static Method[] _methods_; public UserDao_$$_jvst840_0() { this.handler = default_interceptor; if (default_interceptor == null) { this.handler = RuntimeSupport.default_interceptor; } super(); } public final void saveUser() { Method[] var1 = _methods_; this.handler.invoke(this, var1[14], var1[15], new Object[0]); } static { Method[] var0 = new Method[24]; Class var1 = Class.forName("org.example.javassit.proxy2.UserDao_$$_jvst840_0"); RuntimeSupport.find2Methods(var1, "saveUser", (String)null, 14, "()V", var0); _methods_ = var0; serialVersionUID = -1L; } public void setHandler(MethodHandler var1) { this.handler = var1; } public MethodHandler getHandler() { return this.handler; } Object writeReplace() throws ObjectStreamException { return RuntimeSupport.makeSerializedProxy(this); } }
==========asm 用法==========
javassit 主要类似于用java源码加反射的机制实现改变一些操作。
asm 主要是针对字节码进行操作, 其主要API包括: ClassReader、 ClassWriter、 ClassVisitor、 FieldVisitor、 MethodVisitor。asm是java的字节码操作框架,可以动态查看类的信息,动态修改,删除,增加类的方法。也可以动态的创建类等信息。这个API类似于字节码操作,需要对一些指令比较熟悉。
asm 结构图如下: https://asm.ow2.io/asm-package-overview.svg
参考: https://asm.ow2.io/developer-guide.html
pom 如下:
<dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>8.0.1</version> </dependency>
1. 简单使用
对一个类增加两个字段, 并且加入get、set 方法, 然后修改其内部的一个方法。
1. cn.qz.myas.AsmUser
package cn.qz.myas; public class AsmUser { private String username; private String password; private Integer age; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public void print() { System.out.println(this.getAge()); } }
2. javap 查看相关指令
D:\study\agentmvn\target\classes>javap -c cn.qz.myas.AsmUser Compiled from "AsmUser.java" public class cn.qz.myas.AsmUser { public cn.qz.myas.AsmUser(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public java.lang.String getUsername(); Code: 0: aload_0 1: getfield #2 // Field username:Ljava/lang/String; 4: areturn public void setUsername(java.lang.String); Code: 0: aload_0 1: aload_1 2: putfield #2 // Field username:Ljava/lang/String; 5: return public java.lang.String getPassword(); Code: 0: aload_0 1: getfield #3 // Field password:Ljava/lang/String; 4: areturn public void setPassword(java.lang.String); Code: 0: aload_0 1: aload_1 2: putfield #3 // Field password:Ljava/lang/String; 5: return public java.lang.Integer getAge(); Code: 0: aload_0 1: getfield #4 // Field age:Ljava/lang/Integer; 4: areturn public void setAge(java.lang.Integer); Code: 0: aload_0 1: aload_1 2: putfield #4 // Field age:Ljava/lang/Integer; 5: return public void print(); Code: 0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: invokevirtual #6 // Method getAge:()Ljava/lang/Integer; 7: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 10: return }
3. 编写ClassVisitor 用于改变相关属性和方法
package cn.qz.myas; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import static org.objectweb.asm.Opcodes.*; public class AsmUserVisitor extends ClassVisitor { public AsmUserVisitor(int i) { super(i); } public AsmUserVisitor(int i, ClassVisitor classVisitor) { super(i, classVisitor); } @Override public FieldVisitor visitField(int i, String s, String s1, String s2, Object o) { System.out.println("cn.qz.myas.LogVisitor.visitField\t" + s); return super.visitField(i, s, s1, s2, o); } @Override public MethodVisitor visitMethod(int i, String s, String s1, String s2, String[] strings) { System.out.println("cn.qz.myas.LogVisitor.visitMethod\t" + s); MethodVisitor methodVisitor = cv.visitMethod(i, s, s1, s2, strings); // 修改print 方法 if (s.equals("print")) { methodVisitor = new MethodVisitor(ASM8, methodVisitor) { @Override public void visitCode() { // print 函数前面先打印username 属性 mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKEVIRTUAL, "cn/qz/myas/AsmUser", "getUsername", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); // 调用原来自带的相关字节码操作 mv.visitCode(); } }; } return methodVisitor; } @Override public void visitEnd() { // 增加私有的address 字段以及相关方法。 // Ljava/lang/String; 这种类型可以用Type.getType(String.class).toString() 生成 FieldVisitor fv = cv.visitField(ACC_PRIVATE, "address", "Ljava/lang/String;", null, null); if (fv != null) { fv.visitEnd(); createSetter("address", "Ljava/lang/String;", AsmUser.class); createGetter("address", "Ljava/lang/String;", AsmUser.class); } // 增加private 的sex 字段以及相关方法 FieldVisitor fv2 = cv.visitField(ACC_PRIVATE, "sex", "Ljava/lang/Integer;", null, null); if (fv2 != null) { fv2.visitEnd(); createSetter("sex", "Ljava/lang/Integer;", AsmUser.class); createGetter("sex", "Ljava/lang/Integer;", AsmUser.class); } super.visitEnd(); } void createSetter(String propertyName, String type, Class c) { String methodName = "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, methodName, "(" + type + ")V", null, null); // 获取到this mv.visitVarInsn(ALOAD, 0); // 参数 mv.visitVarInsn(Type.getType(c).getOpcode(ILOAD), 1); // 设置值。 注意第二个参数需要是Type.getType(c).getInternalName(), 也就是格式需要是cn/qz/PlainTest mv.visitFieldInsn(PUTFIELD, Type.getType(c).getInternalName(), propertyName, type); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); } void createGetter(String propertyName, String returnType, Class c) { String methodName = "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, methodName, "()" + returnType, null, null); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, Type.getType(c).getInternalName(), propertyName, returnType); mv.visitInsn(Type.getType(c).getOpcode(IRETURN)); mv.visitMaxs(0, 0); } }
4. cn.qz.myas.Client 调用上面ClassVisitor 进行操作, 并将重写后的类重新生成到原class 文件
package cn.qz.myas; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; public class Client { private final static String CLASS_FILE_SUFFIX = ".class"; public static void main(String[] args) throws Exception { Class clazz = AsmUser.class; // 使用 ClassReader 去读取类的字节码信息。 ClassReader cr = new ClassReader(clazz.getName()); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassVisitor classVisitor = new AsmUserVisitor(Opcodes.ASM8, cw); cr.accept(classVisitor, ClassReader.EXPAND_FRAMES); // 生成到本地 byte[] bys = cw.toByteArray(); OutputStream os = null; os = new FileOutputStream(getFile(clazz)); os.write(bys); os.flush(); os.close(); } private static File getFile(Class clazz) { StringBuffer sb = new StringBuffer(); sb.append(clazz.getResource("/")) .append(clazz.getCanonicalName().replace(".", File.separator)) .append(CLASS_FILE_SUFFIX); return new File(sb.substring(6)); } }
5. 重新反汇编查看指令 (也可以用IDEA 自带的Jclasslib 进行查看)
D:\study\agentmvn\target\classes>javap -c cn.qz.myas.AsmUser Compiled from "AsmUser.java" public class cn.qz.myas.AsmUser { public cn.qz.myas.AsmUser(); Code: 0: aload_0 1: invokespecial #14 // Method java/lang/Object."<init>":()V 4: return public java.lang.String getUsername(); Code: 0: aload_0 1: getfield #20 // Field username:Ljava/lang/String; 4: areturn public void setUsername(java.lang.String); Code: 0: aload_0 1: aload_1 2: putfield #20 // Field username:Ljava/lang/String; 5: return public java.lang.String getPassword(); Code: 0: aload_0 1: getfield #25 // Field password:Ljava/lang/String; 4: areturn public void setPassword(java.lang.String); Code: 0: aload_0 1: aload_1 2: putfield #25 // Field password:Ljava/lang/String; 5: return public java.lang.Integer getAge(); Code: 0: aload_0 1: getfield #30 // Field age:Ljava/lang/Integer; 4: areturn public void setAge(java.lang.Integer); Code: 0: aload_0 1: aload_1 2: putfield #30 // Field age:Ljava/lang/Integer; 5: return public void print(); Code: 0: getstatic #39 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: invokevirtual #41 // Method getUsername:()Ljava/lang/String; 7: invokevirtual #46 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 10: getstatic #39 // Field java/lang/System.out:Ljava/io/PrintStream; 13: aload_0 14: invokevirtual #48 // Method getAge:()Ljava/lang/Integer; 17: invokevirtual #51 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 20: return public void setAddress(java.lang.String); Code: 0: aload_0 1: aload_1 2: putfield #55 // Field address:Ljava/lang/String; 5: return public java.lang.String getAddress(); Code: 0: aload_0 1: getfield #55 // Field address:Ljava/lang/String; 4: areturn public void setSex(java.lang.Integer); Code: 0: aload_0 1: aload_1 2: putfield #60 // Field sex:Ljava/lang/Integer; 5: return public java.lang.Integer getSex(); Code: 0: aload_0 1: getfield #60 // Field sex:Ljava/lang/Integer; 4: areturn }
6. 使用IDEA 查看生成的class 信息
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package cn.qz.myas; public class AsmUser { private String username; private String password; private Integer age; private String address; private Integer sex; public AsmUser() { } public String getUsername() { return this.username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return this.password; } public void setPassword(String password) { this.password = password; } public Integer getAge() { return this.age; } public void setAge(Integer age) { this.age = age; } public void print() { System.out.println(this.getUsername()); System.out.println(this.getAge()); } public void setAddress(String var1) { this.address = var1; } public String getAddress() { return this.address; } public void setSex(Integer var1) { this.sex = var1; } public Integer getSex() { return this.sex; } }
7. 建立测试类进行测试:
package cn.qz; import cn.qz.myas.AsmUser; import java.io.IOException; public class PlainTest { public static void main(String[] args) throws InterruptedException, IOException { AsmUser asmUser = new AsmUser(); asmUser.setAge(111); asmUser.setUsername("testusername"); asmUser.print(); } }
结果:
testusername
111
2. Asm 动态的生成一个类
生成一个类且继承上面的AsmUser, 实现一个接口IInterface1。并且生成之后动态的加载到JVM 中,然后反射创建一个对象后进行调用。
1. IInterface1 接口
package cn.qz.myas; public interface IInterface1 { void test1(); }
2. 我们先建立一个cn.qz.myas.SubAsmUser2 (用于指令参考)
源码如下:
package cn.qz.myas; public class SubAsmUser2 extends AsmUser implements IInterface1 { public SubAsmUser2() { } public void test1() { System.out.println("hello test1!!!"); } }
反汇编查看字节码指令:
D:\study\agentmvn\target\classes>javap -c cn.qz.myas.SubAsmUser2 Compiled from "SubAsmUser2.java" public class cn.qz.myas.SubAsmUser2 extends cn.qz.myas.AsmUser implements cn.qz.myas.IInterface1 { public cn.qz.myas.SubAsmUser2(); Code: 0: aload_0 1: invokespecial #1 // Method cn/qz/myas/AsmUser."<init>":()V 4: return public void test1(); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String hello test1!!! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return }
3. 新建client 类做处理
package cn.qz.myas; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import java.lang.reflect.Method; import static org.objectweb.asm.Opcodes.*; public class Client { private final static String CLASS_FILE_SUFFIX = ".class"; public static void main(String[] args) throws Exception { String classAbsoluteName = "cn.qz.myas.SubAsmUser"; String classAbsoluteNameSeprator = "cn/qz/myas/SubAsmUser"; String superClass = convertClassName(AsmUser.class.getName()); String[] interfaces = new String[]{convertClassName(IInterface1.class.getName())}; ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); // Opcodes.V1_8 指定类的版本 // Opcodes.ACC_PUBLIC 表示这个类是public, // cn/qz/myas/SubAsmUser 类的全限定名称 // 第一个null位置变量定义的是泛型签名, // AsmUser.class.getName() 这个类的父类 // 最后一个参数是实现的接口 cw.visit(V1_8, ACC_PUBLIC, classAbsoluteNameSeprator, null, superClass, interfaces); ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM8, cw) { @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { System.out.println("org.objectweb.asm.ClassVisitor.visitMethod\t" + name); MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions); // 完成 <init> 的代码逻辑 if ("<init>".equals(name)) { methodVisitor = new MethodVisitor(ASM8, methodVisitor) { @Override public void visitCode() { mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, superClass, "<init>", "()V", false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); } }; } // 完成 test1 的代码逻辑 if ("test1".equals(name)) { methodVisitor = new MethodVisitor(ASM8, methodVisitor) { @Override public void visitCode() { mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("hello test1!!!"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); } }; } return methodVisitor; } }; // 增加init 对象实例化方法(必须加) classVisitor.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null).visitCode(); // 构造test1() 方法 classVisitor.visitMethod(ACC_PUBLIC, "test1", "()V", null, null).visitCode(); classVisitor.visitEnd(); byte[] code = cw.toByteArray(); // 生成到本地 OutputStream os = null; try { os = new FileOutputStream(new File("D:/agentjar/SubAsmUser.class")); os.write(code); os.flush(); os.close(); } catch (Exception e) { // ignore } Class<?> exampleClass = loadClass(classAbsoluteName, code); for (Method method : exampleClass.getMethods()) { System.out.println("exampleClass:\t" + exampleClass + "\tmethod: " + method.getName()); } // 反射调用方法 exampleClass.getMethod("test1").invoke(exampleClass.newInstance(), null); } private static Class loadClass(String className, byte[] b) { Class clazz = null; try { ClassLoader loader = ClassLoader.getSystemClassLoader(); Class cls = Class.forName("java.lang.ClassLoader"); java.lang.reflect.Method method = cls.getDeclaredMethod( "defineClass", new Class[]{String.class, byte[].class, int.class, int.class}); // Protected method invocation. method.setAccessible(true); try { Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length)}; clazz = (Class) method.invoke(loader, args); } finally { method.setAccessible(false); } } catch (Exception e) { e.printStackTrace(); System.exit(1); } return clazz; } private static String convertClassName(String className) { return className.replace(".", "/"); } }
结果:
org.objectweb.asm.ClassVisitor.visitMethod <init> org.objectweb.asm.ClassVisitor.visitMethod test1 exampleClass: class cn.qz.myas.SubAsmUser method: test1 exampleClass: class cn.qz.myas.SubAsmUser method: print exampleClass: class cn.qz.myas.SubAsmUser method: setAge exampleClass: class cn.qz.myas.SubAsmUser method: setPassword exampleClass: class cn.qz.myas.SubAsmUser method: getUsername exampleClass: class cn.qz.myas.SubAsmUser method: setUsername exampleClass: class cn.qz.myas.SubAsmUser method: getPassword exampleClass: class cn.qz.myas.SubAsmUser method: getAge exampleClass: class cn.qz.myas.SubAsmUser method: wait exampleClass: class cn.qz.myas.SubAsmUser method: wait exampleClass: class cn.qz.myas.SubAsmUser method: wait exampleClass: class cn.qz.myas.SubAsmUser method: equals exampleClass: class cn.qz.myas.SubAsmUser method: toString exampleClass: class cn.qz.myas.SubAsmUser method: hashCode exampleClass: class cn.qz.myas.SubAsmUser method: getClass exampleClass: class cn.qz.myas.SubAsmUser method: notify exampleClass: class cn.qz.myas.SubAsmUser method: notifyAll hello test1!!!
(1) IDEA 查看生成的class 信息
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package cn.qz.myas; public class SubAsmUser extends AsmUser implements IInterface1 { public SubAsmUser() { } public void test1() { System.out.println("hello test1!!!"); } }
(2) javap 查看字节码指令
D:\study\agentmvn\target\classes>javap -c cn.qz.myas.SubAsmUser public class cn.qz.myas.SubAsmUser extends cn.qz.myas.AsmUser implements cn.qz.myas.IInterface1 { public cn.qz.myas.SubAsmUser(); Code: 0: aload_0 1: invokespecial #10 // Method cn/qz/myas/AsmUser."<init>":()V 4: return public void test1(); Code: 0: getstatic #17 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #19 // String hello test1!!! 5: invokevirtual #25 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return }
至此简单研究了下javassit 和 asm 的使用。javassit 实现比较简单,是基于源码级别的API, 其封装的一些API也比较方便的使用。ASM是基于字节码的API, 如果改造一个类可能需要对字节码指令有些了解。
两者都可以实现对class 文件进行修改,一个是直接修改class, 一个相当于从源码级别修改。也都是在运行之前进行修改, 如果运行中修改。 我理解需要结合Java5 提供的javaagent 技术(Java 5 中提供的 Instrument 包), 在一个类加载前调用相关的钩子进行改造; 或者JDK6 提供的agentmain 和 Attach 机制在运行期间动态的替换(JVM 启动了一个AttachListener 线程用于进行一些处理)。
asm 强大的字节码操作API被广泛使用,看cglib 的时候其就是依赖asm。例如一个项目依赖树如下:
[INFO] | +- cglib:cglib:jar:3.1:compile [INFO] | | \- org.ow2.asm:asm:jar:4.2:compile [INFO] | \- org.ehcache:ehcache:jar:3.8.1:compile [INFO] | \- org.glassfish.jaxb:jaxb-runtime:jar:2.3.3:compile [INFO] | +- org.glassfish.jaxb:txw2:jar:2.3.3:compile [INFO] | +- com.sun.istack:istack-commons-runtime:jar:3.0.11:compile [INFO] | \- com.sun.activation:jakarta.activation:jar:1.2.2:runtime
javaagent 和 agentmain 使用参考: https://www.cnblogs.com/qlqwjy/p/15639710.html
补充: 对于非静态方法,第一个参数传递的都是this 对象, 对于静态方法,则第一个参数就是实参列表的第一个值, 也就是对象方法aload_0 代表this 对象, 静态方法aload_0 代表第一个参数
1. 测试类
package cn.qz; public class Client { public static void main(String[] args) { System.out.println(new Client().test1("3", "2")); } public String test1(String test1, String param2) { System.out.println(this); System.out.println(test1); System.out.println(param2); return "123"; } public static String test2(String test1, String param2) { System.out.println(test1); System.out.println(param2); return "123"; } }
2. 反汇编查看相关指令
D:\study\agentmvn\target\classes>javap -c cn.qz.Client Compiled from "Client.java" public class cn.qz.Client { public cn.qz.Client(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #3 // class cn/qz/Client 6: dup 7: invokespecial #4 // Method "<init>":()V 10: ldc #5 // String 3 12: ldc #6 // String 2 14: invokevirtual #7 // Method test1:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; 17: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 20: return public java.lang.String test1(java.lang.String, java.lang.String); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 10: aload_1 11: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 14: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 17: aload_2 18: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 21: ldc #10 // String 123 23: areturn public static java.lang.String test2(java.lang.String, java.lang.String); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 10: aload_1 11: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 14: ldc #10 // String 123 16: areturn }