简单介绍:
Javassist
是一个开源的分析、编辑和创建 Java 字节码的类库。关于 java 字节码的处理,目前有很多工具,如 bcel,asm,不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用
javassist
。
javassist
是 jboss 的一个子项目,其主要的优点,在于简单,而且快速。直接使用 java 编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。简而言之:Javassist
能够转换现有类的基本内容,或创建一个新类。装载到类池中的类由
javassist.CtClass
实例表示。与标准的java.lang.Class
类一样,CtClass
提供了检查类数据(如字段和方法)的方法。不过,这只是CtClass
的部分内容,它还定义了在类中添加新字段、方法和构造函数、以及改变类、父类和接口的方法。奇怪的是,Javassist
没有提供删除一个类中字段、方法或者构造函数的任何方法。
- 字段、方法和构造函数分别由
javassist.CtField
、javassist.CtMethod
和javassist.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.CtField
、javassist.CtMethod
和javassist.CtConstructor
的实例表示。这些类定义了修改由它们所表示的对象的所有方法的方法,包括方法或者构造函数中的实际字节码内容。
- 字段、方法和构造函数分别由
总结:反射可以使用字节码文件里面的信息,
javassist
可以根据字节码信息动态的生成自己所需要的类。直接看别人的笔记吧:
也可以看视频:
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
106package 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
<!--要想使用这种机制,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
8package 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
9public 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 的这种机制去动态生成接口实现类,来得到生成类的对象。