# Mybatis源码解析之配置加载

Posted 叶长风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了# Mybatis源码解析之配置加载相关的知识,希望对你有一定的参考价值。

Mybatis源码解析之配置加载(二)

这一篇是承接上一篇文章Mybatis源码解析之配置加载(一),上一篇原本是想把整个配置加载都分析完全,然后发现内容还是比较多,所以决定分成两篇来说好了,现在就开始剩下的配置分析。

配置加载


继续回到parseConfiguration方法中,**parseConfiguration()**方法如下:

private void parseConfiguration(XNode root) 
    try 
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(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);
    
  

properties加载


我们首先看properties的加载:

private void propertiesElement(XNode context) throws Exception 
    if (context != null) 
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) 
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      
      if (resource != null) 
        defaults.putAll(Resources.getResourceAsProperties(resource));
       else if (url != null) 
        defaults.putAll(Resources.getUrlAsProperties(url));
      
      Properties vars = configuration.getVariables();
      if (vars != null) 
        defaults.putAll(vars);
      
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    
  

这里properties其实到没有多少好说的,其实就是我们一般在配置db连接的时候往往都是使用单独的一个db.properties这种,不是直接配置到xml中,当然我们上一篇文章就简便起见都配置都conf.xml文件中了,一般配置都是如下:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db
jdbc.username=root
jdbc.password=root

<!-- 加载数据库属性文件 -->
<properties resource="db.properties">

然后上述解析properties文件就是解析resource节点,得到数据库配置信息。

解析typeAliases


接下来我们看typeAliases,settings的解析与加载与properties差不多,解析typeAliases源码如下:

private void typeAliasesElement(XNode parent) 
    if (parent != null) 
      for (XNode child : parent.getChildren()) 
        if ("package".equals(child.getName())) 
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
         else 
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try 
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) 
              typeAliasRegistry.registerAlias(clazz);
             else 
              typeAliasRegistry.registerAlias(alias, clazz);
            
           catch (ClassNotFoundException e) 
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          
        
      
    
  

这里对alias进行了注册,转到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 (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) 
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    
    TYPE_ALIASES.put(key, value);
  

实质上就是将对应的alias名与class保存进map中,最后在使用的时候再从map中取出来,这个在后面使用的时候再来讲这段。

解析插件


解析plugin的过程:

private void pluginElement(XNode parent) throws Exception 
    if (parent != null) 
      for (XNode child : parent.getChildren()) 
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      
    
  

这里的plugin都是作为单独的一个个功能增强来使用,所以拦截器是最好的表现形式,这里的**configuration.addInterceptor()**里面就是一个InterceptorChain,这个InterceptorChain在初始化的时候会将每一个plugin加入到chain中,然后在查询的时候一个个过滤,看是否需要使用插件,这个在后面也会说到。

解析mapper


前面几种解析原理都差不多,就不再详述了,最重要的还是mapper的加载,我们转到

mapperElement(root.evalNode("mappers"))

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 
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) 
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
           else if (resource == null && url != null && mapperClass == null) 
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
           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的具体过程,但在我们上一篇文章中的对应配置为:

<mappers>
        <mapper resource="cn/com/mapper/userMapper.xml"/>
</mappers>

所以对应的解析条件为else中的第二个if条件,如下:

InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();

在加载xml文件后,对xml进行了解析,进入到parse()方法中。

public void parse() 
    if (!configuration.isResourceLoaded(resource)) 
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  

我们转到**configurationElement()**方法中

private void configurationElement(XNode context) 
    try 
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) 
        throw new BuilderException("Mapper's namespace cannot be empty");
      
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
     catch (Exception e) 
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    
  

重点都是在这个方法了,首先确定当前mapper属于哪一个namespace。

String namespace = context.getStringAttribute("namespace");
builderAssistant.setCurrentNamespace(namespace);

接下来这两行:

cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));

是对缓存的处理,这个就留到讲缓存的时候再说吧,暂且跳过。

最重要的是对resultMap的加载:

resultMapElements(context.evalNodes("/mapper/resultMap"));

private void resultMapElements(List<XNode> list) throws Exception 
    for (XNode resultMapNode : list) 
      try 
        resultMapElement(resultMapNode);
       catch (IncompleteElementException e) 
        // ignore, it will be retried
      
    
  
  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception 
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) 
      if ("constructor".equals(resultChild.getName())) 
        processConstructorElement(resultChild, typeClass, resultMappings);
       else if ("discriminator".equals(resultChild.getName())) 
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
       else 
        List<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) 
          flags.add(ResultFlag.ID);
        
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      
    
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try 
      return resultMapResolver.resolve();
     catch (IncompleteElementException  e) 
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    
  

这些里面其实最重要的就是**return resultMapResolver.resolve();**这行了,这一行对resultMap进行了最终的解析,这里面就不再讲述了,有兴趣的可以看下。

接下来看Mapper中的sql的解析:

sqlElement(context.evalNodes("/mapper/sql"));
private void sqlElement(List<XNode> list) throws Exception 
    if (configuration.getDatabaseId() != null) 
      sqlElement(list, configuration.getDatabaseId());
    
    sqlElement(list, null);
  

  private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception 
    for (XNode context : list) 
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      id = builderAssistant.applyCurrentNamespace(id, false);
      if (databaseIdMatchesCurrentMybatis之Mapper调用源码分析

mybatis源码解析之Configuration加载

mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)

mybatis源码-解析配置文件之解析的流程

mybatis源码-解析配置文件之配置文件Configuration解析(超详细, 值得收藏)

mybatis源码配置文件解析之五:解析mappers标签(解析XML映射文件)