javassist动态⽣成XxxDaoImpl类
Javassist是由东京⼯业⼤学的千叶滋所创建的⼀个开源的类库用来分析、编辑和创建Java字节码,已加⼊开放源代码JBoss应⽤服务器项⽬
- 通过使⽤Javassist操作字节码为JBoss实现动态"AOP"框架
引⼊javassist的依赖
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.1-GA</version>
</dependency>
javassist的常用方法
ClassPool类获取类池的静态方法和制造类和接口的实例方法
方法名 | 功能 |
---|---|
public static ClassPool getDefault() | 获取类池对象,这个类池就是用来给我生成class的 |
public CtClass makeClass(“全限定类名”) | 制造类 , 需要告诉javassist制造类的全限定类名是啥 |
public CtClass makeInterface(“全限定接口名”) | 制造接口 , 需要告诉javassist制造接口的全限定接口名是啥 |
CtMethod制造方法的静态方法和设置方法的实例方法
方法名 | 功能 |
---|---|
public static CtMethod make(“方法代码片段”,CtClass) | 制造方法对象 , 第一个参数是方法的代码片段 , 第二个参数指定要把方法添加到哪个制造类中 |
public CtMethod(返回值类型,⽅法名,形式参数列表,所属类) | 构造方法,创建方法对象 |
void setModifiers(枚举类) | 设置方法的修饰符,如Modifier.PUBLIC |
void setBody(“方法体”) | 设置方法体 |
CtClass的实例方法
方法名 | |
---|---|
public CtClass addMethod(CtMethod) | 将方法添加到类中 , 参数是要添加的方法对象 |
public CtClass toClass(); | 在内存中生成类,然后调用Class.forNmae方法手动加载到Jvm得到制造类的字节码对象 |
public Class toClass(); | 在内存中生成类同时加载到JVM当中直接得到制造类的字节码对象 |
public CtClass addInterface(ctInterface) | 将接口添加到类中(类实现接口) , 参数是要添加的接口对象 |
在内存中制造类并创建方法,这里运⾏时由于JDK版本过高会出现有异常所以需要加两个参数(点击Modify options->Add VM options)
- add-opens java.base/java.lang=ALL-UNNAMED
- add-opens java.base/sun.net.util=ALL-UNNAMED
@Test
public void testGenerateFirstClass() throws Exception{
// 获取类池,这个类池就是用来给我生成class的
ClassPool pool = ClassPool.getDefault();
// 制造类(需要告诉javassist类名是啥)
CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl");
// 制造方法
String methodCode = "public void insert(){System.out.println(123);}";
CtMethod ctMethod = CtMethod.make(methodCode, ctClass);
// 将方法添加到类中
ctClass.addMethod(ctMethod);
// 在内存中生成class
ctClass.toClass();
// 类加载到JVM当中,返回AccountDaoImpl类的字节码
Class<?> clazz = Class.forName("com.powernode.bank.dao.impl.AccountDaoImpl");
// 创建对象
Object obj = clazz.newInstance();
// 获取AccountDaoImpl中的insert方法
Method insertMethod = clazz.getDeclaredMethod("insert");
// 调用方法insert
insertMethod.invoke(obj);
}
创建方法的另一种形式
// 执行构造方法创建⽅法对象
CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute", newCtClass[]{}, ctClass);
// 设置⽅法的修饰符列表
ctMethod.setModifiers(Modifier.PUBLIC);
// 设置⽅法体
ctMethod.setBody("{System.out.println(\"hello world\");}");
动态生成接口实现类
已知接口中方法的方法名和返回值类型,在内存中动态生成AccountDao接口的实现类
public interface AccountDao {
void delete();
}
@Test
public void testGenerateImpl() throws Exception{
// 获取类池
ClassPool pool = ClassPool.getDefault();
// 制造类
CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl");
// 制造接口
CtClass ctInterface = pool.makeInterface("com.powernode.bank.dao.AccountDao");
// 添加接口到类中,相当于让AccountDaoImpl implements AccountDao
ctClass.addInterface(ctInterface);
// 实现接口中的方法,假设我们已经知道接口中的方法名和返回值类型
CtMethod ctMethod = CtMethod.make("public void delete(){System.out.println(\"hello delete!\");}", ctClass);
// 将方法添加到类中
ctClass.addMethod(ctMethod);
// 在内存中生成类的同时将生成的类加载到JVM当中
Class<?> clazz = ctClass.toClass();
// 面向接口编程,强制类型转换
AccountDao accountDao = (AccountDao)clazz.newInstance();
accountDao.delete();
}
不知道方法的方法名和返回值类型, 但是方法中的代码片段固定, 动态生成接口的实现类中并实现所有方法
public interface AccountDao {
void delete();
int insert(String actno);
int update(String actno, Double balance);
String selectByActno(String actno)
}
@Test
public void testGenerateAccountDaoImpl() throws Exception{
// 获取类池
ClassPool pool = ClassPool.getDefault();
// 制造类
CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl");
// 制造接口
CtClass ctInterface = pool.makeInterface("com.powernode.bank.dao.AccountDao");
// 实现接口
ctClass.addInterface(ctInterface);
// 要实现接口中所有的方法,先获取接口中所有的抽象方法
Method[] methods = AccountDao.class.getDeclaredMethods();
Arrays.stream(methods).forEach(method -> {
try {
// public void delete(){}
// public int update(String actno, Double balance){}
StringBuilder methodCode = new StringBuilder();
// 追加修饰符列表
methodCode.append("public ");
// 追加返回值类型(getReturnType()以Class形式返回方法的返回值类型对应的Class对象)
methodCode.append(method.getReturnType().getName());
// 追加空格
methodCode.append(" ");
// 追加方法名
methodCode.append(method.getName());
// 获取方法的所有形参类型并拼接方法参数
me(thodCode.append("(");
// getParameterTypes()以Class[]形式返回方法的每个参数的数据类型对应的Class对象
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("){System.out.println(11111); ");
// 根据方法的返回值类型的简类名,动态的添加return语句
String returnTypeSimpleName = method.getReturnType().getSimpleName();
// 判断返回值类型
if ("void".equals(returnTypeSimpleName)) {
}else if("int".equals(returnTypeSimpleName)){
methodCode.append("return 1;");
}else if("String".equals(returnTypeSimpleName)){
methodCode.append("return \"hello\";");
}
methodCode.append("}");
System.out.println(methodCode);
// 制造方法并添加到类中
CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
ctClass.addMethod(ctMethod);
} catch (Exception e) {
e.printStackTrace();
}
});
// 在内存中生成class并加载到JVM当中
Class<?> clazz = ctClass.toClass();
// 创建实现类的对象
AccountDao accountDao = (AccountDao) clazz.newInstance();
// 调用动态生成的AccountDao实现类的方法
accountDao.insert("aaaaa");
accountDao.delete();
accountDao.update("aaaa", 1000.0);
accountDao.selectByActno("aaaa");
}
MyBatis生成DAO接口实现类原理
MyBatis号称轻量级只需要一个jar包就行 , 所以它对javassist进行了二次包装,底层动态生成类的时候不需要再引入javassist的依赖
SqlSession类获取核心配置文件信息的方法
方法名 | 功能 |
---|---|
getConfiguration() | 获取MyBaits核心配置文件的configuration标签内的信息 |
getMappedStatement(sqlId) | 通过sql语句id获取mapper配置文件中的sql语句 |
getSqlCommandType() | 获取sql语句的枚举类型(增,删,改,查) |
手写GenerateDaoProxy工具类
只要按照MaBatis的规定创建规范的XxxMapper.xml文件, MaBatis解析完xml文件就会为你动态生成dao接口的实现类
public interface AccountDao {
// 根据账号查询账户信息
Account selectByActno(String actno);
// 更新账户信息.1表示更新成功,其他值表示失败
int updateByActno(Account act);
//其他增删的方法...
}
// dao实现类中的⽅法一般都是通过SqlSession对象调⽤增删改查等⽅法,没有任何业务逻辑
public class AccountDaoImpl implements AccountDao {
@Override
public Account selectByActno(String arg0) {
SqlSession sqlSession = SqlSessionUtil.openSession();
Account account = (Account) sqlSession.selectOne("account.selectByActno", arg0);
return account;
}
@Override
public int updateByActno(Account arg0) {
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.update("account.updateByActno",arg0);
return count;
}
}
<?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="com.powernode.bank.dao.AccountDao">
<select id="selectByActno" resultType="com.powernode.bank.pojo.Account">
select * from t_act where actno = #{actno}
</select>
<update id="updateByActno">
update t_act set balance = #{balance} where actno = #{actno}
</update>
</mapper>
GenerateDaoProxy工具类(MyBatis框架的已写好),只要按照规范它就可以动态生成dao接口实现类并将实现类的对象创建出来返回
- 方法体中所有代码片段的数据类型必须使用全类名: 如对于javassist来说必须要知道SqlSession是哪个包下的SqlSession类
- 确定sqlid(namespace.id): sqlid由框架使用者提供具有多变性,所以MyBatis框架规定namespace必须是dao接口的全限定名称,Id必须是接口中的方法名
- 确定sql语句类型: 使用sqlSession对象获取核心配置文件中configuration标签内的信息,然后通过sqlid获取sql语句的类型,确定执行sqlSession对象增删改查的方法
public class GenerateDaoProxy {
/**
* @param daoInterface dao接口
* @return dao接口实现类的实例化对象
*/
public static Object generate(SqlSession sqlSession, Class daoInterface){
// 类池
ClassPool pool = ClassPool.getDefault();
// 在内存中动态生成一个代理类(com.powernode.bank.dao.AccountDao --> com.powernode.bank.dao.AccountDaoProxy)
CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy");
// 制造接口
CtClass ctInterface = pool.makeInterface(daoInterface.getName());
// 实现接口
ctClass.addInterface(ctInterface);
// 实现接口中所有的方法
Method[] methods = daoInterface.getDeclaredMethods();
Arrays.stream(methods).forEach(method -> {
try {
// Account selectByActno(String actno)--->public Account selectByActno(String arg0){代码; }
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("{");
// 生成SqlSession sqlSession = SqlSessionUtil.openSession();对于javassist来说必须知道SqlSession是哪个包下的SqlSession类
methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.powernode.bank.utils.SqlSessionUtil.openSession();");
// 生成return 接口的方法返回值类型 sqlSession.selectOne("sqlId", arg0);拼接方法时的形式参数用的是arg0
String sqlId = daoInterface.getName() + "." + method.getName();
// 通过sqlId判断sql语句的类型
SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
if (sqlCommandType == SqlCommandType.INSERT) {
}
if (sqlCommandType == SqlCommandType.DELETE) {
}
if (sqlCommandType == SqlCommandType.UPDATE)
// 实现return sqlSession.update("account.updateByActno", arg0);
methodCode.append("return sqlSession.update(\""+sqlId+"\", arg0);");
}
if (sqlCommandType == SqlCommandType.SELECT) {
// 实现return (Account) sqlSession.selectOne("account.selectByActno", agr0);
String returnType = method.getReturnType().getName();
// selectOne方法的返回值类型默认是object类型,需要我们强转为接口方法的返回值类型
methodCode.append("return ("+returnType+")sqlSession.selectOne(\""+sqlId+"\", arg0);");
}
methodCode.append("}");
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;
}
}
通过自己封装的GenerateDaoProxy工具类动态生成AccountDao的实现类AccountServiceImpl
public class AccountServiceImpl implements AccountService {
// 手动编写AccountDao的实现类并创建对象返回
private AccountDao accountDao = new AccountDaoImpl();
// 通过自己封装的GenerateDaoProxy工具类动态生成AccountDao的实现类并创建好对象返回
private AccountDao accountDao = (AccountDao) GenerateDaoProxy.generate(SqlSessionUtil.openSession(), AccountDao.class);
// 业务方法...
}