MyBatis源码分析(三)

之前已经讲了一个MyBatis项目的基本配置,接下来我们来通过断点跟踪,一步一步揭开它的神秘面纱.

@Slf4j
public class App 
{
    public static void main( String[] args ) throws IOException {
        //通过配置文件获取输入流
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        //构建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //打开session
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //第四步 获取Mapper接口对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //第五步 调用Mapper接口对象的方法操作数据库
        User user = mapper.selectByPrimaryKey(1);
        //获取结果,处理业务
        log.info("查询结果:",user.getId());
    }
}

1.如何通过配置文件获取输入流

//第一步,调用
  public static InputStream getResourceAsStream(String resource) throws IOException {
    return getResourceAsStream(null, resource);
  }
//第二步调用
  public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
    InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
    if (in == null) {
      throw new IOException("Could not find resource " + resource);
    }
    return in;
  }

通过进入源码,我们可以看到通过配置文件获取输入流最核心的一步  classLoaderWrapper.getResourceAsStream(resource, loader);

那么classLoaderWrapper这个成员变量又是如何来的呢?原来是Resource的静态成员变量

  private static ClassLoaderWrapper classLoaderWrapper = new ClassLoaderWrapper();

在静态变量实例化的时候,我把关键的几行代码粘出来.只是给成员变量systemClassLoader赋值.

public class ClassLoaderWrapper {

  ClassLoader defaultClassLoader;
  ClassLoader systemClassLoader;

  ClassLoaderWrapper() {
    try {
      systemClassLoader = ClassLoader.getSystemClassLoader();
    } catch (SecurityException ignored) {
      // AccessControlException on Google App Engine
    }
  }
  ClassLoader[] getClassLoaders(ClassLoader classLoader) {
    return new ClassLoader[]{
        classLoader,
        defaultClassLoader,
        Thread.currentThread().getContextClassLoader(),
        getClass().getClassLoader(),
        systemClassLoader};
  }

}

我们回到classLoaderWrapper.getResourceAsStream(resource, loader),可以看出最终是通过类加载器去读取配置文件获取输入流

  public InputStream getResourceAsStream(String resource) {
    return getResourceAsStream(resource, getClassLoaders(null));
  }

  InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
    for (ClassLoader cl : classLoader) {
      if (null != cl) {

        // try to find the resource as passed
        InputStream returnValue = cl.getResourceAsStream(resource);

        // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
        if (null == returnValue) {
          returnValue = cl.getResourceAsStream("/" + resource);
        }

        if (null != returnValue) {
          return returnValue;
        }
      }
    }
    return null;
  }

类加载器是一个数组,一共五个类加载器,前两个初次是没有赋值的,可以最终的到一个APP-classLoader,根据;类加载机制双亲委派,三种类型加载器

BootStrapClassLoader-ExtClassLoader-APPClassLoader

  ClassLoader[] getClassLoaders(ClassLoader classLoader) {
    return new ClassLoader[]{
        classLoader,
        defaultClassLoader,
        Thread.currentThread().getContextClassLoader(),
        getClass().getClassLoader(),
        systemClassLoader};
  }

第一步,我们可以总结为简单的一句话:通过类加载器读取类的根路径下的配置文件获取输入流,我们以后的开发也可以借鉴这种方式

2.构建SqlSessionFactory

 //构建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

这一步主要是build方法,前面的创建对象调用默认无参构造,并没有做什么事情.

  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

调用同一个类的重载方法

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

重点在于:XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

我们通过传递可知,后两个参数environment为null,properties为null.

  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
new XPathParser(inputStream, true, props, new XMLMapperEntityResolver())这一步到底做了什么?
首先看下new XMLMapperEntityResolver(),你会在下图看到熟悉的dtd,你会想到什么?这一步也只是初始化了内部成员变量.
MyBatis源码分析(三)_第1张图片

我们再看下new XPathParser(inputStream, true, props, new XMLMapperEntityResolver())下一步调用
 public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(inputStream));
  }
首先进入第一个方法内部一探究竟,给成员变量赋值
  private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }

然后是通过输入流,构建Document对象,赋值给成员变量document

