Mybatis3源码笔记(三)Configuration


1. XMLConfigBuilder

上一篇大致介绍了SqlSession的生成。在DefaultSqlSessionFactory的构造函数中就提到了Configuration这个对象。现在我们来看看Configuration的生成流程。

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

代码比较简单,就是根据XMLConfigBuilder根据配置文件XML来parse生成的。其实不用看代码,我们脑海中应该也有一个大致的Configuration的构成细节,肯定是根据mybatis-config.xml具体生成对应的组成属性。一般的mybatis-config.xml文件如下:



    
        
            
                
            
            
                
                
                
            
        
    

    
        
    


但是我们都知道XML的解析都有DTD文件来约束和验证,那我们常用的mybatis-config.xml是DTD肯定也是有的。在哪呢?秘密就在XMLConfigBuilder构造函数中的XMLMapperEntityResolver

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

  private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
  private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
  private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";

  private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
  
  ...

上面很明显就用到mybatis-3-config.dtdmybatis-3-mapper.dtd,同时为了兼容旧版本的ibatis,还用到了ibatis-3-config.dtdibatis-3-mapper.dtd

之前说过mybatis-3-mapper.dtd,那mybatis-3-mapper.dtd看名字,我们也能猜到是专门用来解析mapper的xml文件的,一般的样例如下:

<?xml version="1.0" encoding="UTF-8"?>




    

    
        
        
            
        
        
            
        
    

    

    
        
        
            
        
        
            
        
  



具体的dtd文件就不贴出来了,免的有凑字数的嫌疑。继续看代码:

public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
   //最终把XML文件生成document对象用来后面的解析工作
    this.document = createDocument(new InputSource(inputStream));
  }  
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    //本地异常日志记录(ThreadLocal)单例模式来记录每一次的执行过程,用来详细定位异常信息。(为了防止内存泄露,在finally里有reset的操作)
    ErrorContext.instance().resource("SQL Mapper Configuration");
    //记录入参props
    this.configuration.setVariables(props);
    this.parsed = false;
    //记录入参environment 
    this.environment = environment;
    this.parser = parser;
  }

  public Configuration parse() {
    //相同的XMLConfigBuilder对象只允许parse一次
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //开始解析configuration根节点
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

下面就到了重头戏Configuration的详细解析过程。其实前几项的解析相对来说比较简单,就是最后的mapper的解析比较复杂。

private void parseConfiguration(XNode root) {
    try {
      //解析properties节点(variables)
      propertiesElement(root.evalNode("properties"));
      //解析settings节点
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //根据settings内容解析VFS(虚拟文件系统vfsImpl)
      loadCustomVfs(settings);
      //根据settings内容解析log日志具体实现类(logImpl)
      loadCustomLogImpl(settings);
      //解析typeAliases节点(TypeAliasRegistry里注册对应的别名)
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析plugins节点(注册interceptorChain里记录对应的拦截器)
      pluginElement(root.evalNode("plugins"));
      //解析objectFactory节点(自定义objectFactory)
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析objectWrapperFactory节点(自定义objectWrapperFactory)
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析reflectorFactory节点(自定义reflectorFactory)
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //设置其它的setting参数
      settingsElement(settings);
      //解析environments节点(environment)解析放到objectFactory and objectWrapperFactory解析之后,具体原因参见issue117
      environmentsElement(root.evalNode("environments"));
      //解析databaseIdProvider节点(databaseId)
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析typeHandlers节点,注册类型转换器(typeHandlerRegistry)
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析mappers节点(重中之重)
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
下面逐一解析下各个节点
1. propertiesElement(root.evalNode("properties"))
  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      //解析所有子节点property
      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.");
      }
      //根据url或者resource生成对应的property集合
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      //入参中的variables如果也存在的话,一并放入defaults
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      parser.setVariables(defaults);
      //设置variables
      configuration.setVariables(defaults);
    }
  }
  
    
    
  

  
    
  
2. Properties settings = settingsAsProperties(root.evalNode("settings"))
  private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    Properties props = context.getChildrenAsProperties();
    // 利用MetaClass来检查Configuration
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    //检查下对应的setting的key值在configuration里存不存在
    for (Object key : props.keySet()) {
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    return props;
  }
  
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
  
3. loadCustomVfs(settings)根据setting解析Vfs(代码比较简单,就不注释了)
  private void loadCustomVfs(Properties props) throws ClassNotFoundException {
    String value = props.getProperty("vfsImpl");
    if (value != null) {
      String[] clazzes = value.split(",");
      for (String clazz : clazzes) {
        if (!clazz.isEmpty()) {
          @SuppressWarnings("unchecked")
          Class<? extends VFS> vfsImpl = (Class<? extends VFS>) Resources.classForName(clazz);
          configuration.setVfsImpl(vfsImpl);
        }
      }
    }
  }
4. loadCustomLogImpl(settings)根据setting解析log实现
private void loadCustomLogImpl(Properties props) {
  Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
  configuration.setLogImpl(logImpl);
}

这个resolveClass方法会经常用到,我们跟一下看看。

public  Class resolveAlias(String string) {
  try {
  if (string == null) {
  return null;
  }
  // issue #748
  String key = string.toLowerCase(Locale.ENGLISH);
  Class value;
  //根据typeAliases先去捞一波class,如果没有的话用Resources走classpath生成class
  if (typeAliases.containsKey(key)) {
  value = (Class) typeAliases.get(key);
  } else {
  value = (Class) Resources.classForName(string);
  }
  return value;
  } catch (ClassNotFoundException e) {
  throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
  }
}
public class TypeAliasRegistry {

private final Map> 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);
...
5. typeAliasesElement(root.evalNode("typeAliases"))解析typeAliases节点(TypeAliasRegistry里注册对应的别名),正好跟上前的resolveClass联动起来。
  private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //注册package包下面所有的Class,key:getSimpleName(),value:Class(不包括接口,内部类,匿名类)
        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);
          }
        }
      }
    }
  }