0%

使用javassist生成类

  • 简单介绍:

    • Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库。

    • 关于 java 字节码的处理,目前有很多工具,如 bcel,asm,不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用 javassist

      javassist 是 jboss 的一个子项目,其主要的优点,在于简单,而且快速。直接使用 java 编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。简而言之:Javassist 能够转换现有类的基本内容,或创建一个新类。

    • 装载到类池中的类由 javassist.CtClass 实例表示。与标准的 java.lang.Class 类一样, CtClass 提供了检查类数据(如字段和方法)的方法。不过,这只是 CtClass 的部分内容,它还定义了在类中添加新字段、方法和构造函数、以及改变类、父类和接口的方法。奇怪的是,Javassist 没有提供删除一个类中字段、方法或者构造函数的任何方法。

      • 字段、方法和构造函数分别由 javassist.CtFieldjavassist.CtMethodjavassist.CtConstructor 的实例表示。这些类定义了修改由它们所表示的对象的所有方法的方法,包括方法或者构造函数中的实际字节码内容。

    总结:反射可以使用字节码文件里面的信息,javassist 可以根据字节码信息动态的生成自己所需要的类

七、使用javassist生成类

7.1 javassist简单介绍

  • 简单介绍:

    • Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库。

    • 关于 java 字节码的处理,目前有很多工具,如 bcel,asm,不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用 javassist

      javassist 是 jboss 的一个子项目,其主要的优点,在于简单,而且快速。直接使用 java 编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。简而言之:Javassist 能够转换现有类的基本内容,或创建一个新类。

    • 装载到类池中的类由 javassist.CtClass 实例表示。与标准的 java.lang.Class 类一样, CtClass 提供了检查类数据(如字段和方法)的方法。不过,这只是 CtClass 的部分内容,它还定义了在类中添加新字段、方法和构造函数、以及改变类、父类和接口的方法。奇怪的是,Javassist 没有提供删除一个类中字段、方法或者构造函数的任何方法。

      • 字段、方法和构造函数分别由 javassist.CtFieldjavassist.CtMethodjavassist.CtConstructor 的实例表示。这些类定义了修改由它们所表示的对象的所有方法的方法,包括方法或者构造函数中的实际字节码内容。

    总结:反射可以使用字节码文件里面的信息,javassist 可以根据字节码信息动态的生成自己所需要的类

  • 直接看别人的笔记吧:

    老杜MyBatis–自己补充 (yuque.com)

  • 也可以看视频:P59 - P64

