======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
补充: 对于非静态方法,第一个参数传递的都是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
}