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
}

 

 

 

posted @ 2021-09-01 22:06  QiaoZhi  阅读(1604)  评论(0编辑  收藏  举报