Mybatis的Mapper接口代理机制

提示:本文章基于B站动力节点的课程仿写

前言

本文章基于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;
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值