最近翻了翻mybatis初始化部分的源码,和大家分享一下阅读源码的过程和乐趣

mybatis源码解析

1.初始化过程

静态代码块读取主配文件

static {
        String config = "mybatis-config.xml";
        try {
            //第一步加载主配文件
            InputStream resourceAsStream = Resources.getResourceAsStream(config);
            //第二步解析主配文件构建sqlSession工厂
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

1.1初始化结构图

最近翻了翻mybatis初始化部分的源码,和大家分享一下阅读源码的过程和乐趣_第1张图片

1.2初始化步骤

第一步

将mybatis-config.xml主配文件加载到内存中。

InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");

mybatis中封装了一个工具类Resources中的getResourceAsStream方法用来获取主配文件的输入流,该方法默认使用系统类加载器加载指定文件。

  • Resources类结构

最近翻了翻mybatis初始化部分的源码,和大家分享一下阅读源码的过程和乐趣_第2张图片

classLoaderWrapper 字段和getResourceAsStream方法:

classLoaderWrapper字段是ClassLoaderWrapper类型 ,是mybatis对ClassLoader(类加载器)的封装

所以getResourceAsStream底层调用的是ClassLoader中的getResourceAsStream方法。

Resources类

public static InputStream getResourceAsStream(String resource) throws IOException {
    return getResourceAsStream(null, resource); //传入的类加载器为null,调用重载方法
  }

  /**
   * Returns a resource on the classpath as a Stream object
   *
   * @param loader   The classloader used to fetch the resource
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
// getResourceAsStream重载方法
  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类

最近翻了翻mybatis初始化部分的源码,和大家分享一下阅读源码的过程和乐趣_第3张图片

可以看到该类中有两个ClassLoader类型的字段,所以ClassLoaderWapper本质上是对ClassLoder的封装,

该类中的getResourceAsStream方法:

//ClassLoaderWapper中的方法
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) {
            //调用ClassLoader中的getResourceAsStream方法
          returnValue = cl.getResourceAsStream("/" + resource);
        }

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

从源码中可以看出该方法本质上是调用的ClassLoader类中的getResourceAsStream方法返回指定文件的输入流。

第一步总结:mybatis通过Resources工具类一步步调用ClassLoader中方法来加载主配文件并非返回该文件的输入流。

第二步

解析主配文件然后将主配文件中的信息存放在Configuration类中并将该信息类作为参数来创建一个DefaultSqlSessionFactory对象(DefaultSqlSessionFactory是SqlSessionFactory接口的实现类(也可以说DefaultSqlSessionFactory对象就是SqlSessionFactory对象)),构建DefaultSqlSessionFactory需要创建SqlSessionFactoryBuilder对象并调用该对象中的build方法来创建,说白了想要得到DefaultSqlSessionFactory对象就必须借助SqlSessionFactoryBuilder中的build方法。这就是建造者模式

sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

build方法内部是如何工作的,我们看源码。

build方法:

//依次向下调用重载方法 
public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

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

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

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        //XMLConfigBuilder 故名意思xml配置构建者,用来构建Configuration类
      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.
      }
    }
  }
//真正产生对象的方法
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

在build方法中发现一个XMLConfigBuilder类,该类是用来构建Configuration配置信息类的。

Configuration类长这样

protected Environment environment;

  protected boolean safeRowBoundsEnabled;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase;
  protected boolean aggressiveLazyLoading;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls;
  protected boolean useActualParamName = true;
  protected boolean returnInstanceForEmptyRow;
  protected boolean shrinkWhitespacesInSql;
  protected boolean nullableOnForEach;

  protected String logPrefix;
  protected Class<? extends Log> logImpl;
  protected Class<? extends VFS> vfsImpl;
  protected Class<?> defaultSqlProviderType;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  protected ResultSetType defaultResultSetType;
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

  protected Properties variables = new Properties();
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  protected boolean lazyLoadingEnabled = false;
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

  protected String databaseId;
  /**
   * Configuration factory class.
   * Used to create Configuration for loading deserialized unread properties.
   *
   * @see Issue 300 (google code)
   */
  protected Class<?> configurationFactory;

  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");

  protected final Set<String> loadedResources = new HashSet<>();
  protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

  /*
   * A map holds cache-ref relationship. The key is the namespace that
   * references a cache bound to another namespace and the value is the
   * namespace which the actual cache is bound to.
   */
  protected final Map<String, String> cacheRefMap = new HashMap<>();

  public Configuration(Environment environment) {
    this();
    this.environment = environment;
  }

该类中定义了很多属性,这些属性就是用来映射xml文件中的表签的,除此之外还有很多get、set方法。

对Configuration类有了大概的认识后,接着来看XMLConfigBuilder类

源码:

//XMLConfigBuilder类

 private boolean parsed;
  private final XPathParser parser;
  private String environment;
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();


//该类有很多构造方法 依次向下调用
public XMLConfigBuilder(Reader reader) {
    this(reader, null, null);
  }

  public XMLConfigBuilder(Reader reader, String environment) {
    this(reader, environment, null);
  }

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

  public XMLConfigBuilder(InputStream inputStream) {
    this(inputStream, null, null);
  }

  public XMLConfigBuilder(InputStream inputStream, String environment) {
    this(inputStream, environment, null);
  }

  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    //new XPathParser XPath解析器解析xml文件中各个节点信息的工具
      this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //调用父类BaseBuilder 的构造方法
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

XMLConfigBuilder是抽象类BaseBuilder的子类。在调用构造方法时最终调用了父类BaseBuilder的构造方法。

BaseBuilder类源码

protected final Configuration configuration;
  protected final TypeAliasRegistry typeAliasRegistry;
  protected final TypeHandlerRegistry typeHandlerRegistry;

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

该类中有3个protected修饰的属性:

  • Configuration :前面介绍了他是用来存储xml解析后信息的类。
  • TypeAliasRegistry:别名注册表,是用来存放别名信息的 点开它的源码会发现它的底层就是一个map集合用来存储一些实体类的别名或者mybatis自带的别名例如Map集合 的别名就是 map 这一点从它的构造方法中就可以看出。
private final Map<String, Class<?>> typeAliases = new HashMap<>();

//构造方法中初始化很多别名
public TypeAliasRegistry() {
  registerAlias("string", String.class);

  registerAlias("byte", Byte.class);
  registerAlias("long", Long.class);
  registerAlias("short", Short.class);
  registerAlias("int", Integer.class);
  registerAlias("integer", Integer.class);
  registerAlias("double", Double.class);
  registerAlias("float", Float.class);
  registerAlias("boolean", Boolean.class);

  registerAlias("byte[]", Byte[].class);
  registerAlias("long[]", Long[].class);
  registerAlias("short[]", Short[].class);
  registerAlias("int[]", Integer[].class);
  registerAlias("integer[]", Integer[].class);
  registerAlias("double[]", Double[].class);
  registerAlias("float[]", Float[].class);
  registerAlias("boolean[]", Boolean[].class);

  registerAlias("_byte", byte.class);
  registerAlias("_long", long.class);
  registerAlias("_short", short.class);
  registerAlias("_int", int.class);
  registerAlias("_integer", int.class);
  registerAlias("_double", double.class);
  registerAlias("_float", float.class);
  registerAlias("_boolean", boolean.class);

  registerAlias("_byte[]", byte[].class);
  registerAlias("_long[]", long[].class);
  registerAlias("_short[]", short[].class);
  registerAlias("_int[]", int[].class);
  registerAlias("_integer[]", int[].class);
  registerAlias("_double[]", double[].class);
  registerAlias("_float[]", float[].class);
  registerAlias("_boolean[]", boolean[].class);

  registerAlias("date", Date.class);
  registerAlias("decimal", BigDecimal.class);
  registerAlias("bigdecimal", BigDecimal.class);
  registerAlias("biginteger", BigInteger.class);
  registerAlias("object", Object.class);

  registerAlias("date[]", Date[].class);
  registerAlias("decimal[]", BigDecimal[].class);
  registerAlias("bigdecimal[]", BigDecimal[].class);
  registerAlias("biginteger[]", BigInteger[].class);
  registerAlias("object[]", Object[].class);

  registerAlias("map", Map.class);
  registerAlias("hashmap", HashMap.class);
  registerAlias("list", List.class);
  registerAlias("arraylist", ArrayList.class);
  registerAlias("collection", Collection.class);
  registerAlias("iterator", Iterator.class);

  registerAlias("ResultSet", ResultSet.class);
}
  • TypeHandlerRegistry:类型处理程序注册表,它用于数据库类型与Java类型之间的转换(双向转换)例如在获取结果时将数据库中的VARCHAR类型转换为Java的String类型。底层使用的是Java原生JDBC中ResultSet结果集中的getSring(String columnLabel)、setString(int parameterIndex, String x)方法。

让我们重新回到XMLConfigBuilder类中,上面说到该类继承了BaseBuilder中的3个属性。new 完XMLConfigBuiler对象后,这三个属性对应的实体对象还是没有数据的,所以下一步就是解析xml文件并将信息封装到对应的属性中。

XMLConfigBuilder中的方法:

最近翻了翻mybatis初始化部分的源码,和大家分享一下阅读源码的过程和乐趣_第4张图片

可以看到XMLConfigBuilder中有很多私有方法这些方法基本都是用来向BaseBuilder中的那3个属性设置值的。

私有方法被调用只能是在公有方法中。

parse方法:


public Configuration parse() {
    //判断是否解析过防止浪费资源
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
   
    parsed = true;
    //参数 parser 是 XpathPraser 的实体对象 也是XMLConfigBuilder中的属性 上文介绍过了
    //它是正真干活的工具  
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

 

private final XPathParser parser;

XPathParser源码:

 private final Document document; //文档树对象
  private boolean validation;
  private EntityResolver entityResolver;
  private Properties variables;
  private XPath xpath;  //内置了一个XPath选择器
  
 public XNode evalNode(String expression) {
    return evalNode(document, expression);
  }

  public XNode evalNode(Object root, String expression) {
    Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    if (node == null) {
      return null;
    }
    return new XNode(this, node, variables);
  }

//其余方法省略

看XPathParser源码 ,可以看到它有两个重要的属性 Document 和 XPath 有js基础的同学应该知道这两个类对应的是什么,在前端的HTML中 ,我们把整个HTML文件抽象成一个文档树就是父标签包含子标签的那种,XPath则是一个在文档书中查找指定节点(标签)的方法。在XML文件中也是这么一个文档书。再看evalNode方法的返回值是XNode类型的,XNode就是mybatis对Node类的一个封装就类似于上文说的ClassLoaderWapper和ClassLoader的关系一样。所以XNode就代表一个节点(标签)通过找到节点来拿到节点对应的值。 那么evalNode方法就是根据给定节点名称来返回一个节点对象,对象里面包含着节点的信息。

parse方法中的代码 parseConfiguration(parser.evalNode(“/configuration”)); 参数中/configuration就是主配置文件的根节点。有了根节点就可以获取其子节点,从而解析全部的节点。

接着来看XMLConfigBuilder中的parseConfiguration方法

 private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

该方法就是对所有标签依次解析。最后将解析后的值封装到Configuration类中 并通过该类拿到SqlSessionFactory。

第三步

解析映射文件

mapperElement(root.evalNode("mappers"));

XMLConfigBuilder类中的mapperElement方法:

 private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
          //判断是否为多文件注册
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
            //单文件注册的 3种解析方式
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
            
            // resource方式解析
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              mapperParser.parse();
            }
              //url方式解析
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            try(InputStream inputStream = Resources.getUrlAsStream(url)){
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
              mapperParser.parse();
            }
              //class方式解析
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

在主配文件中注册mapper分为单文件注册和多文件注册

  • 单文件注册:

    • 使用节点的resource属性直接映射XML文件。
    • 使用节点url属性映射网络或者磁盘路径下的某个XML文件。
    • 使用节点的class属性直接映射某个mapper接口。
  • 多文件注册:

    • 使用节点的package属性将包内的映射器接口实现全部注册为映射器

源码中 使用 resource 和 url 的 方式中 可以看到 XMLMapperBuilder 类,打开可以看到 这个类和XMLConfigBuilder类很相似,只是前者多了一个用于存储SQL片段XNode节点的Map集合。

XMLMapperBuilder类:

public class XMLMapperBuilder extends BaseBuilder {

  private final XPathParser parser;
  private final MapperBuilderAssistant builderAssistant;
  private final Map<String, XNode> sqlFragments;
  private final String resource;

er>节点的resource属性直接映射XML文件。

  • 使用节点url属性映射网络或者磁盘路径下的某个XML文件。

  • 使用节点的class属性直接映射某个mapper接口。

  • 多文件注册:

    • 使用节点的package属性将包内的映射器接口实现全部注册为映射器

源码中 使用 resource 和 url 的 方式中 可以看到 XMLMapperBuilder 类,打开可以看到 这个类和XMLConfigBuilder类很相似,只是前者多了一个用于存储SQL片段XNode节点的Map集合。

XMLMapperBuilder类:

public class XMLMapperBuilder extends BaseBuilder {

  private final XPathParser parser;
  private final MapperBuilderAssistant builderAssistant;
  private final Map<String, XNode> sqlFragments;
  private final String resource;

你可能感兴趣的:(框架源码,mybatis,java,mysql)