Javassist简介

Wesley13
• 阅读 763

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。它可以用来检查、”动态”修改以及创建 Java类。其功能与jdk自带的反射功能类似,但比反射功能更强大。

package com.javassis.test;

public class CoolGuy {
    private String hotGirlfriendName;
    private String niceCarModel;
    public String getHotGirlfriendName() {
        return hotGirlfriendName;
    }
    public void setHotGirlfriendName(String hotGirlfriendName) {
        this.hotGirlfriendName = hotGirlfriendName;
    }
    public String getNiceCarModel() {
        return niceCarModel;
    }
    public void setNiceCarModel(String niceCarModel) {
        this.niceCarModel = niceCarModel;
    }
    
}

public class Person {
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
}

package com.javassis.test;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Loader;
import javassist.NotFoundException;

public class Main {
    public static void main(String[] args) throws NotFoundException, NoSuchMethodException, SecurityException,
            InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
            CannotCompileException, IOException, ClassNotFoundException {
        ClassPool classPool = ClassPool.getDefault();
        /**
         * CoolGuy是已经存在的类
         */
        CtClass ctc = classPool.get("com.javassis.test.CoolGuy");
        Class<?> c = ctc.toClass();
        Object cg = (Object) c.newInstance();
        Method m = c.getDeclaredMethod("setHotGirlfriendName", String.class);
        m.invoke(cg, "柳岩");
        System.out.println("CoolGuy's girlfriend is " + ((CoolGuy) cg).getHotGirlfriendName());
        /**
         * 动态构造一个SuckGuy类
         */
        CtClass suckCt = classPool.makeClass("com.javassis.test.SuckGuy");
        // 添加域
        CtField girlfriendField = new CtField(classPool.get("java.lang.String"), "hotGirlfriendName", suckCt);
        CtField carField = new CtField(classPool.get("java.lang.String"), "niceCarModel", suckCt);
        suckCt.addField(girlfriendField);
        suckCt.addField(carField);
        // 添加方法
        CtMethod getMethod = CtNewMethod.make("public String getHotGirlfriendName() { return this.hotGirlfriendName;}",
                suckCt);
        CtMethod setMethod = CtNewMethod
                .make("public void setHotGirlfriendName(String girl) { this.hotGirlfriendName = girl;}", suckCt);
        suckCt.addMethod(getMethod);
        suckCt.addMethod(setMethod);
        getMethod = CtNewMethod.make("public String getNiceCarModel() { return this.niceCarModel;}", suckCt);
        setMethod = CtNewMethod
                .make("public void setNiceCarModel(String niceCarModel) { this.niceCarModel = niceCarModel;}", suckCt);
        suckCt.addMethod(getMethod);
        suckCt.addMethod(setMethod);
        Class<?> cuckClass = suckCt.toClass();
        Object suckGuy = cuckClass.newInstance();
        m = cuckClass.getDeclaredMethod("setHotGirlfriendName", String.class);
        m.invoke(suckGuy, "凤姐");
        String suckGirlfriend = (String) cuckClass.getMethod("getHotGirlfriendName", null).invoke(suckGuy, null);
        System.out.println("SuckGuy's girlfriend is " + suckGirlfriend);
        /**
         * 设置CoolGuy父类为Person
         */
        //ctc.writeFile();
        ctc.defrost(); // 先解冻,不然会报class is frozen异常,
        // CtClass对象通过writeFile()、toClass()、toBytecode()转化为Class后,Javassist冻结了CtClass对象,因此,JVM不允许再次加载Class文件,所以不允许对其修改
        ctc.setSuperclass(classPool.get("com.javassis.test.Person"));
        Loader cl = new Loader(classPool);// 同一个classloader下不能加载两次class,所以新new一个loader
        // c = ctc.toClass();
        c = cl.loadClass("com.javassis.test.CoolGuy");
        cg = (Object) c.newInstance();
        m = c.getMethod("setAge", int.class);
        m.invoke(cg, 25);
        m = c.getMethod("getAge", null);
        System.out.println("CoolGuy's age is " + m.invoke(cg, null));
    }
}

 输出:

CoolGuy's girlfriend is 柳岩
SuckGuy's girlfriend is 凤姐
CoolGuy's age is 25

