用过mybatis的人都知道mybatis的特点就是sql写在配置文件中,使用者使用的时候只需要调相对应的接口方法,或者是ibatis那种调配置文件中的ID。

那mabatis怎么实现调用一个空接口或者调用配置文件中的ID来执行sql的呢?

如果要解读源码,那这篇文章实在太长,这里只做一个小实验演示,并且指出小实验中代码与mybatis代码间的关系。

反射技术是一些java框架经常用到的技术,使用反射可以在不改变源代码的情况下改变代码运行的流程方式。

jdk提供了一个生成接口的实现类,其方法调用内容都来自于指定的接口实现类的方法,也就是说,你在你的代码里写的mapper接口,在mybatis中看来都会被转到mybatis自定义的真正执行类,想一想为什么接口方法名和ID一致?

代码

废话不多说,看看jdk提供的方法吧。

ProxyFactory.java

首先看看这个实例生成的方法,ProxyFactory.java

import java.lang.reflect.Proxy;

/**
 * @author gavin
 * @version V1.0
 */
public class ProxyFactory<T> {

    /**
     * 注意这里传入的class一定是接口的class,不能是类的class
     * @param t
     * @return
     */
    protected T newInstance(Class t){
        return (T) Proxy.newProxyInstance(t.getClassLoader(),new Class[]{t},new InvocationHandlerImpl());
    }
}

InvocationHandlerImpl.java

在上边代理生成类里,我们指定了接口实现类来具体执行方法,实现类InvocationHandlerImpl.java代码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @author gavin
 * @version V1.0
 */
public class InvocationHandlerImpl implements InvocationHandler {
    static Map<String, String> map = new HashMap<>();

    static {
        map.put("hello", "正在说hello");
        map.put("goodbye", "正在说goodbye");
    }

    /**
     * 功能是打印特定文字
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String str = map.get(method.getName());
        if (str == null) {
            System.out.println("未实现 "+method.getName());
        } else {
            System.out.println(str);
        }
        return null;
    }
}

在这个实现类中,我们根据方法名来决定从map中取哪个字符串并打印出来。

FakeInterface.java

接下来,我们写一个接口FakeInterface.java

/**
 * @author gavin
 * @version V1.0
 */
public interface FakeInterface {
    void hello();

    void goodbye();

    void other();
}

ExcuteInterface.java

最后,来写一个main方法ExcuteInterface.java

/**
 * @author gavin
 * @version V1.0
 */
public class ExcuteInterface {
    public static void main(String[] args) {
        ProxyFactory<FakeInterface> proxyFactory = new ProxyFactory();
        FakeInterface fakeInterface = proxyFactory.newInstance(FakeInterface.class);

        fakeInterface.hello();
        fakeInterface.goodbye();
        fakeInterface.other();
    }
}

运行结果

运行结果不言自明,应该就是我们在map中取到的字符串了。

正在说hello
正在说goodbye
未实现 other

Mapper的实现

mapper的实现就是基于jdk提供的这个实现方法,从使用者自定义的接口中获取方法名,入参和出参,然后综合判断后执行对应的sql。

实际上,mybatis是有一个MapperMethod类来执行sql的。具体的执行就是MapperMethod的sqlSession执行具体sql。

知道了这个原理,我们也能自己写一个简单版的sql执行器了。

  • 在配置文件中配置key和sql。
  • 在代码运行第一步加载key和sql到InvocationHandler接口实现类中的map中。
  • InvocationHandler接口实现类中设置上SqlStatement
  • 从proxyFactory中获取接口实现类。
  • 执行接口方法,获取相应数据。

SqlSession在SqlSessionTemplate的实现

SqlSessionTemplate是ibatis中的写法,具体可以看源码。

SqlSessionTemplate类并没有真正实现SqlSession接口,而是又写了一个InvocationHandler接口实现内部类,在这个实现内部类中通过外部类SqlSessionTemplate的SqlSessionFactory获取到真正的SqlSession用来执行。

说白了SqlSessionTemplate就是给spring准备的,mybatis不想为了sping再写一个SqlSession。

标签: java, 代理, mybatis, 反射

添加新评论