Mybatis-Configuration加载

config.xml解析为org.w3c.dom.Document

本文首先来简单看一下MyBatis中将config.xml解析为org.w3c.dom.Document的流程,代码为上文的这部分:

1

2

3

4

5

6

7

8

9

static {

    try {

        reader = Resources.getResourceAsReader("mybatis/config.xml");

        ssf = new SqlSessionFactoryBuilder().build(reader);

    }

    catch (IOException e) {

        e.printStackTrace();

    }

}

第3行的代码实现为:

1

2

3

4

5

6

7

8

9

public static Reader getResourceAsReader(String resource) throws IOException {

    Reader reader;

    if (charset == null) {

      reader = new InputStreamReader(getResourceAsStream(resource));

    } else {

      reader = new InputStreamReader(getResourceAsStream(resource), charset);

    }

    return reader;

}

相当于就是将输入的路径转换为一个字符输入流并返回。

接着继续看静态块第4行的代码,new SqlSessionFactoryBuilder().build(reader),把代码定位到SqlSessionFactoryBuilder类的builder方法,这里使用了多态,直接跟到build方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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.

      }

    }

}

解析config.xml的代码在第3行XMLConfigBuilder类的构造方法中,看一下XMLConfigBuilder类的构造方法做了什么:

1

2

3

public XMLConfigBuilder(Reader reader, String environment, Properties props) {

      this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);

}

这里的关键是第二行代码的第一个参数XPathParser,看一下实例化XPathParser类的代码:

1

2

3

4

public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {

     commonConstructor(validation, variables, entityResolver);

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

}

第2行的代码commonConstructor方法没什么好看的,将validation、variables、entityResolver设置到XPathParser类的参数中而已,顺便再实例化一个javax.xml.xpath.XPath出来,XPath用于在XML文档中通过元素和属性进行导航,并对元素和属性进行遍历。

接着看第3行的createDocument方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

private Document createDocument(InputSource inputSource) {

    // important: this must only be called AFTER common constructor

    try {

      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

      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 {

        }

      });

      return builder.parse(inputSource);

    } catch (Exception e) {

      throw new BuilderException("Error creating document instance.  Cause: " + e, e);

    }

}

看一下第5行~第11行的代码设置DocumentBuilderFactory中参数的含义:

  • setValidating表示是否验证xml文件,这个验证是DTD验证
  • setNamespaceAware表示是否支持xml命名空间
  • setIgnoringComments表示是否忽略注释
  • setIgnoringElementContentWhitespace表示是否忽略元素中的空白
  • setCoalescing表示是否将CDATA节点转换为Text节点,并将其附加到相邻(如果有)的Text节点
  • setExpandEntityReferences表示是否扩展实体引用节点

第13行的代码由设置的参数从DocumentBuilderFactory中获取一个DocumentBuilder实例DocumentBuilderImpl,并由第14行的代码设置一个实体解析器,由第15行~第29行的代码设置一个错误处理器。

最后看一下第30行的代码parse方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public Document parse(InputSource is) throws SAXException, IOException {

        if (is == null) {

            throw new IllegalArgumentException(

                DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,

                "jaxp-null-input-source", null));

        }

        if (fSchemaValidator != null) {

            if (fSchemaValidationManager != null) {

                fSchemaValidationManager.reset();

                fUnparsedEntityHandler.reset();

            }

            resetSchemaValidator();

        }

        domParser.parse(is);

        Document doc = domParser.getDocument();

        domParser.dropDocumentReferences();

        return doc;

}

看过Spring配置文件解析源码的朋友应该对这一段代码比较熟悉,一样的,使用DocumentBuilder将解析InputSource成org.w3c.dom.Document并将Document存储到XPathParser中。

Document转换为Configuration

前面的代码将config.xml转换为了org.w3c.dom.Document,下一步就是将org.w3c.dom.Document中的内容转换为Java对象了,其中最主要的一个对象就是org.apache.ibatis.session.Configuration,还是回到之前的SqlSessionFactoryBuilder的build方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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.

      }

    }

}

先看一下第4行的parse方法,parse方法是XMLConfigBuilder中的,之前重点分析了它的属性XPathParser,看一下XMLConfigBuilder的parse方法是如何实现的:

1

2

3

4

5

6

7

8

public Configuration parse() {

    if (parsed) {

      throw new BuilderException("Each XMLConfigBuilder can only be used once.");

    }

    parsed = true;

    parseConfiguration(parser.evalNode("/configuration"));

    return configuration;

}

