提示:本文章基于B站动力节点的课程仿写
文章目录
- 前言
- 一、解析mybatis-config.xml
- 1.1 引入dom4j依赖
- 1.2 解析mybatis-config.xml
- 1.3 解析mapper映射文件
- 二、引入javassist
- 2.1 引入javassist依赖
- 2.基于mybatis的javassist来实现该功能
前言
本文章基于B站动力节点的课程仿写,不仅仅会用,更可以对mybatis底层动态实现接口的生成有更深入的理解
提示:以下是本篇文章正文内容,下面案例可供参考
一、解析mybatis-config.xml
1.1 引入dom4j依赖
基于maven实现
dom4j可以用来解析读取mybatis-config.xml和mapper映射文件
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
1.2 解析mybatis-config.xml
@Test
public void t1()throws Exception{
//创建SAXReader对象
SAXReader saxReader = new SAXReader();
Reader resource = Resources.getResourceAsReader("mybatis-config.xml");
//解析XML文件,document代表了整个xml文件
Document document = saxReader.read(resource);
//从根下开始找configuration标签,然后找configuration标签下的子标签environment
String xpath="/configuration/environments";
//解析这个标签得到xpath的节点node
//Element是Node的子类,方法更多,使用更方便
Element node = (Element) document.selectSingleNode(xpath);
//拿到environments的default的值
String attributeDefaultValue = node.attributeValue("default");
//拿到environments的默认使用数据库环境
//@id ---> 是xml语法 表示获取id标签为xxx的environment
xpath="/configuration/environments/environment[@id='"+attributeDefaultValue+"']";
System.out.println(xpath);
Element environmentElement = (Element)document.selectSingleNode(xpath);
//获取environment下的transaction对象
//element()表示获取当前标签下的子标签
Element transactionManager = environmentElement.element("transactionManager");
//获取transactionManager的type的值
//获取到事务管理器的类型
String transactionManagerTypeValue = transactionManager.attributeValue("type");
System.out.println(transactionManagerTypeValue);
//获取dataSource节点
//获取到数据源
Element dataSourceElement =(Element)environmentElement.element("dataSource");
String dataSourceTypeValue = dataSourceElement.attributeValue("type");
System.out.println("mybatis-config.xml数据库的数据源类型是===>"+dataSourceTypeValue);
//获取数据源下所有的节点
List<Element> dataSourceNodes = dataSourceElement.elements();
for (Element dataSourceNode : dataSourceNodes) {
//遍历dataSource下所有的节点的name value
String name = dataSourceNode.attributeValue("name");
String value = dataSourceNode.attributeValue("value");
System.out.println(name+"="+value);
}
//获取所有的mappers标签
xpath="/configuration/mappers";
Element mappersElement =(Element)document.selectSingleNode(xpath);
List<Element> mapperNodes = mappersElement.elements();
for (Element mapperNode : mapperNodes) {
String mapperNodeResource = mapperNode.attributeValue("resource");
System.out.println("mapper标签的资源名为===>"+mapperNodeResource);
}
}
1.3 解析mapper映射文件
@Test
public void parseMapper()throws Exception{
SAXReader saxReader = new SAXReader();
Reader resource = Resources.getResourceAsReader("CarMapper.xml");
Document document = saxReader.read(resource);
//从根路径下解析mapper
String xpath="/mapper";
Element mapperElement = (Element)document.selectSingleNode(xpath);
//获取mapper的命名空间namespace
String mapperNameSpace = mapperElement.attributeValue("namespace");
System.out.println("mapper的namespace为===>"+mapperNameSpace);
//获取mapper下所有的节点
List<Element> mapperNodes = mapperElement.elements();
for (Element mapperNode : mapperNodes) {
String NodeId = mapperNode.attributeValue("id");
String NodeTypeResult = mapperNode.attributeValue("typeResult");
//获取标签中的sql语句并且去掉空格
String sql = mapperNode.getTextTrim();
System.out.println("结点id="+NodeId+"结点返回集="+NodeTypeResult+sql);
//将sql中的#{数据}替换成为?
String newSql = sql.replaceAll("#\\{[0-9A-Za-z]*}", "?");
System.out.println(newSql);
}
}
二、引入javassist
mybatis底层动态实现mapper接口的实现类是基于javassist来实现的
javassist可以将实现类在内存中动态生成
2.1 引入javassist依赖
代码如下(示例):
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.1-GA</version>
</dependency>
AccountDao接口
package com.mk.javassist.dao;
public interface AccountDao {
void delete();
int insert(String user);
int update(String user, Double balance);
String selectByUser(String user);
}
@Test
public void t3() throws Exception {
//获取类池
ClassPool pool = ClassPool.getDefault();
//制造类
CtClass ctClass = pool.makeClass("com.mk.dao.AccountDaoImpl");
//制造接口 这个接口必须是存在的
CtClass ctInterface = pool.makeInterface("com.mk.javassist.dao.AccountDao");
//添加接口到类中
ctClass.addInterface(ctInterface);
//制造方法
String methodCode = "public void delete(){System.out.println(123456);}";
CtMethod ctMethod = CtMethod.make(methodCode, ctClass);
//将方法添加到类中
ctClass.addMethod(ctMethod);
//在内存中生成类,同时将生成的类加载到JVM中
Class<?> aClass = ctClass.toClass();
AccountDao accountDao = (AccountDao) aClass.newInstance();
accountDao.delete();
输出结果:
2.基于mybatis的javassist来实现该功能
mybatis将javassist进行了封装,我们只需要引入mybatis依赖就可以使用
代码如下(示例):
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
从导包中不难看出javassist是在mybatis的包中
package com.mk.util;
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.session.SqlSession;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 可以动态生成Dao的实现类
*/
public class GenerateDaoProxy {
/**
* 生成Dao接口的实现类,并且将实现类的对象创建并返回
* @param daoInterface
* @return
*/
public static Object generate(SqlSession sqlSession,Class daoInterface){
//获取类池
ClassPool pool = ClassPool.getDefault();
//制造类
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 {
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++) {
methodCode.append(parameterTypes[i].getName());
methodCode.append(" ");
methodCode.append("parameter"+i);
if(i != parameterTypes.length-1){
methodCode.append(",");
}
}
methodCode.append(")");
methodCode.append("{");
//方法体中的代码 begin
methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.mk.dao.SqlSessionUtil.openSqlSession();");
//注:sql语句的id是框架使用者提供的,具有多变性,对于框架的开发人员来说不知道
//既然框架开发者不知道sqlId,mybatis开发者出台了一个规定:凡是使用GenerateDaoProxy机制的
//sqlId都不能随便写。namespace必须是dao接口的全限定名称。id必须是dao接口中方法名
String sqlId=daoInterface.getName()+"."+method.getName();
//第一个方法:拿到XXXMapper.xml映射文件。第二个方法:获取到MappedStatement对象,存储了sqlId对应的sql语句和标签参数
//第三个方法:通过拿到的sql标签返回sql标签的类型
SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
if(sqlCommandType == SqlCommandType.DELETE){
}else if(sqlCommandType == SqlCommandType.INSERT){
}else if(sqlCommandType.equals( SqlCommandType.UPDATE)){
methodCode.append("return sqlSession.update(\""+sqlId+"\",parameter0);");
}else if(sqlCommandType.equals(SqlCommandType.SELECT)){
methodCode.append("return ("+method.getReturnType().getName()+")sqlSession.selectByAccountUser(\""+sqlId+"\",parameter0);");
}
//方法体中的代码 end
methodCode.append("}");
//将methodCode拼接的每一个方法都加入到ctClass类中
CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
ctClass.addMethod(ctMethod);
} catch (CannotCompileException e) {
e.printStackTrace();
}
});
//创建对象
Object object=null;
try {
Class<?> clazz = ctClass.toClass();
object = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return object;
}
}