三个类告诉你MyBatis是如何用动态代理实现的

249 阅读4分钟
今天来讲一下mybatis的实现原理,我们都知道mybatis是使用动态代理的方式实现的一套ORM框架,那么他是怎么实现的呢?

在回答这个问题之前,我们先来捋一下mybatis框架的使用流程,一般我们会把他跟spring一起结合使用,spring容器来统一管理所有对象,使用mybatis时首先要进行几个配置。

  1. dao的包路径(也就是接口对象的包路径)
  2. xml文件的路径

我们都知道使用mybatis开发的时候,一般是一个接口类对应一个xml文件,接口中的每个方法也对应着xml中的document节点,这里我就不详细说,用过的都知道,那么mybatis就是通过动态代理的方式根据我们的dao层的接口以及接口方法对应的xml去自动生成的实现类,统一帮我们做掉了建立数据连接,拼接sql语句,执行sql语句,转换sql返回值到对象等这一些列的动作。

下面说一下简单实现他的工作流程:

  1. 项目启动时根据xml文件路径读取所有的xml信息,到map中存储,map的key可以定义为类名+方法名
  2. 编写代理类,代理类负责根据被代理的类名+方法名,读取对应的sql配置,然后根据入参,拼接解析出完整sql,然后交给jdbc去执行,最后将返回数据转换成接口返回值的对象做返回
  3. 根据dao的包路径读取所有的需要代理的dao对象,里面上面第二条写的代理类来循环为每个dao创建代理类
  4. 将所有生成的dao代理类放到spring容器中进行管理,使用时直接通过spring的注解就可以注入被代理过的dao

ok下面看下简单的代码(PS:前方高能,代码过于简陋😂,不然也不会只有3个类,大家懂意思就可以了)

// 先定义一个dao接口,有两个方法
interface ITestDao {

    String test1(@Param("code") String code);

    String test2(@Param("code") String code);
} 

// 创建一个代理类,实现InvocationHandler接口,这里xml就不建文件了,直接写死在这里了大家懂意思就好拉,哈哈哈。。。
public class MybatisProxy implements InvocationHandler {

    public static Map<String,String> xmlMap = new HashMap<String,String>();

    static{
        // 系统初始化读取xml放入map
        // 假装这里是从xml重读取的sql数据,当然实际mybatis读取的数据结构更复杂一些,比如入参类型出参类型等,这里暂且不表
        xmlMap.put("ITestDao.test1","select * from test1 where code=:code");
        xmlMap.put("ITestDao.test2","select * from test2 where code=:code");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 类名加方法名作为读取该方法的sql的key
        String name = method.getDeclaringClass().getName() + "." + method.getName();
        // 从map中读取
        String sql = MybatisProxy.xmlMap.get(name);
        // 判断方法参数读取参数拼接到sql上
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++){
            for (Annotation annotation:parameterAnnotations[i]) {
                if(annotation instanceof Param){
                    String value = ((Param) annotation).value();
                    sql = sql.replace(":"+value,"'" + args[i] + "'");
                }
            }

        }
        // 计算出来最终要执行的sql
        System.out.println("do sql : " + sql);
        return jdbc(sql,method);
    }

    private Object jdbc(String sql,Method method) {
        // 1. 通过jdbc连接数据库,然后执行sql
        // 2. 把返回结果根据方法的返回值类型做转换,然后返回
        return null;
    }
} 

// 最后一个就是系统启动的时候用来生成实现类并且放到spring中的,下面这个只是一个main方法用来测试看效果的,实际参考最开始的那个流程,以及代码里面的注释
public class CreateProxyObj {

    // dao所有在的包路径
    public static String DAO_PACKAGE = "com.xxx.xxx.dao";

    public static <T> T createProxyObj(Class<T> clazz){
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] {clazz},new MybatisProxy());
    }

    // 当然不是用main方法跑的,需要系统初始化时自动执行
    public static void main(String[] args){
        // 1. 根据DAO_PACKAGE包找到这个包下所有的class
        // 2. 循环给每个class创建代理,创建方式如下
        ITestDao proxyObj = CreateProxyObj.createProxyObj(ITestDao.class);

        // 创建完了其实就可以用了
        proxyObj.test1("111");
        proxyObj.test2("222");
        // 但通常创建完了会把代理过的对象,放到spring容器中,这样使用的时候就能够通过@Autowired注解的方式注入使用拉
    }
} 

以上就是,mybatis的一个简单的实现原理,也清晰的向大家展示了动态代理的实现使用方式用法。