• 动态生成类--javassist的使用


    javassist简介

    Javassist是⼀个开源的分析、编辑和创建Java字节码的类库。是由东京⼯业⼤学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加⼊了开放源代码JBoss 应⽤服务器项⽬,通过使⽤
    Javassist对字节码操作为JBoss实现动态"AOP"框架。
    Javassist是可以动态编辑Java字节码的类库。它可以在Java程序运行时定义一个新的类,并加载到JVM中;还可以在JVM加载时修改一个类文件。Javassist不必关心字节码相关的规范也是可以编辑类文件的。

    javassist使用

    在Javassist中每个需要编辑的class都对应一个CtCLass实例,CtClass的含义是编译时的类(compile time class),这些类会存储在Class Pool中(Class poll是一个存储CtClass对象的容器)。

    环境配置

    为了减少演示的复杂度,我们都在Maven项目下进行,因为我们可以直接引入依赖就可以达到我们导包的目的,很方便,不用再去下载jar包,然后自己手动导入了。
    导入依赖:版本号可以使用别的

    	<dependency>
    	 <groupId>org.javassist</groupId>
    	 <artifactId>javassist</artifactId>
    	 <version>3.29.1-GA</version>
    	</dependency>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.13.2</version>
          <scope>test</scope>
        </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    使用javassist创建方法

    步骤
    1.获取类池
    2.制造类
    3.制造方法
    4.将方法添加到类中
    5.在内存中生成类,同时生成加载到JVM中

    测试的时候可以使用反射机制创建对象

    	@Test
        public void testGenerateFirstClass() throws Exception {
            //1.获取类池
            ClassPool pool = ClassPool.getDefault();
            //2.制造类
            CtClass ctClass = pool.makeClass("com.javassist.bank.dao.impl.AccountDaoImpl");
            //3.制造方法
            CtMethod ctMethod = CtMethod.make("public void insert(){System.out.println(123);}", ctClass);
            //4.将方法添加到类中
            ctClass.addMethod(ctMethod);
            //5.在内存中生成类,同时生成加载到JVM中
            ctClass.toClass();
    
    
            //反射机制创建对象,调方法
            Class<?> clazz = Class.forName("com.javassist.bank.dao.impl.AccountDaoImpl");
            //创建对象
            Object o = clazz.newInstance();
            Method insertMethod = clazz.getDeclaredMethod("insert");
            insertMethod.invoke(o);
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    注意:运行的时候会出现InaccessibleObjectException,原因是jdk8版本之后有了java.base这个包,jdk8版本之前没有,导致找不到。
    需要加这两个参数
    –add-opens java.base/java.lang=ALL-UNNAMED
    –add-opens java.base/sun.net.util=ALL-UNNAMED
    请添加图片描述
    注意配置完后是点击这个绿色小箭头运行,点击别的会新建一个junit
    请添加图片描述
    运行结果请添加图片描述

    使用javassist和已有接口创建实现方法

    创建一个接口

    public interface AccountDao {
        void delete();
    }
    
    • 1
    • 2
    • 3

    步骤
    1.获取类池
    2.制造类
    3.制造接口
    4.添加接口到类中(实现接口)
    5.制造方法
    6.实现接口里面的方法,将方法添加到类中
    7.在内存中生成类,同时生成加载到JVM中。

        @Test
        public void testGenerateImpl() throws Exception {
            //1.获取类池
            ClassPool pool = ClassPool.getDefault();
            //2.制造类
            CtClass ctClass = pool.makeClass("com.javassist.bank.dao.impl.AccountDaoImpl");
            //3.制造接口
            CtClass ctInterface = pool.makeInterface("com.javassist.bank.dao.AccountDao");
            //4.添加接口到类中(实现接口)
            ctClass.addInterface(ctInterface);//相当于AccountDaoImpl implements AccountDao
            //5.制造方法
            CtMethod ctMethod = CtMethod.make("public void delete(){System.out.println(\"Hello detele\");}", ctClass);
            //6.实现接口里面的方法,将方法添加到类中
            ctClass.addMethod(ctMethod);
            //7.在内存中生成类,同时生成加载到JVM中,这里会返回一个class对象
            Class<?> clazz = ctClass.toClass();
    
            //8.创建对象并调用
            AccountDao accountDao = ((AccountDao) clazz.newInstance());
            accountDao.delete();
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    运行结果
    请添加图片描述

    动态创建实现方法

    以上是知道接口有哪个方法,而javassist是可以进行动态创建的,我们需要进行动态创建实现方法
    接口:

    public interface AccountDao {
        void delete();
        int insert(String actno);
        int update(String actno,Double balance);
        String selectByActno(String actno);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    步骤

    • 1.获取类池
    • 2.制造类
    • 3.制造接口
    • 4.添加接口到类中(实现接口)
    • 5、获取接口中所有的方法
    • 6.通过遍历循环动态制造方法,并将方法添加到类中
      • 6.1通过StringBuffer动态制造方法
      • 6.2通过循环遍历动态获取参数类型
      • 6.3动态添加return语句
    • 7.在内存中生成类,同时生成加载到JVM中
        @Test
        public void testGenerateAccountDaoImpl() throws Exception {
            //1.获取类池
            ClassPool pool = ClassPool.getDefault();
            //2.制造类
            CtClass ctClass = pool.makeClass("com.javassist.bank.dao.impl.AccountDaoImpl");
            //3.制造接口
            CtClass ctInterface = pool.makeInterface("com.javassist.bank.dao.AccountDao");
            //4.添加接口到类中(实现接口)
            ctClass.addInterface(ctInterface);//相当于AccountDaoImpl implements AccountDao
            //5、获取接口中所有的方法
            Method[] methods = AccountDao.class.getDeclaredMethods();
            //6.通过遍历循环动态制造方法,并将方法添加到类中
            Arrays.stream(methods).forEach(method -> {
                try {
                    //6.1通过StringBuffer动态制造方法
                    StringBuffer methodCode = new StringBuffer();
                    methodCode.append("public ");//接口一定是public
                    methodCode.append(method.getReturnType().getName() + " ");//追加返回值类型
                    methodCode.append(method.getName());//追加方法名
                    methodCode.append("(");
                    //6.2通过循环遍历动态获取参数类型
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    for (int i = 0; i < parameterTypes.length; i++) {
                        Class<?> parameterType = parameterTypes[i];
                        methodCode.append(parameterType.getName() + " ");//追加参数类型
                        methodCode.append("arg" + i);//追加参数名
                        if (i != parameterTypes.length-1) {
                            methodCode.append(",");
                        }
                    }
                    methodCode.append(")");
                    methodCode.append("{");
                    methodCode.append("System.out.println(321);");
                    //6.3动态添加return语句
                    String returnSimpleName = method.getReturnType().getSimpleName();//获取返回值类型的简称,因为getReturnType()是带包名的
                    if ("void".equals(returnSimpleName)) {
    
                    }else if ("int".equals(returnSimpleName)){
                        methodCode.append("return 1;");
                    }else if ("String".equals(returnSimpleName)){
                        methodCode.append("return \"Hello\";");
                    }
                    methodCode.append("}");
                    System.out.println(methodCode);
                    CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
                    ctClass.addMethod(ctMethod);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            //7.在内存中生成类,同时生成加载到JVM中,这里会返回一个class对象
            Class<?> clazz = ctClass.toClass();
            //8.创建对象并调用
            AccountDao accountDao = ((AccountDao) clazz.newInstance());
            accountDao.insert("aaa");
            accountDao.delete();
            accountDao.selectByActno("aaaa");
            accountDao.update("aaa",20.6);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    运行结果请添加图片描述

  • 相关阅读:
    linuex服务器中如何安装mysql数据库(一次性完成,包含远程连接)
    122.Thrift和spark-sql客户端部署
    _cpp AVL树(map、set等关联式容器的底层结构)
    快速部署OpenStack全新UI管理Skyline Dashboard
    详解大数据、数据存储和边缘计算技术在元宇宙中的应用
    下载安装nvm,使用nvm管理node.js版本
    【Java】PAT Basic Level 1024 科学计数法
    2021年中国研究生数学建模竞赛A题(华为公司命题)——相关矩阵组的低复杂度计算和存储建模
    java获取两个日期之间的所有日期
    【JavaSe】断言 assert 到底怎么用?
  • 原文地址:https://blog.csdn.net/weixin_45832694/article/details/127570359