Mybatis3源碼分析「第二篇」加載Configuration-XMLConfigBuilder


Mybatis3源碼分析「第二篇」加載Configuration-XMLConfigBuilder

文章內索引

  • Configuration類在Mybatis中的作用
  • Configuration的屬性
  • 從mybatis-config.xml文件中對應的屬性
  • 從Mapper配置文件中讀取的屬性
  • Configuration加載過程
  • XMLConfigBuilder.parse()方法
  • 加載properties節點
  • 加載別名
  • 加載Mapper配置文件
  • 加載其他配置項

Configuration類在Mybatis中的作用

Configuration類保存了所有Mybatis的配置信息。也就是說mybaits-config.xml及UserMapper.xml中所有配置信息都可以在Configruation對象中找到相應的信息。一般情況下Mybatis在運行過程中只會創建一個Configration對象,並且配置信息不能再被修改。如何配置Mybatis可以看這個文檔:http://mybatis.org/mybatis-3/zh/configuration.html

Configuration的屬性

configuration的屬性主要分為兩大部分:

  1. 從mybatis-config.xml中讀取的配置
  2. 從mapper配置文件或Mapper註解讀取的配置

下面簡單說一下這兩部分的屬性與配置的對應關係

從mybatis-config.xml文件中對應的屬性

<code>      protected boolean safeRowBoundsEnabled = false;      protected boolean safeResultHandlerEnabled = true;      protected boolean mapUnderscoreToCamelCase = false;      protected boolean aggressiveLazyLoading = true;      protected boolean multipleResultSetsEnabled = true;      protected boolean useGeneratedKeys = false;      protected boolean useColumnLabel = true;      protected boolean cacheEnabled = true;      protected boolean callSettersOnNulls = false;      protected String logPrefix;      protected Class  extends Log> logImpl;      protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;      protected JdbcType jdbcTypeForNull = JdbcType.OTHER;      protected Set<string> lazyLoadTriggerMethods = new HashSet<string>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));      protected Integer defaultStatementTimeout;      protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;      protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;      protected Properties variables = new Properties();      protected ObjectFactory objectFactory = new DefaultObjectFactory();      protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();      protected MapperRegistry mapperRegistry = new MapperRegistry(this);      protected boolean lazyLoadingEnabled = false;      protected ProxyFactory proxyFactory;      protected final InterceptorChain interceptorChain = new InterceptorChain();      protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();      protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();      protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();/<string>/<string>/<code>

以上屬性可以說都是由mybatis-config.xml文件中讀取的。

例如文件中的setting配置

<code>    <settings>      <setting>      <setting>      <setting>      <setting>      <setting>      <setting>      <setting>      <setting>      <setting>      <setting>      <setting>      <setting>      <setting>      <setting>    /<settings>/<code>

看配置的內容和Configuration中的屬性名稱,就大概知道對應關係。相信之後的解析內容了不會太複雜。

從Mapper配置文件中讀取的屬性

如下屬性是從Mapper配置文件中讀取的

<code>      protected final Map<string> mappedStatements = new StrictMap<mappedstatement>("Mapped Statements collection");      protected final Map<string> caches = new StrictMap<cache>("Caches collection");      protected final Map<string> resultMaps = new StrictMap<resultmap>("Result Maps collection");      protected final Map<string> parameterMaps = new StrictMap<parametermap>("Parameter Maps collection");      protected final Map<string> keyGenerators = new StrictMap<keygenerator>("Key Generators collection");/<keygenerator>/<string>/<parametermap>/<string>/<resultmap>/<string>/<cache>/<string>/<mappedstatement>/<string>/<code>

其中最主要的也是相對複雜的有如下兩個(Mapper配置文件也主要是配置這兩項):

  1. mappedStatements屬性,保存了所有Mapper配置文件中的select/update/insert/delete節點信息。屬性類型為一個Map,key為sql對應的ID,MappedSatement為一個java對象,保存了一個select/update/insert/delete的節點信息。
  2. resultMaps屬性,保存了所有Mapper配置文件中的resultMap節點。

