zoukankan      html  css  js  c++  java
  • MyBatis源码解读(2)——MapperProxy

    SqlSession可以说是整个MyBatis的重中之重,在SqlSession中涉及到前一篇四大对象:Executor、StatementHandler、ParameterHandler、ResultHandler,所以在此先只对SqlSession有一个大概的了解。

    在代码中我们可以看到当我们构造出一个SqlSession实例过后,可以通过SqlSession构造出Mappper映射器。UserMapper是一个接口,那么我们可以肯定的是,它一定是用了Java的动态代理生成了一个代理类。

    SqlSession sqlSession = SessionFactory.getSqlSession(resource); 
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    接着上一篇讲到通过DefaultSqlSessionFactory可以得到一个SqlSession的实例DefaultSqlSession。

    通过源代码可以发现,SqlSessionManger又实现了SqlSession,在上一节中可知SqlSessionManager同样也继承了SqlSessionFactory接口。我们把它们结合起来看看。

    看来这个SqlSessionManager有点神奇,同时继承SqlSession和SqlSessionFactory。从名字上来看好像是管理SqlSession的,这里不讨论,随着源代码的阅读相信我们能逐步清晰,包括整个包的结构。

    回到DefaultSqlSession中来。在SqlSession接口中提供了很多方法,用于我们的增删改查,这在旧版的MyBatis或者iBatis中常常所使用的,我们现在大多直接使用xml配置文件以达到更加灵活的效果。所以我们将注意力放在getMapper方法上。

    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    注意UserMapper仅仅是一个接口,这里会涉及到Java的动态代理,所以得要有一定的基础才能读懂。

    通过打断点调试我们可以发现确实产生了一个叫MapperProxy的代理类。

    下面是DefaultSqlSession的getMapper方法:

    //org.apache.ibatis.session.default.DefaultSqlSession
    public
    <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }

    看起来语法有点奇怪,这是一个泛型方法。看来是调用了Configuration的getMapper方法,还不是DefaultSqlSession实现了getMapper。接着再看Configuration的getMapper方法:

    //org.apache.ibatis.session.Configuration
    public
    <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }

     Configuration.getMapper一共两个参数,一个是Class类型,一个是SqlSession,在DefaultSqlSession.getMapper调用Configuration.getMapper时,将传递进来的Class类型参数和其本身传递给了Configuration.getMapper。此时还不是在Configuration中实现了getMapper,看来还是一个叫做mapperRegistry的变量。

    //org.apache.ibatis.session.Configuration
    protected
    final MapperRegistry mapperRegistry = new MapperRegistry(this);

    看着名字好像是注册Mapper映射器的地方,想来也是,既然要得到Mapper的映射,那么所有的Mapper都要一个地方去注册(在我们的mybytis-config.xml里),注册好过后需要的时候再去查找是否已经注册,那么就是MapperRegistry,所以取一个好的变量名是非常重要的。

     1 //org.apache.ibatis.binding.MapperRegistry
     2 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
     3   final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
     4   if (mapperProxyFactory == null) {
     5     throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
     6   }
     7   try {
     8     return mapperProxyFactory.newInstance(sqlSession);
     9   } catch (Exception e) {
    10     throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    11   }
    12 }

    在第3行代码中试图从一个叫knownMappers的变量取出MapperProxyFactory。这个knownMapper的定义:

    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

    既然能用get方法取,那说明就有add方法咯?果不其然我们在MapperRegistry类中发现了public <T> void addMapper(Class<T> type)方法,那么是在哪里调用的这个方法呢?

    我们来重新理一理。

    使用MyBatis的第一步是配置mybatis-config.xml,配置好过后,mybatis-config跑起来的第一步也一定是首先解析xml配置文件,将解析好的配置文件各个配置参数放入Configuration对象中,包括Mapper的配置,所以应该是在解析xml文件的某个类中解析过来后调用Configuration的方法将mapper放置到MapperRegister中。事实也的确如此,有兴趣可以跟踪下代码看看。回到MapperRegistry.getMapper的方法中。

    当我们一切正确时,我们就能获取到一个MapperProxyFactory实例。想必MapperProxy代理类的生成正是通过MapperProxyFactory工厂类构建的,即第8行代码。进入MapperProxyFactory类。

    //org.apache.ibatis.binding.MapperProxyFactory
    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }

    在这里终于看到了MapperProxy代理类,是通过sqlSession、mapperInterface、mechodCache三个参数构造的。

    newInstance有一个重载方法:

    //org.apache.ibatis.binding.MapperProxyFactory
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }

    终于是走到头了,这里就是返回的一个代理类实例。最后来看看MapperProxy。

    MapperProxy是一个重要的类,所以我们将其代码全部贴出:

     1 //org.apache.ibatis.binding.MapperProxy
     2 public class MapperProxy<T> implements InvocationHandler, Serializable {
     3 
     4   private static final long serialVersionUID = -6424540398559729838L;
     5   private final SqlSession sqlSession;
     6   private final Class<T> mapperInterface;
     7   private final Map<Method, MapperMethod> methodCache;
     8 
     9   public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    10     this.sqlSession = sqlSession;
    11     this.mapperInterface = mapperInterface;
    12     this.methodCache = methodCache;
    13   }
    14 
    15   @Override
    16   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    17     if (Object.class.equals(method.getDeclaringClass())) {
    18       try {
    19         return method.invoke(this, args);
    20       } catch (Throwable t) {
    21         throw ExceptionUtil.unwrapThrowable(t);
    22       }
    23     }
    24     final MapperMethod mapperMethod = cachedMapperMethod(method);
    25     return mapperMethod.execute(sqlSession, args);
    26   }
    27 
    28   private MapperMethod cachedMapperMethod(Method method) {
    29     MapperMethod mapperMethod = methodCache.get(method);
    30     if (mapperMethod == null) {
    31       mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
    32       methodCache.put(method, mapperMethod);
    33     }
    34     return mapperMethod;
    35   }
    36 
    37 }

    要使用Java的动态代理就必须得实现InvocationHandler接口,在第17行代码中首先判断代理对象是一个接口还是一个类,显然我们没有对mapper接口进行任何实现,那么它将生成一个MapperMethod对象,接着调用其execute方法,把sqlSession和参数传递进去。

  • 相关阅读:
    后端程序员必备的 Linux 基础知识+常见命令(近万字总结)
    信息收集流程
    在不影响程序使用的情况下添加shellcode
    使用Zolom内存解析运行python脚本(不落地)
    要点3:输入函数对比与自定义输入方式
    要点2:循环、条件控制
    对等连接和云联网
    上传自定义镜像到腾讯云
    Windows 激活
    MySQL错误(报错)一览表(对照表)
  • 原文地址:https://www.cnblogs.com/yulinfeng/p/6067867.html
Copyright © 2011-2022 走看看