Mybatis手撸(二)创建简单的映射器代理工厂

晴天的空间

热衷学习,热衷生活!😄

沉淀、分享、成长,让自己和他人都能有所收获!😄

一、前言

我们在使用Mybatis的时候,都会有这样子的一个疑问:“为什么Mybatis只需定义一个接口,不用写实现类就能使用XML中或者注解中的SQL语句完成对数据库的CRUD呢?”。看过Mybatis源码之后才知道原来Mybatis使用了Mapper接口代理类,把所有的数据库操作都交给了代理类处理。

二、Binding模块

这个Mapper接口代理类在Binding模块,核心类是org.apache.ibatis.binding.MapperProxyBinding模块核心类如下:

  • org.apach.ibatis.binding.MapperRegistryMapper接口注册类,管理Mapper接口类型和其代理创建工作的映射,我们在开发中创建的Mapper接口类都会注册到这里进行管理。
  • org.apach.ibatis.binding.MapperProxyFactoryMapper接口代理类创建工厂类。
  • org.apach.ibatis.binding.MapperProxyMapper接口代理类,封装了SqlSession相关操作,这个我们后续会学习到,是一个SQL执行。
  • org.apach.ibatis.binding.MapperMethod:封装Mapper接口对应的方法和SQL执行信息,这个就是我们发开中创建的Mapper接口类每个方法对应XML配置的SQL语句。

流程图如下:

三、设计

通常如果能找到大家所做事情的共性内容,具有统一的流程处理,那么它就是可以被凝聚和提炼的,封装成通用的组件或者服务,被所有人共用减少重复的无用功。

参考我们最开使用的JDBC的方法,从获取数据库连接、查询、封装结果集、返回结果集,这些步骤都是一个固定的流程,那么这个流程我们是可以封装成一个通用组件或者服务的。

当我们来设计一个ORM框架的过程中,首先要考虑怎么把用户定义的数据库操作接口、XML配置的SQL语句、数据库三者联系起来。其实最合适的操作就是会用代理的方法进行处理,因为代理可以封装一个复杂的流程为接口对象的实现类,设计如下图:

  • 首先提供一个Mappper接口代理类MapperPoxy,通过代理类包装对数据的操作,目前我们本章节会先提供一个简单的包装,模拟对数据库的调用。
  • 然后为Mapper接口代理类提供一个简单工厂类MapperProxyFactory调用instance方法为每个Mapper接口成为代理类。

四、实现

在实现之前,先对代理知识进行了一个学习,我们使用到的是动态代理模式,我们通过一个小demo对动态代理模式进行学习,还有另外两种代理模式:静态代理、Cglib代理。

动态代理

动态代理具有以下特点:

  • 动态代理对象不需要实现接口,只有目标对象需要实现接口。
  • 实现基于接口的动态代理需要利用JDK中的API,在JVM内存中动态的构建Proxy,是运行期生效。
  • 需要使用java.lang.reflect.Proxy和其newProxyInstance()方法,这个方法要传入三个参数,源码如下:

    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException{
        //...
    }
    • ClassLoader loader:指定当前目标对象使用的类加载,获取加载器的方法是getClassLoader()
    • interfaces:目标对象实现接口的类型,使用泛型方式确认类型。
    • InvocationHandler h:事件处理,执行目标方法对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。

我们通过具体的实现对动态代理进行一个操作,类结构图如下:

代码实现:

Mapper接口代理类:

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;

    private Map<String, String> sqlSession;

    private final Class<T> mapperInterface;

    public MapperProxy(Map<String, String> sqlSession, Class<T> mapperInterface) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            //Object中的方法,直接执行
            return method.invoke(this, args);
        } else {
            return "类" + mapperInterface.getName() + "被代理了, 执行了方法:" + method.getName() +
                    " ,sqlSession为:" + sqlSession;
        }
    }
}

Mapper接口代理类创建工厂类:

public class MapperProxyFactory<T> {

    /**
     * Mapper 接口类型
     */
    private final Class<T> mapperInterface;

    public MapperProxyFactory (Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public T newInstance(Map<String, String> sqlSession) {
        MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface);
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }
}

IUserDao类:

public interface IUserDao {
    String queryUserName(String uId);
}

测试类:

public class MapperProxyTest {

    @Test
    public void test() {
        MapperProxyFactory<IUserDao> factory = new MapperProxyFactory<IUserDao>(IUserDao.class);

        Map<String, String> sqlSession = new HashMap<>();
        sqlSession.put("cn.bugstack.mybatis.test.dao.IUserDao.queryUserName", "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名");
        sqlSession.put("cn.bugstack.mybatis.test.dao.IUserDao.queryUserAge", "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户年龄");

        IUserDao userDao = factory.newInstance(sqlSession);

        String res = userDao.queryUserName("1");
        System.out.println(res);
    }
}

测试结果如下:

类qtspace.cn.binding.IUserDao被代理了, 执行了方法:queryUserName ,sqlSession为:{cn.bugstack.mybatis.test.dao.IUserDao.queryUserAge=模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户年龄, cn.bugstack.mybatis.test.dao.IUserDao.queryUserName=模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名}

可以从上面的测试结果看出IUserDao被代理了,在执行MapperProxy#invoker()方法的时候代理执行IUserDao#queryUserName()方法。

需要注意的是:动态代理的方式中,所有的方法都会通过invoker()方法执行,但是动态代理有一个问题就是它只能代理实现了某个接口的实现类,并且代理类只能代理接口中实现的方法,要是实现类中有自己私有的方法,而接口中没有,该方法是不能被代理的。

阅读 470

热衷学习,热衷生活。

16 声望
1 粉丝
0 条评论

热衷学习,热衷生活。

16 声望
1 粉丝
宣传栏