花里胡哨系列之Mybatis源码---配置文件解析(二)

一.配置文件介绍

    官网描述:MyBatis 的配置文件会深深影响 MyBatis 行为的设置和属性信息

    配置文件基本结构

  

二.配置文件解析流程

String resource = "org/mybatis/example/mybatis-config.xml";
//将 配置文件路径转为输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
//根据输入流 创建SqlSessionFactory 
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

      配置文件路径转为输入流,这一步很简单,最终调用 ClassLoader.getResourceAsStream(String name) 得到输入流,重点关注 SqlSessionFactoryBuilder().build(inputStream)

SqlSessionFactoryBuilder:

public SqlSessionFactory build(InputStream inputStream) {
  return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    //这一步其实是将输入流转为 Document对象封装在 XMLConfigBuilder,之后利用得到的Document获取配置文件中的相关信息
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    //解析document中的内容
    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.
    }
  }
}

  //看看bulid方法

public SqlSessionFactory build(Configuration config) {
//直接返回DefaultSqlSessionFactory,也就是我们要的最终结果SqlSessionFactory 
  return new DefaultSqlSessionFactory(config);
}

      这里稍微看一下将输入流解析成Document流程

XMLConfigBuilder:

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
  //创建XPathParser,在这个构造函数里面解析Xml,然后创建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;
}
XPathParser:
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
  commonConstructor(validation, variables, entityResolver);
  //创建 Document
  this.document = createDocument(new InputSource(inputStream));
}
private Document createDocument(InputSource inputSource) {
  // important: this must only be called AFTER common constructor
  try {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
    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 {
        // NOP
      }
    });
    //解析成Document对象
    return builder.parse(inputSource);
  } catch (Exception e) {
    throw new BuilderException("Error creating document instance.  Cause: " + e, e);
  }
}

      Document对象拿到了,那么需要将里面的东西取出来细分,不然怎么说是面向对象。接下来分析 XMLConfigBuilder是如何解析Document对象的。

XMLConfigBuilder:

public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  /**
   * 这个configuration就是配置文件的标签
   * <configuration>
   *   <environments default="development">
   *     <environment id="development">
   *       <transactionManager type="JDBC"/>
   *       <dataSource type="POOLED">
   *         <property name="driver" value="${driver}"/>
   *         <property name="url" value="${url}"/>
   *         <property name="username" value="${username}"/>
   *         <property name="password" value="${password}"/>
   *       </dataSource>
   *     </environment>
   *   </environments>
   *   <mappers>
   *     <mapper resource="org/mybatis/example/BlogMapper.xml"/>
   *   </mappers>
   * </configuration>
   */
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}
private void parseConfiguration(XNode root) {
  try {
    //issue #117 read properties first
    //解析properties标签
    propertiesElement(root.evalNode("properties"));
    //解析setting配置,并转换为properties
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    //加载vfs?
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    // 解析 typeAliases 配置
    typeAliasesElement(root.evalNode("typeAliases"));
    // 解析 plugins 配置
    pluginElement(root.evalNode("plugins"));
    // 解析 objectFactory 配置
    objectFactoryElement(root.evalNode("objectFactory"));
    // 解析 objectWrapperFactory 配置
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    // 解析 reflectorFactory 配置
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    // settings 中的信息设置到 Configuration 对象中
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    // 解析 environments 配置
    environmentsElement(root.evalNode("environments"));
    // 解析 databaseIdProvider,获取并设置 databaseId 到 Configuration 对象
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    // 解析 typeHandlers 配置
    typeHandlerElement(root.evalNode("typeHandlers"));
    // 解析 mappers 配置
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

      大家可以看到,将configuration标签传进来后,parseConfiguration内的一些列方法基本是按官网给出的配置文件属性进行解析的。接下来,会分析properties,settings,typeAliases,environments标签,至于其它的标签暂不分析(其实都差不多,只是变量名称不一样。。)

 1. properties

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

   

/**
 * 解析properties标签,先解析子节点property内容,
 * 再解析resource或者url内容,可能导致 后面属性覆盖property
 * @param context
 * @throws Exception
 */
private void propertiesElement(XNode context) throws Exception {
  if (context != null) {
    /**
     * <properties resource="jdbc.properties">
     * <property name="jdbc.username" value="coolblog"/>
     * <property name="hello" value="world"/>
     * </properties>
     */
    //解析子节点 property
    Properties defaults = context.getChildrenAsProperties();
    String resource = context.getStringAttribute("resource");
    String url = context.getStringAttribute("url");
    //properties 标签中的url和resource属性不可共存
    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) {//解析property文件
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if (url != null) {//解析url资源文件
      defaults.putAll(Resources.getUrlAsProperties(url));
    }
    Properties vars = configuration.getVariables();
    if (vars != null) {
      defaults.putAll(vars);
    }
    parser.setVariables(defaults);
    //设置到 configuration,可以供其他标签使用
    configuration.setVariables(defaults);
  }
}

   //解析 property子标签

public Properties getChildrenAsProperties() {
  Properties properties = new Properties();
  for (XNode child : getChildren()) {//遍历子节点
    String name = child.getStringAttribute("name");
    String value = child.getStringAttribute("value");
    if (name != null && value != null) {
      properties.setProperty(name, value);
    }
  }
  return properties;
}

 2.settings

   这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为,详见官网:https://mybatis.org/mybatis-3/zh/configuration.html

   这个标签总体来说是简单的,一起看看吧

   

private Properties settingsAsProperties(XNode context) {
  if (context == null) {
    return new Properties();
  }
  /**
   * <settings>
   * <setting name="cacheEnabled" value="true"/>
   * <setting name="lazyLoadingEnabled" value="true"/>
   * <setting name="autoMappingBehavior" value="PARTIAL"/>
   * </settings>
   */
//解析 子标签
  Properties props = context.getChildrenAsProperties();
  // Check that all settings are known to the configuration class
  //创建 Configuration 类的“元信息”对象,用于校验
  MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
  for (Object key : props.keySet()) {
    // 检测 Configuration 中是否存在相关属性,不存在则抛出异常
    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;
}

       子标签解析没什么好说的,关键看看

       MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); 

      MetaClass里面封装了Configuration类的基本信息,如构造函数,get,set方法 

       根据Configuration里面的字段,校验配置文件里面的信息,防止出现配置错误,先看看Configuration类的基本字段:

      

    这些字段和官网提供的settings基本对的上,看看是如何将Configuration这个类封装成 MetaClass

MetaClass:
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
  this.reflectorFactory = reflectorFactory;
  //通过反射获取 Class的各种信息,封装到 Reflector 类中
  this.reflector = reflectorFactory.findForClass(type);
}

public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
  return new MetaClass(type, reflectorFactory);
}

  

