MyBatis——使用 Javassit 在内存中生成类

一、Javassit 的使用

Javassist(Java Programming Assistant)是一个用于在运行时编辑字节码的开源库。

它允许 Java 程序动态地编辑类文件,创建新的类,或者直接以字节码形式创建新的类。

Javassist 主要用于在运行时生成新的类、修改现有类的行为,或者在类加载时对字节码进行动态修改。

Javassist 提供了一组简单而强大的 API,使得开发人员可以方便地操作类的结构,例如添加、删除、修改字段、方法和构造函数,以及修改类的继承关系等。

这种在运行时对字节码进行操作的能力使得 Javassist 在某些场景下非常有用,比如 AOP(面向切面编程)、动态代理、应用服务器领域等。

一些 Javassist 的主要特点包括:

  • 简单易用:Javassist 提供了简洁的 API,使得开发人员可以轻松地进行字节码操作,而不需要深入了解 JVM 字节码指令集。

  • 灵活性:Javassist 允许开发人员在运行时动态编辑类文件,从而可以根据需要对类的结构进行灵活的修改。这种灵活性使得 Javassist 可以被广泛应用于很多动态代码生成的场景。

  • 跨平台性:作为一个 Java 库,Javassist 可以在不同的 Java 平台上运行,提供了对字节码操作的跨平台能力。

总的来说,Javassist 是一个强大而灵活的字节码操作库,在需要在运行时动态生成或修改类文件的场景中具有广泛的应用价值。

  • 引入依赖
XML 复制代码
<dependency>
  <groupId>org.javassist</groupId>
  <artifactId>javassist</artifactId>
  <version>3.29.1-GA</version>
</dependency>
  • 使用 Javassist
java 复制代码
package org.qiu.javassist;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;

import java.lang.reflect.Method;

public class JavassistTest {
    public static void main(String[] args) throws Exception {
        // 获取类池
        ClassPool pool = ClassPool.getDefault();
        // 创建类
        CtClass ctClass = pool.makeClass("com.qiu.javassist.Test");
        // 创建方法
        // 1.返回值类型 2.方法名 3.形式参数列表 4.所属类
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute", new CtClass[]{}, ctClass);
        // 设置方法的修饰符列表
        ctMethod.setModifiers(Modifier.PUBLIC);
        // 设置方法体
        ctMethod.setBody("{System.out.println(\"hello world\");}");
        // 给类添加方法
        ctClass.addMethod(ctMethod);
        // 调用方法
        Class<?> aClass = ctClass.toClass();
        Object o = aClass.newInstance();
        Method method = aClass.getDeclaredMethod("execute");
        method.invoke(o);
    }
}

运行要注意:加两个参数,要不然会有异常【Java 高版本存在的问题】

  • VM options:--add-opens java.base/java.lang=ALL-UNNAMED

  • Environment variables:--add-opens java.base/sun.net.util=ALL-UNNAMED

二、使用 Javassist 生成 DaoImpl 类

java 复制代码
package org.qiu.bank.utils;

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

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

/**
 * 使用javassist库动态生成dao接口的实现类
 *
 * @author 秋玄
 * @version 1.0
 * @since 1.0
 */
public class GenerateDaoByJavassist {

