javassist的使用,以及使用javassist简单的实现MyBatis中的GenerateDaoProxy机制
1. 什么是javassist?
Javaassist 就是一个可以处理Java 字节码的类库,也就是说可以使用Javassist动态的生成接口的实现类。
可以在内存中动态的生成类。
2. 简单的使用javassist:这里只考虑通过接口简单的实现类
提供一个接口:
public interface AccountDao {
void delete();
}
通过javassist来创建一个类,实现这个接口:
第一步:获取类池,这个类池就是用来生成class的
第二步:制造类
第三步:生成接口
第四步:添加接口到类中
第五步:制造出来方法
第六步:将方法加到类里
第七步:实例化该类并调用方法
public void testGenerateImpl() throws Exception{
// 获取类池
ClassPool pool = ClassPool.getDefault();
// 生成类
CtClass ctClass = pool.makeClass("AccountDaoImpl");
// 生成接口
CtClass ctInterface = pool.makeInterface("AccountDao");
// 添加接口到类中
ctClass.addInterface(ctInterface);
// 实现接口中的方法
// 制造方法,第一个参数为整个方法的字符串,第二位为装载的类对象
CtMethod ctMethod = CtMethod.make("public void delete(){System.out.println(1233);}", ctClass);
// 将方法加到类里
ctClass.addMethod(ctMethod);
// 生成类, 同时将生成的类加载到JVM中
Class<?> clazz = ctClass.toClass();
AccountDao account = (AccountDao) clazz.newInstance();
account.delete();
}
需要将配置添加到运行的配置中:
–add-opens java.base/java.lang=ALL-UNNAMED
–add-opens java.base/sun.net.util=ALL-UNNAMED
3. 提供SqlSession的工具类
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
public class SqlSessionUtil {
private SqlSessionUtil(){}
private static SqlSessionFactory sqlSessionFactory;
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
public static SqlSession openSession(){
SqlSession sqlSession = local.get();
if (sqlSession == null) {
sqlSession = sqlSessionFactory.openSession();
// 将sqlSession对象绑定到当前的线程上
local.set(sqlSession);
}
return sqlSession;
}
// 关闭sqlSession对象
public static void close(SqlSession sqlSession){
if (sqlSession != null) {
sqlSession.close();
// 从当前线程中移除sqlSession对象和当前线程的绑定关系
local.remove();
}
}
}
使用ThreadLocal的目的:为了方便在业务中进行事务控制,要保证同一个线程获取到的SqlSession是同一个SqlSession。
注意:这里的mybatis的配置文件的文件名和路径都是写死的。
文件名为:mybatis-config.xml, 路径为类资源加载的根路径。
4. 动态生成简单的Dao的实现类:
java pojo类:
public class Account {
private Long id;
private String actno;
private Double balance;
// 省略构造方法,get、set方法,toString方法等等
}
Dao接口:
public interface AccountDao {
/**
* 根据账号查询账户信息
* @param actno 账号
* @return 账户信息
*/
Account selectByActno(String actno);
/**
* 根据账号更新账户信息
* @param account 账号
* @return 更新的记录条数
*/
int updateByActno(Account account);
}
javassist实现接口:
import com.bjpowernode.bank.dao.AccountDao;
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.mapping.SqlCommandType;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.session.SqlSession;
import java.lang.reflect.Method;
import java.util.Arrays;
/*
动态生成Dao的实现类
*/
public class GenerateDaoProxy {
/**
* 生成dao的实现类,并将这个类的对象创建出来并返回
* @param daoInterface 要是实现的dao接口
* @return
*/
public static Object generate(SqlSession sqlSession, Class daoInterface){
//类池
ClassPool pool = ClassPool.getDefault();
//制造类
CtClass ctClass = pool.makeClass(daoInterface+"Proxy");
//制造接口
CtClass ctInterface = pool.makeInterface(daoInterface.getName());
// 实现接口的方法
ctClass.addInterface(ctInterface);
// 将方法添加到类里
Method[] methods = AccountDao.class.getDeclaredMethods();
Arrays.stream(methods).forEach(method -> {
// 使用StringBuilder来拼接每一个方法体
// method 是接口中的抽象方法
// 把method抽像方法实现了
CtMethod ctMethod = null;
try {
// 这一部分是修饰符列表+返回值类型+方法名+形参列表
StringBuilder methodStr = new StringBuilder();
methodStr.append("public ");
// 获取返回值类型
String returnType = method.getReturnType().getName();
methodStr.append(returnType+" ");
// 获取方法名
String name = method.getName();
methodStr.append(name);
methodStr.append("(");
// 获取所有参数
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
String parameterTypeName = parameterTypes[i].getName();
methodStr.append(parameterTypeName);
methodStr.append(" ");
methodStr.append("arg" + i);
if(i != parameterTypes.length-1){
methodStr.append(",");
}
}
methodStr.append("){");
// 这一部分是代码部分
// 这一部门是死的,第一行都是这个
methodStr.append("org.apache.ibatis.session.SqlSession sqlSession = SqlSessionUtil.openSession();");
// 需要知道是什么类型的sql语句
/**
* sqlSession.getConfiguration() 获取xxxMapper内的配置
* sqlSession.getConfiguration().getMappedStatement("id") 通过这个id可以获取到这个mapper内的一个sql语句
* sqlSession.getConfiguration().getMappedStatement("id").getStatementType() 可以获取到是哪一种sql语句
* SqlCommandType是一个枚举
* UNKNOWN,
* INSERT,
* UPDATE,
* DELETE,
* SELECT,
* FLUSH;
* 但是sql语句的id是框架的使用者提供的,具有多变性。对于我框架的开发人员来说。我不知道
* 既然我框架的开发者不知道sqlId,怎么办?mybatis框架的开发者就出台了一项规定:凡是使用GenerateDaoProxy机制的。
* sqlId不能随便写。namesapce必须是dao接口的全限定名称。id必须是接口种的方法名。
* 因此sqlId就是"接口名.方法名"
*/
String sqlId = daoInterface.getName() + "." + method.getName();
SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
System.out.println(sqlCommandType);
if(sqlCommandType == SqlCommandType.INSERT){
methodStr.append("sqlSession.");
}else if(sqlCommandType == SqlCommandType.DELETE){
}else if(sqlCommandType == SqlCommandType.UPDATE){
methodStr.append("return sqlSession.update(\""+sqlId+"\", arg0);");
}else if(sqlCommandType == SqlCommandType.SELECT){
methodStr.append("return ("+method.getReturnType().getName()+")sqlSession.selectOne(\""+sqlId+"\", arg0);");
}
methodStr.append("}");
System.out.println(methodStr);
// 方法添加到类中
ctMethod = CtMethod.make(String.valueOf(methodStr), ctClass);
ctClass.addMethod(ctMethod);
} catch (Exception e) {
e.printStackTrace();
}
});
// 加载类
Object obj = null;
try {
Class<?> clazz = ctClass.toClass();
// 创建类
obj = clazz.newInstance();
} catch (CannotCompileException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return obj;
}
}