这里看一下第6行,可以使用XPathParser的evalNode方法解析标签,后面解析标签会大量用到此方法,此方法将标签解析为XNode,像config.xml(可见上一篇文章的示例)解析完之后的XNode,toString()方法输出的内容是这样的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

<configuration>

<properties resource="properties/db.properties"/>

<settings>

<setting name="cacheEnabled" value="true"/>

<setting name="lazyLoadingEnabled" value="true"/>

<setting name="useGeneratedKeys" value="true"/>

</settings>

<typeAliases>

<typeAlias alias="Mail" type="org.xrq.mybatis.pojo.Mail"/>

</typeAliases>

<environments default="development">

<environment id="development">

<transactionManager type="JDBC"/>

<dataSource type="POOLED">

<property name="driver" value="${driveClass}"/>

<property name="url" value="${url}"/>

<property name="username" value="${userName}"/>

<property name="password" value="${password}"/>

</dataSource>

</environment>

</environments>

<mappers>

<mapper resource="mybatis/mail.xml"/>

</mappers>

</configuration>

可见xml文件中<configuration>中所有内容都已经被成功解析并放在XNode中了,剩下的只要调用XNode的方法获取自己想要的内容即可。

最后扫一眼parseConfiguration方法,之所以说扫一眼,因为之后要分析里面的一些常用的和重点的内容,这里只是列一下代码而已:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

private void parseConfiguration(XNode root) {

    try {

      Properties settings = settingsAsPropertiess(root.evalNode("settings"));

      //issue #117 read properties first

      propertiesElement(root.evalNode("properties"));

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

    }

}

这里就是逐个解析<configuration>标签下的子标签,并将数据设置到对应的属性中,这里要一个一个看一下。

settings解析

首先看settingsAsPropertiess(root.evalNode(“settings”))这句代码,显而易见这句话获取了<configuration>下的<settings>节点。跟一下代码的实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

private Properties settingsAsPropertiess(XNode context) {

    if (context == null) {

      return new Properties();

    }

    Properties props = context.getChildrenAsProperties();

    // Check that all settings are known to the configuration class

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

    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;

}

第5行将节点解析成键值对的形式(Properties是Hashtable的子类),看一下props的toString方法打印的内容:

1

{useGeneratedKeys=true, lazyLoadingEnabled=true, cacheEnabled=true}

可见settings里面的数据已经被解析成了Properties了。之后还有一步,<settings>标签下的每个<setting>中的name属性不是随便填写的,都是MyBatis支持的配置,因此需要对Properties里面的Key做一个校验,校验的代码就是第7行至第12行的代码,其中有一个name不是MyBatis支持的就会抛出异常,MyBatis初始化整体失败。

至于具体校验的是哪些Key,这就要跟一下第7行的代码了,首先是MetaClass.forClass(Configuration.class, localReflectorFactory),第二个实参是XMLConfigBuilder里面直接new出来的,它的实际类型为DefaultReflectorFactory,看一下forClass方法实现:

1

2

3

public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {

     return new MetaClass(type, reflectorFactory);

}

看一下new MetaClass做了什么事:

1

2

3

4

private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {

     this.reflectorFactory = reflectorFactory;

     this.reflector = reflectorFactory.findForClass(type);

}

显而易见,继续跟一下第3行的代码DefaultRelectorFactory的findForClass方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

public Reflector findForClass(Class<?> type) {

    if (classCacheEnabled) {

            // synchronized (type) removed see issue #461

      Reflector cached = reflectorMap.get(type);

      if (cached == null) {

        cached = new Reflector(type);

        reflectorMap.put(type, cached);

      }

      return cached;

    } else {

      return new Reflector(type);

    }

}

不管怎么样都会执行new Reflector(type)这一句代码,看一下此时做了什么事,注意传入的参数是Configuration的class对象:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public Reflector(Class<?> clazz) {

    type = clazz;

    addDefaultConstructor(clazz);

    addGetMethods(clazz);

    addSetMethods(clazz);

    addFields(clazz);

    readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);

    writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);

    for (String propName : readablePropertyNames) {

      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);

    }

    for (String propName : writeablePropertyNames) {

      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);

    }

}

这么多方法至于具体要看哪个,要注意的是之前XMLConfigBuilder里面对于Key的判断是”!metaConfig.hasSetter(String.valueOf(key))”,代码的意思是判断的是否Key有set方法,那么显而易见这里要继续跟第5行的addSetMethods方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

