乐趣区

关于java: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() 办法执行,然而动静代理有一个问题就是它只能代理实现了某个接口的实现类,并且代理类只能代理接口中实现的办法,要是实现类中有本人公有的办法,而接口中没有,该办法是不能被代理的。

退出移动版