★★★7.2 使用javassist动态生成DAO接口的实现类

  • 非常精彩,如果不记得了可以再看看 P59 - P64

  • 通过 javassist,我们来动态生成实现了 DAO 接口的类。

    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
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    package com.f.bank.utils;

    import org.apache.ibatis.javassist.ClassPool;
    import org.apache.ibatis.javassist.CtClass;
    import org.apache.ibatis.javassist.CtMethod;
    import org.apache.ibatis.mapping.SqlCommandType;
    import org.apache.ibatis.session.SqlSession;

    import java.lang.reflect.Method;
    import java.util.Arrays;

    /**
    * 工具类:可以动态的生成DAO的实现类(或者说可以动态的生成DAO的代理类)
    * 注意注意注意注意注意!!!!!!:
    * 凡是使用GenerateDaoProxy的,SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。
    *
    * @author fzy
    * @date 2024/1/6 21:29
    */
    public class GenerateDaoProxy {
    /**
    * 生成dao接口的实现类,并且将实现类的对象创建出来并返回
    *
    * @param daoInterface 要实现的接口
    * @return 接口的动态实现类的实例对象
    */
    public static Object generate(SqlSession sqlSession, Class daoInterface) {
    // 获取类池
    ClassPool pool = ClassPool.getDefault();
    // 制造类
    // CtClass ctClass = pool.makeClass("dao实现类的类名");
    CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy"); // 实际本质上就是在内存中动态生成一个代理类
    // 制造接口
    CtClass ctInterface = pool.makeClass(daoInterface.getName());
    // 实现接口
    ctClass.addInterface(ctInterface);
    // 实现接口中的所有方法
    Method[] methods = daoInterface.getDeclaredMethods();
    Arrays.stream(methods).forEach(method -> {
    try {
    // method是接口中的抽象方法
    // 将method这个抽象方法进行实现
    StringBuilder methodCode = new StringBuilder();
    methodCode.append("public ");
    methodCode.append(method.getReturnType().getName());
    methodCode.append(" ");
    methodCode.append(method.getName());
    methodCode.append("(");
    // 方法的形式参数列表
    Class<?>[] parameterTypes = method.getParameterTypes();
    for (int i = 0; i < parameterTypes.length; i++) {
    Class<?> parameterType = parameterTypes[i];
    methodCode.append(parameterType.getName());
    methodCode.append(" ");
    methodCode.append("arg" + i);
    if (i != parameterTypes.length - 1) {
    methodCode.append(", ");
    }
    }
    methodCode.append(")");
    methodCode.append("{");
    // 方法体中的代码
    // 类名需要为全限定名称,不然mybatis不能确定到具体某个类
    methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.f.bank.utils.SqlSessionUtil.openSession();");
    // 需要知道是什么类型的sql语句
    // sql语句的id是框架使用者自己定义的,具有多变性。对于我框架开发者来说,我不知道
    // 既然我框架开发者不知道sqlId,怎么办呢?
    // mybatis框架的开发者于是就出台了一个规定:凡是使用GenerateDaoProxy机制的,sqlId都不能随便写。namespace必须是dao接口的全限定名称,id必须是dao接口中方法名。
    String sqlId = daoInterface.getName() + "." + method.getName();
    SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
    switch (sqlCommandType) {
    case INSERT:
    break;
    case DELETE:
    break;
    case UPDATE:
    methodCode.append("return sqlSession.update(\"" + sqlId + "\", arg0);");
    break;
    case SELECT:
    String returnType = method.getReturnType().getName();
    methodCode.append("return (" + returnType + ") sqlSession.selectOne(\"" + sqlId + "\", arg0);");
    break;
    default:
    }
    methodCode.append("}");
    System.out.println(methodCode);
    //CtMethod ctMethod = CtMethod.make("方法代码片段", "拥有该方法的类")
    CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
    // 将制造的方法添加到制造的类中
    ctClass.addMethod(ctMethod);
    } catch (Exception e) {
    e.printStackTrace();
    }
    });
    // 创建对象
    Object obj = null;
    try {
    Class<?> clazz = ctClass.toClass();
    obj = clazz.newInstance();
    } catch (Exception e) {
    e.printStackTrace();
    }
    // 返回动态生成的类的对象
    return obj;
    }
    }

    注意:在 Mybatis 中规定,如果你只写了一个接口,你接口的实现类不想写了,你想让 Mybatis 框架给你动态生成,实现类中的方法的代码也想让 Mybatis 框架给你动态生成,可以,但有条件

    • 条件就是:你的 sqlId 不能随便写。namespace 必须是你的 dao 接口的全限定名称,id 必须是你的 dao 接口中的方法名。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
      PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <!--要想使用这种机制,namespace必须是dao接口的全限定名称-->
      <mapper namespace="com.f.bank.dao.AccountDao">
      <!--要想使用这种机制,id必须是dao接口中的方法名-->
      <select id="selectByActno" resultType="com.f.bank.pojo.Account">
      SELECT * FROM t_act WHERE actno = #{actno}
      </select>
      <update id="updateAct">
      UPDATE t_act SET balance = #{balance} WHERE actno = #{actno}
      </update>
      </mapper>
      1
      2
      3
      4
      5
      6
      7
      8
      package com.f.bank.dao;

      import com.f.bank.pojo.Account;

      public interface AccountDao {
      Account selectByActno(String actno);
      int updateAct(Account act);
      }
  • 好消息是,以上动态生成接口实现类的内容,mybatis 内部已经实现了。直接调用以下代码即可获取 dao 接口的代理类:

    1
    2
    // 使用mybatis获取dao接口代理类对象
    private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class AccountServiceImpl implements AccountService {
    //private AccountDao accountDao = new AccountDaoImpl();
    // 使用GenerateDaoProxy得到动态生成类的实例对象
    //private AccountDao accountDao = (AccountDao) GenerateDaoProxy.generate(SqlSessionUtil.openSession(), AccountDao.class);
    // 在mybatis当中,提供了相关的机制。也可以动态为我们生成dao接口的实现类(代理类:dao接口的代理)
    // mybatis当中实际上采用了代理模式。在内存中生成dao接口的代理类,然后创建代理类的实例。
    private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);
    ......
    }
    • 使用以上代码的前提是:**AccountMapper.xml 文件中的 namespace 必须和 dao 接口的全限定名称一致,id必须和 dao 接口中方法名一致。**
  • 自此,我们就不必再去实现 DAO 接口了,而是使用 mybatis 的这种机制去动态生成接口实现类,来得到生成类的对象。

---------------The End---------------