private void addSetMethods(Class<?> cls) {

    Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();

    Method[] methods = getClassMethods(cls);

    for (Method method : methods) {

      String name = method.getName();

      if (name.startsWith("set") && name.length() > 3) {

        if (method.getParameterTypes().length == 1) {

          name = PropertyNamer.methodToProperty(name);

          addMethodConflict(conflictingSetters, name, method);

        }

      }

    }

    resolveSetterConflicts(conflictingSetters);

}

到这里应该很明显了,结论就是:<setting>的name属性对应的值,必须在Configuration类有相应的Setter,比如设置了一个属性useGenerateKeys方法,那么必须在Configuration类中有setUseGenerateKeys方法才行

顺便说一下第13行有一个resolveSetterConflicts方法,其作用是:Setter有可能在类中被重载导致有多个,此时取Setter中方法参数只有一个且参数类型与Getter一致的Setter

properties解析

接着看一下propertiesElement(root.evalNode(“properties”))方法,这句读取的是<configuration>下的<properties>节点,代码实现为:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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

    }

}

看到第4行~第7行的代码指定了MyBatis的<properties>标签下不能同时指定”resource”属性和”url”属性。

接着第9行~第13行的代码将.properties资源解析为Properties类,最后将Properties类设置到XPathParser和Configuration的variables属性中,variables是一个Propreties变量。

类型别名解析

跳过loadCustomVfs(settings)直接看typeAliasesElement(root.evalNode(“typeAliases”))这行,因为前者我也没看懂干什么用的,后者是用于定义类型的别名的,解析的是<configuration>下的<typeAliases>标签,用过MyBatis的应该很熟悉。看一下源码实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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

          }

        }

      }

    }

}

从源码实现中我们可以知道两点,<typeAliases>标签下可以定义<package>和<typeAlias>两种标签,但是看第4行和第7行的判断,这是一段if…else…,因此可以知道<package>标签和<typeAlias>标签只能定义其中的一种。首先看一下解析<package>标签的代码,第6行的registerAliases方法:

1

2

3

4

5

6

7

8

9

10

11

12

public void registerAliases(String packageName, Class<?> superType){

    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();

    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()) {

        registerAlias(type);

      }

    }

}

第3行根据路径packageName寻找它下面的”.class”文件拿到所有的”.class”文件对应的类的Class,然后遍历所有的Class,做了三层判断

  • 必须不是匿名类
  • 必须不是接口
  • 必须不是成员类

此时此Class对应的类符合条件,会进行注册,通过registerAlias方法进行注册,看一下方法实现:

1

2

3

4

5

6

7

8

public void registerAlias(Class<?> type) {

    String alias = type.getSimpleName();

    Alias aliasAnnotation = type.getAnnotation(Alias.class);

    if (aliasAnnotation != null) {

      alias = aliasAnnotation.value();

    }

    registerAlias(alias, type);

}

第2行获取Class的simpleName,simpleName指的是移除了包名的名称,比如aa.bb.cc.Mail,getSimpleName()获取的就是Mail。

第3行获取类上面的注解Alias,如果Alias注解中有定义value属性且指定了值,那么第4行~第6行的判断优先取这个值作为Class的别名。

第7行注册别名:

1

2

3

4

5

6

7

8

9

10

11

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

}

其实就做了两步操作:

  1. 将alias全部小写
  2. 将alias以及Class对象放到TYPE_ALIASES中,TYPE_ALIASES是一个HashMap

这样一个流程,就将<package>标签name属性路径下的Class(如果符合要求),全部放到了HashMap中以供使用。

接着看一下<typeAlias>标签的解析,也就是前面说的else部分:

1

2

3

4

5

6

7

8

9

10

11

12

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

}

这里先解析<typeAlias>中的alias属性,再解析<typeAlias>中的type属性,当然alias也可以不定义,不定义走的就是第6行的registerAlias方法,定义走的就是第8行的registerAlias方法,这两个重载的registerAlias方法前面也都说过了,就不说了。

默认typeAlias

上面说的是自定义typeAlias,MyBatis本身也默认提供给开发者了一些typeAlias定义,在两处地方。第一处地方在Configuration的构造方法中:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

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

    ...

}

第二处地方是在TypeAliasRegistry的构造方法中:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

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

}

对于这些数据,我们可以直接使用registerAlias方法的第一个参数对应的字符串而不需要定义这些typeAlias。

元素设置

继续MyBatis的Configuration加载源码分析:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

private void parseConfiguration(XNode root) {

    try {

      Properties settings = settingsAsPropertiess(root.evalNode("settings"));

      //issue #117 read properties first

      propertiesElement(root.evalNode("properties"));

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

    }

}

