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);

一 叶 知 秋,奥 妙 玄 心

相关推荐
鹿屿二向箔3 小时前
基于SSM(Spring + Spring MVC + MyBatis)框架的汽车租赁共享平台系统
spring·mvc·mybatis
沐雪架构师6 小时前
mybatis连接PGSQL中对于json和jsonb的处理
json·mybatis
鹿屿二向箔7 小时前
基于SSM(Spring + Spring MVC + MyBatis)框架的咖啡馆管理系统
spring·mvc·mybatis
aloha_78917 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
毕业设计制作和分享18 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
paopaokaka_luck21 小时前
基于Spring Boot+Vue的助农销售平台(协同过滤算法、限流算法、支付宝沙盒支付、实时聊天、图形化分析)
java·spring boot·小程序·毕业设计·mybatis·1024程序员节
cooldream20091 天前
Spring Boot中集成MyBatis操作数据库详细教程
java·数据库·spring boot·mybatis
不像程序员的程序媛1 天前
mybatisgenerator生成mapper时报错
maven·mybatis
小布布的不1 天前
MyBatis 返回 Map 或 List<Map>时,时间类型数据,默认为LocalDateTime,响应给前端默认含有‘T‘字符
前端·mybatis·springboot
背水1 天前
Mybatis基于注解的关系查询
mybatis