DefaultReflectorFactory:
@Override
public Reflector findForClass(Class<?> type) {
  if (classCacheEnabled) {
    // synchronized (type) removed see issue #461,创建Reflector对象
    return reflectorMap.computeIfAbsent(type, Reflector::new);
  } else {
    return new Reflector(type);
  }
}
Reflector:

 

public Reflector(Class<?> clazz) {
  type = clazz;
  //封装默认构造函数
  addDefaultConstructor(clazz);
  //封装 get(is)方法
  addGetMethods(clazz);
  //封装set方法
  addSetMethods(clazz);
  //添加所有字段
  addFields(clazz);
  readablePropertyNames = getMethods.keySet().toArray(new String[0]);
  writablePropertyNames = setMethods.keySet().toArray(new String[0]);
  for (String propName : readablePropertyNames) {
    caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
  }
  for (String propName : writablePropertyNames) {
    caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
  }
}

   //这里分析一下 get方法的封装流程

private void addGetMethods(Class<?> clazz) {
  Map<String, List<Method>> conflictingGetters = new HashMap<>();
  //获取所有方法和父类方法
  Method[] methods = getClassMethods(clazz);
  //条件:入参个数为0 且 是 get方法, 加入 conflictingGetters
  Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
    .forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
  //解决get冲突, 如 getXXX 和 isXXX。根据返回类型判断
  resolveGetterConflicts(conflictingGetters);
}

  

private Method[] getClassMethods(Class<?> clazz) {
  Map<String, Method> uniqueMethods = new HashMap<>();
  Class<?> currentClass = clazz;
  while (currentClass != null && currentClass != Object.class) {
    //获取当前类的所有方法,公有,私有,默认,保存在uniqueMethods中
    addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());

    // we also need to look for interface methods -
    // because the class may be abstract
    //获取接口的所有方法
    Class<?>[] interfaces = currentClass.getInterfaces();
    for (Class<?> anInterface : interfaces) {
      addUniqueMethods(uniqueMethods, anInterface.getMethods());
    }
    //父类
    currentClass = currentClass.getSuperclass();
  }

  Collection<Method> methods = uniqueMethods.values();

  return methods.toArray(new Method[0]);
}
private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
  for (Method currentMethod : methods) {
    if (!currentMethod.isBridge()) {
      //获取方法名称:返回类型 + “#” +方法名+参数类型名
      String signature = getSignature(currentMethod);
      // check to see if the method is already known
      // if it is known, then an extended class must have
      // overridden a method
      if (!uniqueMethods.containsKey(signature)) {
        uniqueMethods.put(signature, currentMethod);
      }
    }
  }
}
PropertyNamer:
public static boolean isGetter(String name) { //对于 以get开头或者以 is开头的方法名都算get方法
  return (name.startsWith("get") && name.length() > 3) || (name.startsWith("is") && name.length() > 2);
}

  // 如果存在 getAge()和 isAge()方法,会产生冲突,因为都是用Age作为Map的key保存在 conflictingGetters变量中

