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

一 叶 知 秋,奥 妙 玄 心

相关推荐
向阳121810 小时前
mybatis 动态 SQL
数据库·sql·mybatis
新手小袁_J11 小时前
JDK11下载安装和配置超详细过程
java·spring cloud·jdk·maven·mybatis·jdk11
xlsw_20 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
cmdch20171 天前
Mybatis加密解密查询操作(sql前),where要传入加密后的字段时遇到的问题
数据库·sql·mybatis
秋恬意1 天前
什么是MyBatis
mybatis
CodeChampion1 天前
60.基于SSM的个人网站的设计与实现(项目 + 论文)
java·vue.js·mysql·spring·elementui·node.js·mybatis
ZWZhangYu2 天前
【MyBatis源码分析】使用 Java 动态代理,实现一个简单的插件机制
java·python·mybatis
程序员大金2 天前
基于SSM+Vue的个性化旅游推荐系统
前端·vue.js·mysql·java-ee·tomcat·mybatis·旅游
奔跑草-2 天前
【服务器】MyBatis是如何在java中使用并进行分页的?
java·服务器·mybatis
秋恬意3 天前
接口绑定有几种实现方式
mybatis