上回看到了第7行的<typeAlias>标签的解析,后面先暂时跳过<plugins>、<objectFactory>、<objectWrapperFactory>、<reflectorFactory>、<typeHandlers>、<databaseIdProvider>这几部分,这几部分要么属于MyBatis中不太常用的,要么属于MyBatis中比较进阶的应用,之后再说。

现在先看一下元素设置的代码,即第12行的settingsElement方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

private void settingsElement(Properties props) throws Exception {

    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));

    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));

    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));

    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));

    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));

    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));

    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));

    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));

    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));

    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));

    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));

    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));

    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));

    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));

    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));

    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));

    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));

    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));

    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));

    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));

    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), false));

    configuration.setLogPrefix(props.getProperty("logPrefix"));

    @SuppressWarnings("unchecked")

    Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));

    configuration.setLogImpl(logImpl);

    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));

}

看到这个方法的实现主要就是将之前解析出来的<settings>中的内容设置到Configuration中。好像一直忘了说一个事,Configuration是XMLConfigBuilder的父类BaseBuilder中的一个属性,BaseBuilder中存储了三个重要属性,画一张图来表示一下:

environments加载

接着就是<environments>的加载了,一个比较重要的属性,用于配置JDBC信息,对应的是environmentsElement(root.evalNode(“environments”))这句代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

private void environmentsElement(XNode context) throws Exception {

    if (context != null) {

      if (environment == null) {

        environment = context.getStringAttribute("default");

      }

      for (XNode child : context.getChildren()) {

        String id = child.getStringAttribute("id");

        if (isSpecifiedEnvironment(id)) {

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

        }

      }

    }

}

第3行~第5行的代码,得到默认的JDBC环境名称。

第6行的代码开始遍历<environments>标签下的每一个<environment>标签,先第7行的代码获取<environment>下的id属性,接着第8行的代码判断当前的<environment>是不是默认的JDBC环境,也就是第3行~第5行代码获取到的default属性对应的值。从这段代码可以看出两个问题:

  • 源码并没有对不满足第8行判断即不是默认<environment>的场景做判断,因此可以得到一个结论:<environments>标签下的default属性是一个必填属性。
  • 即使配置再多的<environment>标签,MyBatis只会加载其中的一个<environment>

第9行的代码根据<transactionManager>标签获取事物管理器,本系列文章配置的是”JDBC”,那么实例化出来的是JdbcTransactionFactory(JDBC–>JdbcTransactionFactory的对应关系在Configuration构造函数配置的alias映射中),其他的还有ManagedTransactionFactory和SpringManagedTransactionFactory,其中前者是MyBatis原生支持的,后者是Spring框架支持的。

第10行的代码和第9行的代码差不多,根据<dataSource>标签获取数据源工厂DataSourceFactory,本系列文章配置的是”POOLED”,那么实例化出来的是PooledDataSourceFactory(POOLED–>PooledDataSourceFactory的对应关系在Configuration构造函数配置的alias映射中),其他的还有UnpooledDataSourceFactory和JndiDataSourceFactory。

第11行的代码根据DataSourceFactory获取DataSource,在MyBatis中根据配置分三种场景:

  • PooledDataSourceFactory对应的DataSource是PooledDataSource
  • UnpooledDataSourceFactory对应的DataSource是UnpooledDataSource
  • JndiDataSourceFactory对应的DataSource要去JNDI服务上去找

第12行~第15行的代码比较简单,根据TransactionFactory和DataSource创建一个Environment并设置到Configuration。

mapper加载

config.xml中两个最重要的标签,一个是<environment>(JDBC环境信息),另一个就是mapper(sql文件映射)了。mapper的加载是”mapperElement(root.evalNode(“mappers”))”这句代码,看一下实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

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.");

          }

        }

      }

    }

}

看到<mappers>下可以定义<mapper>和<package>两种子标签,它们同样是二选一的关系,即只能定义其中一种,这里先看package分支的内容即根据类路径加载Mapper就不看了,基本不用的,就看else分支里面的内容,即根据<mapper>标签解析sql映射。

接着第8行~第10行分别获取每一个<mapper>中的resource、url、mapperClass,接着下面的判断很有意思:

  • resource != null && url == null && mapperClass == null
  • resource == null && url != null && mapperClass == null
  • resource == null && url == null && mapperClass != null

这告诉我们了resource、url、mapperClass三个属性只能定义其中的一个,else分支中抛出的异常同样也印证了这一说法。本系列文章的例子定义的是resource且定义resource的方式最常用,因此进入第一个if判断。