几个需要注意的地方:

1、笔者用的Javassist版本是3.22.0-GA,比较坑爹的是它必须使用java 9,笔者一开始用java 8会报java.lang.NoClassDefFoundError: java/lang/StackWalker$Option

2、CtClass对象通过writeFile()、toClass()、toBytecode()转化为Class后,Javassist冻结了CtClass对象,因此,JVM不允许再次加载Class文件,所以不允许对其修改

,再次对其修改时需要defrost() 先解冻,不然会报class is frozen异常。

3、解冻后对类进行修改,之后使用修改后的类需要再次加载,此时虽然已经经过defrost() ,但同一个classloader下不能加载两次class,再次使用toClass()会报错,所以新new一个loader,使用loader.loadClass()完成Class加载。

接下来看看对如何动态的去修改类的方法:

思路就是把需要修改的方法改个新名字,然后新建一个新方法,名字命名为原名字,新方法copy原方法的内容并加入新的代码。

package com.javassis.test;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;

/*
 * 动态修改CoolGuy的一个方法
 * */
public class UpdateTest {
    public static void main(String[] args) throws Exception {
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctc = classPool.get("com.javassis.test.CoolGuy");
        String mName = "getHotGirlfriendName";
        CtMethod m = ctc.getDeclaredMethod(mName);
        m.setName(mName + "$old");
        CtMethod nm = CtNewMethod.copy(m, mName, ctc, null);
        StringBuffer mbody = new StringBuffer();
        mbody.append("{");
        mbody.append("\n return hotGirlfriendName");
        mbody.append(" + \"和鞠婧祎\"; \n");
        mbody.append("}");
        nm.setBody(mbody.toString());
        ctc.addMethod(nm);
        Class<?> c = ctc.toClass();
        CoolGuy coolGuy = (CoolGuy)c.newInstance();
        coolGuy.setHotGirlfriendName("柳岩");
        System.out.println("CoolGuy's girlfriend is " + coolGuy.getHotGirlfriendName());
    }
}

运行结果:

CoolGuy's girlfriend is 柳岩和鞠婧祎

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
2年前
一篇文章弄懂 Java 反射的使用
说到Java反射,必须先把Java的字节码搞明白了,也就是Class,大Class在之前的文章中,我们知道了Java的大Class就是类的字节码,就是一个普通的类,里面保存的是类的信息,还不太明白Java的大Class的,可以先看一下之前的文章先想一个问题1.给我们一个类,我们如何使用?这还不简单,通过这个类,创建一个类的对象,再通过这个
Wesley13 Wesley13
2年前
java基础知识随身记
2018年11月12日20:51:35一、基础知识:1、JVM、JRE和JDK的区别:JVM(JavaVirtualMachine):java虚拟机,用于保证java的跨平台的特性。  java语言是跨平台,jvm不是跨平台的。JRE(JavaRuntimeEnvironment):java的运行环境,包括jvmjava的核心类
Wesley13 Wesley13
2年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Easter79 Easter79
2年前
Spring的两种代理JDK和CGLIB的区别浅谈
一、原理区别:java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 2、如果目标对象实现了接口,可以
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Easter79 Easter79
2年前
Spring的两种动态代理:Jdk和Cglib 的区别和实现
一、原理区别:java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 2、如果目标对象实现了接口,可以
Wesley13 Wesley13
2年前
Java基础之反射(非常重要)
反射是框架设计的灵魂(使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码))一、反射的概述JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法
Wesley13 Wesley13
2年前
Java动态代理机制解析
动态代理是指在运行时动态生成代理类。不需要我们像静态代理那个去手动写一个个的代理类。生成动态代理类有很多方式:Java动态代理,CGLIB,Javassist,ASM库等。这里主要说一下Java动态代理的实现。Java动态代理InvocationHandler接口Java动态代理中,每一个
京东云开发者 京东云开发者
2个月前
打开java语言世界通往字节码世界的大门——ASM字节码操作类库
一、ASM介绍1、ASM是什么ASM是一个通用的Java字节码操作和分析框架。它可以用于修改现有类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,可以从中构建定制的复杂转换和代码分析工具。ASM提供了与其他Java字节码框架类似的