public static String methodToProperty(String name) {
  if (name.startsWith("is")) {
    name = name.substring(2);
  } else if (name.startsWith("get") || name.startsWith("set")) {
    name = name.substring(3);
  } else {
    throw new ReflectionException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
  }

  if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
    name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
  }

  return name;
}

   //解决 get 冲突

private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
  for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
    Method winner = null;
    String propName = entry.getKey();
    boolean isAmbiguous = false;
    for (Method candidate : entry.getValue()) {
      if (winner == null) {
        winner = candidate;
        continue;
      }
      //获取返回值类型
      Class<?> winnerType = winner.getReturnType();
      Class<?> candidateType = candidate.getReturnType();
      /*
       * 两个方法的返回值类型一致,若两个方法返回值类型均为 boolean,
       * 则选取 isXXX 方法为 winner。否则无法决定哪个方法更为合适,  只能抛出异常,isAmbiguous为true表示抛异常
       */
      if (candidateType.equals(winnerType)) {
        if (!boolean.class.equals(candidateType)) {
          //返回类型相同,但都是非boolean类型,抛异常
          isAmbiguous = true;
          break;
        } else if (candidate.getName().startsWith("is")) {
          winner = candidate;
        }
      } else if (candidateType.isAssignableFrom(winnerType)) {//父子关系,选择子类合适
        // OK getter type is descendant
      } else if (winnerType.isAssignableFrom(candidateType)) {
        winner = candidate;
      } else {
        isAmbiguous = true;
        break;
      }
    }
    addGetMethod(propName, winner, isAmbiguous);
  }
}

  

private void addGetMethod(String name, Method method, boolean isAmbiguous) {
  //isAmbiguous,异常标志位
  MethodInvoker invoker = isAmbiguous
      ? new AmbiguousMethodInvoker(method, MessageFormat.format(
          "Illegal overloaded getter method with ambiguous type for property ''{0}'' in class ''{1}''. This breaks the JavaBeans specification and can cause unpredictable results.",
          name, method.getDeclaringClass().getName()))
      : new MethodInvoker(method);
  getMethods.put(name, invoker);
  Type returnType = TypeParameterResolver.resolveReturnType(method, type);
  getTypes.put(name, typeToClass(returnType));
}

     获取到 Configuration的所有方法后,对 get方法进行过滤,这里的get有些特殊,是指以get或者 is 开头的方法;对于其他set方法,构造函数,都是利用反射获取相关信息,自行分析

3.typeAliases

  类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写

  常见配置: 

<typeAliases> 
 <package name="xyz.coolblog.chapter2.model1"/>
 <package name="xyz.coolblog.chapter2.model2"/>
 <typeAlias alias="author" type="xyz.coolblog.chapter2.model.Author" />
</typeAliases>

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

   通过配置包名,或者 直接指定具体类型和别名

XMLConfigBuilder:
private void typeAliasesElement(XNode parent) {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      /**
       * <typeAliases>
       * <package name="xyz.coolblog.chapter2.model1"/>
       * </typeAliases>
       */
      if ("package".equals(child.getName())) {//扫描包
        String typeAliasPackage = child.getStringAttribute("name");
        configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
      } else {//直接指定具体类型和别名
        /**
         * <typeAliases>
         * <typeAlias alias="article" type="xyz.coolblog.chapter2.model.Article" />
         * </typeAliases>
         */
        String alias = child.getStringAttribute("alias");
        String type = child.getStringAttribute("type");
        try {
          Class<?> clazz = Resources.classForName(type);
          if (alias == null) {
            //没有配置别名,默认以类名为别名,尝试获取@Alias注解的值
            typeAliasRegistry.registerAlias(clazz);
          } else {
             //注册别名
            typeAliasRegistry.registerAlias(alias, clazz);
          }
        } catch (ClassNotFoundException e) {
          throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
        }
      }
    }
  }
}

  看看包扫描过程