Mapper配置文件也主要是配置select/update/insert/delete/resultMap這幾個節點。

Configuration加載過程

針對mybatis-config.xml配置文件和Mapper配置文件,Mybatis也是由兩個相對應的類來解析的。

  1. XMLConfigBuilder解析mybatis-config.xml的配置到Configuration中
  2. XMLMapperBuilder解析Mapper配置文件的配置到Configuration中

XMLConfigBuilder.parse()方法

通過SqlSessionFactory獲取Configuration的代碼

<code>    SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);    System.out.println(sqlSessionFactory.getConfiguration());/<code>

再來看SqlSessionFactoryBuilder.build()方法:

<code>     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 parser = new XMLConfigBuilder(inputStream, environment, properties);          //從這裡可以看出XMLConfigBuilder.parse()返回了一個Configuration對象          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);      }/<code>

加載具體配置

<code>    public Configuration parse() {        if (parsed) {          throw new BuilderException("Each XMLConfigBuilder can only be used once.");        }        parsed = true;        parseConfiguration(parser.evalNode("/configuration"));        return configuration;      }      //從xml配置文件中加載到Configuration對象中      private void parseConfiguration(XNode root) {        try {          //加載properties節點,一般是定義一些變量          propertiesElement(root.evalNode("properties")); //issue #117 read properties first          //加載別名          typeAliasesElement(root.evalNode("typeAliases"));          //攔截器          pluginElement(root.evalNode("plugins"));          objectFactoryElement(root.evalNode("objectFactory"));          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));          settingsElement(root.evalNode("settings"));          environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631          databaseIdProviderElement(root.evalNode("databaseIdProvider"));          //加載Mapper的配置文件,最主要的有兩個:一個是sql的定義,一個是resultMap          typeHandlerElement(root.evalNode("typeHandlers"));          mapperElement(root.evalNode("mappers"));        } catch (Exception e) {          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);        }      }/<code>

加載properties節點

<code>       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");          //不能同時設置resource屬性和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);        }      }/<code> 

從這個方法中可以看出配置規則

  1. 可以設置url或resource屬性從外部文件中加載一個properties文件
  2. 可以通過property子節點進行配置,如果子節點屬性的key與外部文件的key重複的話,子節點的將被覆
  3. 通過編程方式定義的屬性最後加載,優先級最高:
<code>        public SqlSessionFactory build(InputStream inputStream, Properties properties)/<code>

properties配置示例

<code>    <properties>      <property>      <property>    /<properties>/<code>

這裡加載的主要是給後面的配置作為變量使用!

加載別名

<code>       private void typeAliasesElement(XNode parent) {        if (parent != null) {          for (XNode child : parent.getChildren()) {            if ("package".equals(child.getName())) {              //package的方式,很少用到,略過              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);              }            }          }        }      }/<code>

再看 TypeAliasRegistry源碼,發現mybatis已經為定義了很多別名,方便以後的配置

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

還有一個加載通過別名加載class的方法

<code>       public  Class resolveAlias(String string) {        try {          if (string == null) return null;          String key = string.toLowerCase(Locale.ENGLISH); // issue #748          Class 
value; if (TYPE_ALIASES.containsKey(key)) { //如果是別名,直接從註冊表裡返回 value = (Class) TYPE_ALIASES.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); } }
/<code>

加載Mapper配置文件

<code>     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對象解析加載                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對象解析加載                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.");              }            }          }        }      }/<code>

一個Mapper的配置文件最終會由XMLMapperBuilder對象解析加載到Configuration對象中。XMLMapperBuilder的解析過程中XMLConfigBuilder解析過程差不多,以後再詳細分析!

加載其他配置項

還有一些配置項這裡沒有講到,如:插件/攔截器、對象工廠、setting項。這些的加載都比較簡單,只要花點心裡就可以看明白。在以後分析代碼過程中,一定會看到這裡配置,到時再進一步研究,不過可以肯定這裡配置很多情況下都是使用默認的值。



分享到:


相關文章: