MyBatis 3.5.4源码之旅一之初始化流程一

前言

mybatis是什么,跟传统的JDBC,HIBERNATE之间有什么优点这种我就不说了,我还是分享点比较实在的东西,因为源码比较多,也比较复杂,一行行走下去意义不大,这个也不是读源码的好方式,我们读源码不是为了看源码而读,而是要知道他的原理,知道他的思想,一些实现的细节,顺便学习下他的一些风格,一些技巧,一些设计模式,自己项目也可以参考参考等等。所以看源码我打算整理一个大致的结构,然后具体的一些细节就用提问回答的方式去看源码,只要有问题,我们就去看源码,到底是怎么实现的,是怎么个过程。同时我也不会说太多的导包,配置上的细节,主要还是在mybatis3.5.4-SNAPSHOT版本的源码上讲。

初始化流程

在这里插入图片描述
我们都知道,mybaits的基本用法,比如初始化的这段代码:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

上面的代码其实就是做了初始化的工作,把resources目录下的mybatis-config.xml配置文件读进来,然后进行构造,构造出SqlSessionFactory。看起来好像很简单,但是其实里面做了很多的事情,接下来我们就来看看吧。

初始化的问题

1.如何读入配置文件

我们可以看到Resources.getResourceAsStream(resource);这句,其实Resourcesmybatis自定义的类,首先先说一下这个classLoaderWrapper是在Resources初始化的时候创建的:

private static ClassLoaderWrapper classLoaderWrapper = new ClassLoaderWrapper();

看他的构造函数,是取获得系统类加载器,也就是我们常说的应用类加载器:

 ClassLoaderWrapper() {
    try {
      systemClassLoader = ClassLoader.getSystemClassLoader();
    } catch (SecurityException ignored) {
      // AccessControlException on Google App Engine
    }
  }

在这里插入图片描述

然后我们再看看Resources是怎么做的:

 public static InputStream getResourceAsStream(String resource) throws IOException {
    return getResourceAsStream(null, resource);
  }

跟进去,我们发现里面是通过classLoaderWrapper来获取输入流的:

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

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

会先获取类加载器:

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

你会发现,他要获取那么多类加载器,其实只有最后3个是有的,前面两个是null,而且都是同一个系统类加载器:
在这里插入图片描述
我们继续getResourceAsStream,你会发现,源码他拿那么多类加载器是为了读取输入流,我们知道加载器是可以从类路径里(比如target/classes/xxx)加载文件的,字节码文件,资源文件,所以不管哪个加载器,只要读进来了就可以了,而且有些加载器要求有/开头的,具体可以看JDK源码,就不深入讲了:

  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;
  }

2.SqlSessionFactoryBuilder的build构造方法做了什么

接下去我们就要研究这么简单的一句代码了,其实里面做了好多东西:

new SqlSessionFactoryBuilder().build(reader);

里面有一些build重载,有InputStream的最后都是调用了,如果是Reader那就是另外一个,只是输入流类型不一样,处理是一样的,都是创建XMLConfigBuilder后处理:

  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

看看XMLConfigBuilder构造方法:

 public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

首先是创建解析xml的对象XPathParser

  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();
  }

然后创建文档对象,这个属于JDK里的功能,具体有兴趣可以自己跟进去看看:

this.document = createDocument(new InputSource(inputStream));

继续回到XMLConfigBuilder创建this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props)

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
创建Configuration

首先得创建Configuration,一看就知道是配置的意思,对的,配置文件里的信息都会保存在这里面,看看他的构造函数,里面注册了很多内部要用的类型和他的名字,名字是小写的,比如事务,池化,缓存,日志等等:

  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);
  }

我们看一个注册方法registerAlias,其他基本类似的,他会把你的别名小写和类型联系起来:

  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    typeAliases.put(key, value);
  }

然后回来看super(new Configuration());父类的构造方法,保存了别名和类型处理注册中心:

  public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }

剩下的就是设置一些属性了:

 ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值