一、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);
一 叶 知 秋,奥 妙 玄 心