    /**
     * 根据dao接口生成dao接口的代理对象
     *
     * @param sqlSession   sql会话
     * @param daoInterface dao接口
     * @return dao接口代理对象
     */
    public static Object getMapper(SqlSession sqlSession, Class daoInterface) {
        ClassPool pool = ClassPool.getDefault();
        // 生成代理类
        CtClass ctClass = pool.makeClass(daoInterface.getPackageName() + ".impl." + daoInterface.getSimpleName() + "Impl");
        // 接口
        CtClass ctInterface = pool.makeClass(daoInterface.getName());
        // 代理类实现接口
        ctClass.addInterface(ctInterface);
        // 获取所有的方法
        Method[] methods = daoInterface.getDeclaredMethods();
        Arrays.stream(methods).forEach(method -> {
            // 拼接方法的签名
            StringBuilder methodStr = new StringBuilder();
            String returnTypeName = method.getReturnType().getName();
            methodStr.append(returnTypeName);
            methodStr.append(" ");
            String methodName = method.getName();
            methodStr.append(methodName);
            methodStr.append("(");
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                methodStr.append(parameterTypes[i].getName());
                methodStr.append(" arg");
                methodStr.append(i);
                if (i != parameterTypes.length - 1) {
                    methodStr.append(",");
                }
            }
            methodStr.append("){");
            // 方法体当中的代码怎么写?
            // 获取sqlId(这里非常重要:因为这行代码导致以后namespace必须是接口的全限定接口名,sqlId必须是接口中方法的方法名。)
            String sqlId = daoInterface.getName() + "." + methodName;
            // 获取SqlCommondType
            String sqlCommondTypeName = sqlSession.getConfiguration()
                                                  .getMappedStatement(sqlId)
                                                  .getSqlCommandType()
                                                  .name();
            if ("SELECT".equals(sqlCommondTypeName)) {
                methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");
                methodStr.append("Object obj = sqlSession.selectOne(\"" + sqlId + "\", arg0);");
                methodStr.append("return (" + returnTypeName + ")obj;");
            } else if ("UPDATE".equals(sqlCommondTypeName)) {
                methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");
                methodStr.append("int count = sqlSession.update(\"" + sqlId + "\", arg0);");
                methodStr.append("return count;");
            }
            methodStr.append("}");
            System.out.println(methodStr);
            try {
                // 创建CtMethod对象
                CtMethod ctMethod = CtMethod.make(methodStr.toString(), ctClass);
                ctMethod.setModifiers(Modifier.PUBLIC);
                // 将方法添加到类
                ctClass.addMethod(ctMethod);
            } catch (CannotCompileException e) {
                throw new RuntimeException(e);
            }
        });
        try {
            // 创建代理对象
            Class<?> aClass = ctClass.toClass();
            Constructor<?> defaultCon = aClass.getDeclaredConstructor();
            Object o = defaultCon.newInstance();
            return o;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

修改 AccountMapper.xml 文件:namespace 必须是 dao 接口的全限定名称,id 必须是 dao 接口中的方法名

XML 复制代码
<?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">

<mapper namespace="org.qiu.bank.dao.AccountDao">
    <select id="selectByActno" resultType="org.qiu.bank.pojo.Account">
        select * from t_act where actno = #{actno}
    </select>
    <update id="update">
        update t_act set balance = #{balance} where actno = #{actno}
    </update>
</mapper>

修改 service 类中获取 dao 对象的代码

java 复制代码
// private AccountDao accountDao = new AccountDaoImpl();
private AccountDao accountDao = (AccountDao) GenerateDaoByJavassist
    .getMapper(SqlSessionUtil.openSession(),AccountDao.class);

注:启动过程中显示,Tomcat 服务器自动添加了上文的两个运行参数,所以不需要再单独配置。

三、接口代理机制

MyBatis 底层已经实现了以上内容,直接调用即可

java 复制代码
AccountDao accountDao = (AccountDao)sqlSession.getMapper(AccountDao.class);

AccountMapper.xml 文件中的 namespace 必须和 dao 接口的全限定名称一致,id 必须和 dao 接口中方法名一致。

将 service 中获取 dao 对象的代码再次修改

java 复制代码
// private AccountDao accountDao = new AccountDaoImpl();
// private AccountDao accountDao = (AccountDao) GenerateDaoByJavassist.getMapper(SqlSessionUtil.openSession(),AccountDao.class);
private AccountDao accountDao = (AccountDao) SqlSession.openSession()
    												   .getMapper(AccountDao.class);

一 叶 知 秋,奥 妙 玄 心

相关推荐
Java小白笔记5 小时前
关于使用Mybatis-Plus 自动填充功能失效问题
spring boot·后端·mybatis
计算机学姐9 小时前
基于SpringBoot+Vue的篮球馆会员信息管理系统
java·vue.js·spring boot·后端·mysql·spring·mybatis
程序员大金9 小时前
基于SpringBoot+Vue+MySQL的智能物流管理系统
java·javascript·vue.js·spring boot·后端·mysql·mybatis
惜.己14 小时前
MyBatis中一对多关系的两种处理方法
java·开发语言·后端·sql·mysql·mybatis·idea
终末圆14 小时前
MyBatis动态SQL中的`if`标签使用【后端 19】
java·数据结构·数据库·sql·算法·spring·mybatis
飞翔的佩奇16 小时前
Java项目: 基于SpringBoot+mybatis+maven医院管理系统(含源码+数据库+任务书+开题报告+毕业论文)
java·数据库·spring boot·毕业设计·maven·mybatis·医院管理系统
这孩子叫逆17 小时前
35. MyBatis中的缓存失效机制是如何工作的?
java·spring·mybatis
ChinaRainbowSea18 小时前
十八,Spring Boot 整合 MyBatis-Plus 的详细配置
java·数据库·spring boot·spring·mybatis·web
飞翔的佩奇1 天前
Java项目: 基于SpringBoot+mybatis+maven课程答疑系统(含源码+数据库+毕业论文)
java·数据库·spring boot·毕业设计·maven·mybatis·课程答疑
OceanSky61 天前
Mybatis中sql数组为空判断
数据库·sql·mybatis·数组判空