第12行的代码上下文设置一下resource,不是很重要。

第13行的代码根据mapper文件路径获取InputStream,InputStream在之后将会被转为InputSource用来解析mapper文件。

第14行的代码获取一个XMLMapperBuilder,它的流程和上文分析的XMLConfigBuilder是一样的,里面也使用的是XPathParser将mapper文件解析为Document。

第15行的代码跟进去看一下实现,因为XMLMapperBuilder的parse方法和XMLConfigBuilder的parse方法有区别,毕竟解析的是两种MyBatis配置文件:

1

2

3

4

5

6

7

8

9

10

11

public void parse() {

    if (!configuration.isResourceLoaded(resource)) {

      configurationElement(parser.evalNode("/mapper"));

      configuration.addLoadedResource(resource);

      bindMapperForNamespace();

    }

    parsePendingResultMaps();

    parsePendingChacheRefs();

    parsePendingStatements();

}

第2行的代码判断了当前资源是否被加载过,如果没有被加载过则会执行第3行~第5行的代码。

首先是第3行的代码configurationElement:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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. Cause: " + e, e);

    }

}

第3行的代码获取当前mapper文件的namespace,namespace是一个很重要的属性,所有的<sql>、<resultMap>、<insert>、<delete>、<update>、<select>标签,它们的id都是和namespace绑定的,从而确保全局的唯一性,当namespace未定义或者为空字符串的时候,第5行就会抛出异常,因此每个mapper文件的namespace都是一个必填内容。

第7行的代码在MapperBuilderAssistant中设置了一下namespace,这样后文可以通过MapperBuilderAssistant拿namespace而不需要每次传一个String类型的参数。

第8行~第13行的代码分别用于解析<cache-ref>、<cache>、<parameterMap>、<resultMap>、<sql>、<select>、<insert>、<update>、<delete>这几个标签,逐个看一下:

cacheRefElement方法用于解析<cache-ref>标签,总结如下:

  • 解析完的CacheRef放在cacheRefMap中
  • cacheRefMap是一个HashMap
  • 位于Configuration对象中
  • Key为mapper文件的namespace,Value为<cache-ref>中配置的namespace

cacheElement方法用于解析<cache>标签,总结如下:

  • 会根据<cache>中配置的属性new出一个org.apache.ibatis.cache.Cache
  • 使用此Cache作为MyBatis缓存

parameterMapElement方法用于解析<parameterMap>标签,总结如下:

  • 解析完的ParameterMap放在parameterMaps中
  • parameterMaps是一个StrictMap
  • 位于Configuration对象中,StrictMap是HashMap的子类
  • Key为当前mapper的namespace+”.”+<parameterMap>标签中的id属性,Value为ParameterMap对象

resultMapElements方法用于解析<resultMap>标签在,总结如下:

  • 解析完的ResultMap放在resultMaps中
  • resultMaps是一个StrictMap,
  • 位于Configuration对象中
  • Key为当前mapper的namespace+”.”+<resultMap>标签中的id属性,Value为ResultMap对象

sqlElement方法用于解析<sql>标签,总结如下:

  • 解析完的内容放在sqlFragments中
  • sqlFragments是一个StrictMap
  • 位于XMLMapperBuilder对象中
  • Key为当前mapper的namespace+”.”+<sql>标签中的id属性,Value为sql这个XNode本身

buildStatementFromContext用于解析<select>、<insert>、<update>、<delete>这四个标签,总结如下:

  • 解析完的内容放在mappedStatements中
  • mappedStatements是一个StrictMap
  • 位于Configuration对象中
  • Key为当前mapper的namespace+”.”+<select>|<insert>|<update>|<delete>标签中的id属性,Value为MappedStatement对象

构建SqlSessionFactory

最后一步,构建SqlSessionFactory,回看前面SqlSessionFactoryBuilder的build方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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.

      }

    }

}

第4行方法的parser.parse()这句之前一直在分析,将配置文件转换为了MyBatis中定义的各种对象且绝大部分配置存储在Configuration中,少部分配置存储在XMLConfigBuilder的父类BaseBuilder中。

接着就是外层的build方法了,看下实现:

1

2

3

public SqlSessionFactory build(Configuration config) {

     return new DefaultSqlSessionFactory(config);

}

最终构建出来的SqlSessionFactory是DefaultSqlSessionFactory,以Configuration对象为形参。

参考文章:

http://www.importnew.com/24581.html

http://www.importnew.com/24563.html

猜你喜欢

转载自blog.csdn.net/wojiao228925661/article/details/81588457
今日推荐