MyBatis源码分析(三)_第2张图片

 
 
  private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
        }
      });
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }
这里面一大堆到底做了什么呢,大致就是设置xml的验证,毕竟我们要保证xml的配置是符合MyBatis的配置书写规则,不然后续的解析也是有问题的.
XPathParser解析完毕之后,XPathParser此时成员变量已经赋值完毕,调用重载方法
MyBatis源码分析(三)_第3张图片

 首先看一下new Configuration()做了什么?里面的成员变量响应初始化,我们暂且不看,看看无参构造干了什么.下面的代码是不是很熟悉?好像在哪里见过?

没错,这些我们在配置mybatis-config.xml的时候见过,通过别名可以找到对应的类,而我们配置文件只需要写别名,方便我们的记忆和配置

  public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
  }
ErrorContext.instance().resource("SQL Mapper Configuration");这个是什么呢,我们也进去瞅一眼

MyBatis源码分析(三)_第4张图片

   我们一眼就可以看到ThreadLocal,想到了什么吗?指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据.这个是错误上下文,打印异常使用,可以认为是线程单例,因为它针对的级别是线程.

我们再次回到SqlSessionFactory的build方法

MyBatis源码分析(三)_第5张图片

 调用parser.parse()的方法

MyBatis源码分析(三)_第6张图片

 

 

 XPATH解析/configuration,解析之后我们会把所有解析获取的配置信息防止到成员变量configuration里面

MyBatis源码分析(三)_第7张图片

 最终我们获取的SqlSessionFactory的时候,里面的Configuration成员变量就包含了我们xml里面配置的所有解析出来的信息

3.打开session  SqlSession sqlSession = sqlSessionFactory.openSession();

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

调用一下方法,这里面顾名思义,根据我我们解析出来的配置信息,拿到数据源,构建出事务对象,然后创建出默认的SqlSession

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

MyBatis源码分析(三)_第8张图片

 sqlSession里面都是诸如此类的方法DefaultSqlSession做了实现

4.获取Mapper接口对象  UserMapper mapper = sqlSession.getMapper(UserMapper.class);

我们去看下getMapper的实现,我们传入了接口类型,但是我们并没有具体的实现类代码,看看Mybatis是如何做的。

  @Override
  public  T getMapper(Class type) {
    return configuration.getMapper(type, this);
  }

再向下断点调试

 public  T getMapper(Class type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

这个不是核心,接着向下调用,下面这是重点,我们来看一看

  @SuppressWarnings("unchecked")
  public  T getMapper(Class type, SqlSession sqlSession) {
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
private final Map, MapperProxyFactory> knownMappers = new HashMap<>();
knownMappers这个就是一个HashMap,这个map是时候放进去值的呢?
其实我们在解析xml的时候,MyBatis已经悄悄地放进去了,不信请看,这个有一点就是,此刻其实也已经把mapper.xml解析数据放入configuration里面了
  
    
        "com/bjpowernode/mapper/UsersMapper.xml"/>
    

MyBatis源码分析(三)_第9张图片

通过这种不知不觉的方法,就把我们的mapper接口类型放进了map里面

MyBatis源码分析(三)_第10张图片

 

 所以我们最后一定会执行

return mapperProxyFactory.newInstance(sqlSession);
如果我们对于反射和动态代理有了解的话,是不是很熟悉,动态代理实例化。下面的代码也证实了这一点,如果对于动态代理不太了解

 

MyBatis源码分析(三)_第11张图片

 我们先来看一下MapperProxy的方法

 MyBatis源码分析(三)_第12张图片

 上面有一个经典面试题:动态代理,投鞭断流

final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);

 我们直接看最后一个地方,前一个地方cachedMapperMethod(method),这个地方字面应该是缓存,略过

MyBatis源码分析(三)_第13张图片

 这里根据mapper.xml里面的标签确定是什么操作,最后把结果集返回

5.调用Mapper接口对象的方法操作数据库

当我们调用mapper接口的方法,通过动态代理,底层自动给我们生成动态代理对象,执行exceute方法,返回结果集。

6.获取结果,处理业务

此时我们通过获取的是结果集封装到对象的数据,我们就可以处理我们的业务。

这是大概的一个源码流程,后续做更进一步的讲解。

你可能感兴趣的:(MyBatis源码分析(三))