TypeAliasRegistry:
public void registerAliases(String packageName) {
  registerAliases(packageName, Object.class);
}

public void registerAliases(String packageName, Class<?> superType) {
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
  // 查找某个包下的父类为 superType 的类。从调用栈来看,这里的
  // superType = Object.class,所以 ResolverUtil 将查找所有的类。
  // 查找完成后,查找结果将会被缓存到内部集合中。这里面用到了 VFS,虚拟文件系统
  resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  //获取到包下的所有类
  Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
  for (Class<?> type : typeSet) {
    // Ignore inner classes and interfaces (including package-info.java)
    // Skip also inner classes. See issue #6
    if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
      //有Alias注解直接用注解,没有用class的getSimpleName当别名
      registerAlias(type);
    }
  }
}
ResolverUtil:
public ResolverUtil<T> find(Test test, String packageName) {
  String path = getPackagePath(packageName);

  try {//获取包路径下所有类(全限定类名),虚拟文件系统,默认使用DefaultVFS 扫描包下的所有类
    List<String> children = VFS.getInstance().list(path);
    for (String child : children) {
      if (child.endsWith(".class")) {//判断是否以.class结尾
        //添加到set集合
        addIfMatching(test, child);
      }
    }
  } catch (IOException ioe) {
    log.error("Could not read package: " + packageName, ioe);
  }

  return this;
}

  

protected void addIfMatching(Test test, String fqn) {
  try {
    //将 全限定类名中的 / 替换成 .
    String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
    ClassLoader loader = getClassLoader();
    if (log.isDebugEnabled()) {
      log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
    }
    //加载class
    Class<?> type = loader.loadClass(externalName);
    //判断是否是Object.class的子类。。return type != null && parent.isAssignableFrom(type);
    if (test.matches(type)) {
      matches.add((Class<T>) type);
    }
  } catch (Throwable t) {
    log.warn("Could not examine class '" + fqn + "'" + " due to a " +
        t.getClass().getName() + " with message: " + t.getMessage());
  }
}

     对于DefaultVFS 里面具体实现逻辑这里不分析,只需要知道通过该类可以扫描包下面的所有文件对应的路径名称.对此感兴趣的可以私聊我或者底下评论。。。

 4.environments

    常见配置:

<!--default 代表选择哪个 environment(id) -->
<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="" value=""/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>
  • 默认使用的环境 ID(比如:default="development")。
  • 每个 environment 元素定义的环境 ID(比如:id="development")。
  • 事务管理器的配置(比如:type="JDBC")。
  • 数据源的配置(比如:type="POOLED")。

XMLConfigBuilder:

private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      //代表当前环境
      environment = context.getStringAttribute("default");
    }
    for (XNode child : context.getChildren()) {
      //id代表environment环境,用于生产,测试切换
      String id = child.getStringAttribute("id");
      // 检测当前 environment 节点的 id 与其父节点 environments 的
      // 属性 default 内容是否一致,一致则返回 true,否则返回 false
      if (isSpecifiedEnvironment(id)) {
        //事务管理器,分两种
        /**
         * (1)使用JDBC的事务管理机制,就是利用java.sql.Connection对象完成对事务的提交
         * (2)使用MANAGED的事务管理机制,这种机制mybatis自身不会去实现事务管理,而是让程序的容器自定义实现
         */
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}

  //解析事务管理器

  

private TransactionFactory transactionManagerElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    //获取属性值
    Properties props = context.getChildrenAsProperties();
    //反射创建对象
    TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    //设置属性
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a TransactionFactory.");
}

解析数据源:

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
  if (context != null) {
    //POOLED,UNPOOLED,JNDI 三种类型
    String type = context.getStringAttribute("type");
    //获取属性,这一步会将 value="${driver}" 这类表达式转为真实的值
    Properties props = context.getChildrenAsProperties();
    //反射创建对象,先从别名集合中查找type对应的真实类名
    DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

     下面来看看如何将 value="${driver}"转为真实值步骤,其实最终会到 <properties/>标签解析后,保存的变量中寻找值

public Properties getChildrenAsProperties() {
  Properties properties = new Properties();
  for (XNode child : getChildren()) {//遍历子节点,getChildren会转换${}这类表达式为真实值
    String name = child.getStringAttribute("name");
    String value = child.getStringAttribute("value");
    if (name != null && value != null) {
      properties.setProperty(name, value);
    }
  }
  return properties;
}
public List<XNode> getChildren() {
  List<XNode> children = new ArrayList<>();
  NodeList nodeList = node.getChildNodes();
  if (nodeList != null) {
    for (int i = 0, n = nodeList.getLength(); i < n; i++) {
      Node node = nodeList.item(i);
      if (node.getNodeType() == Node.ELEMENT_NODE) {
        //在XNode构造函数中完成 ${}表达式的解析,variables存储properties标签的结果
        children.add(new XNode(xpathParser, node, variables));
      }
    }
  }
  return children;
}
public XNode(XPathParser xpathParser, Node node, Properties variables) {
  this.xpathParser = xpathParser;
  this.node = node;
  this.name = node.getNodeName();
  this.variables = variables;
  //属性解析
  this.attributes = parseAttributes(node);
  this.body = parseBody(node);
}
private Properties parseAttributes(Node n) {
  Properties attributes = new Properties();
  NamedNodeMap attributeNodes = n.getAttributes();
  if (attributeNodes != null) {
    for (int i = 0; i < attributeNodes.getLength(); i++) {
      Node attribute = attributeNodes.item(i);
      //解析特殊表达式 ${}
      String value = PropertyParser.parse(attribute.getNodeValue(), variables);
      attributes.put(attribute.getNodeName(), value);
    }
  }
  return attributes;
}
PropertyParser:
public static String parse(String string, Properties variables) {
  VariableTokenHandler handler = new VariableTokenHandler(variables);
  //构建表达式解析器
  GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
  //解析
  return parser.parse(string);
}

// 这个方法的基本思路:根据 表达式,确定表达式的起始位置("${"), 结束位置("}"), 由此 确定真实表达式内容的始末位置

public String parse(String text) {
  if (text == null || text.isEmpty()) {
    return "";
  }
  // search open token
  int start = text.indexOf(openToken);
  if (start == -1) {
    return text;
  }
  char[] src = text.toCharArray();
  int offset = 0;
  final StringBuilder builder = new StringBuilder();
  StringBuilder expression = null;
  while (start > -1) {
    if (start > 0 && src[start - 1] == '\\') {
      // this open token is escaped. remove the backslash and continue.
      builder.append(src, offset, start - offset - 1).append(openToken);
      offset = start + openToken.length();
    } else {
      // found open token. let's search close token.
      if (expression == null) {
        expression = new StringBuilder();
      } else {
        expression.setLength(0);
      }
      builder.append(src, offset, start - offset);
      offset = start + openToken.length();
      int end = text.indexOf(closeToken, offset);
      while (end > -1) {
        if (end > offset && src[end - 1] == '\\') {
          // this close token is escaped. remove the backslash and continue.
          expression.append(src, offset, end - offset - 1).append(closeToken);
          offset = end + closeToken.length();
          end = text.indexOf(closeToken, offset);
        } else {//获取真实表达式, 如${abc} ,expression = "abc"
          expression.append(src, offset, end - offset);
          break;
        }
      }
      if (end == -1) {
        // close token was not found.
        builder.append(src, start, src.length - start);
        offset = src.length;
      } else {//这一步将表达式 转为?,或者真实值,看具体实现类,同时保存 参数相关信息
        builder.append(handler.handleToken(expression.toString()));
        offset = end + closeToken.length();
      }
    }
    start = text.indexOf(openToken, offset);
  }
  if (offset < src.length) {
    builder.append(src, offset, src.length - offset);
  }
  return builder.toString();
}
VariableTokenHandler:

   

@Override
public String handleToken(String content) {
  if (variables != null) {
    String key = content;
    if (enableDefaultValue) {//默认false
      final int separatorIndex = content.indexOf(defaultValueSeparator);
      String defaultValue = null;
      if (separatorIndex >= 0) {
        key = content.substring(0, separatorIndex);
        defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
      }
      if (defaultValue != null) {
        return variables.getProperty(key, defaultValue);
      }
    }
    if (variables.containsKey(key)) {
      //从已经解析好的 Properties标签中获取对应的值,总算逮住你了,嘿嘿,还跑吗
      return variables.getProperty(key);
    }
  }
  return "${" + content + "}";
}

Environment结束,当然,其中数据源还是有点东西值得细究一下

 UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。

 POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。

  这个放后面,在sql执行,获取数据库连接的时候讲更合适。

三.小结

   配置文件的解析流程已经快结束,不过还有最重要的一块:映射文件的解析,这一块的代码有点难读,结果映射,尤其是动态SQL,各种标签,以及和接口绑定步骤,放在下一章分析。

   最后来一张流程图:

   

  结语:万事开头难

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值