Mybatis 源码(三):Mybatis配置解析

  Mybatis有两个核心配置,全局配置会影响Mybatis的执行;Mapper配置定义了查询的SQL,下面我们来看看Mybatis是如何加载配置文件的。

  本文基于Mybatis 源码(一):源码编译准备中案例进行分析,主要示例代码如下:

 1 public static void main(String[] args) throws IOException {
 2         String resource = "mybatis-config.xml";
 3         // 加载mybatis的配置文件
 4         InputStream inputStream = Resources.getResourceAsStream(resource);
 5         // 获取sqlSessionFactory
 6         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 7         // 获取sqlSession
 8         SqlSession sqlSession = sqlSessionFactory.openSession();
 9         // 查询用户
10         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
11         User user02 = mapper.selectUserById("101");
12         System.out.println("user01: " + user02);
13     }
14 }

1、构建SqlSessionFactory对象

  Mybatis 源码(二):整体设计概览中已提到Mybatis执行过程中的SqlSessionFactory对象,是使用SqlSessionFactoryBuilder构建的,下面来看下SqlSqlSessionFactory对象的获取。

// 获取sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

  创建SqlSessionFactoryBuilder对象,使用建造者模式构建SqlSessionFactory。建造者模式用于构建复杂对象,无需关注内部细节,封装的思想。

    

  SqlSessionFactoryBuilder中用来创建SqlSessionFactory对象的方法是build(),build()方法有9个重载,可以用不同的方法来创建SqlSessionFactory对象。SqlSessionFactory对象默认是单例的。

SqlSessionFactoryBuilder#build()核心伪代码:
1 // 获取SqlSessionFactory对象
2 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
3     
4     // 获取XML配置解析器XMLConfigBuilder:将配置文件加载到内存中并生成一个document对象 ,同时初始化Configuration对象
5     XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
6     
7     // 解析配置并创建SqlSessionFactory对象
8     return build(parser.parse());
9 }

1、创建XMLConfigBuilder对象

XMLConfigBuilder对象是BaseBuilder的子类,用于解析全局配置文件。
  
BaseBuilder主要处理解析工作,类图如下,对于同的构建目标BaseBuilder还有其他的一些子类:
  ·XMLMapperBuilder:解析Mapper映射器

  ·XMLStatementBuilder:解析增删改查标签

  ·XMLScriptBuilder:解析动态SQL

1.1、XMLConfigBuilder初始化

  上面的解析处理类在后续执行流程中会提到,优先看下XMLConfigBuilder对象在初始化时的处理。

1 // 构造函数,转换成XPathParser再去调用构造函数
2 public XMLConfigBuilder(Reader reader, String environment, Properties props) {
3   // EntityResolver的实现类是XMLMapperEntityResolver 来完成配置文件的校验,根据对应的DTD文件来实现
4   this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
5 }

  进入重载的构造方法中,XMLConfigBuilder#XMLConfigBuilder() 核心代码:

 1 // 初始化Configurationd对象
 2 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
 3   // 调用父类构造函数,初始化configuration
 4   super(new Configuration());
 5   ErrorContext.instance().resource("SQL Mapper Configuration");
 6   // 将Properties全部设置到configuration里面去
 7   this.configuration.setVariables(props);
 8   // 设置 是否解析的标志为 false
 9   this.parsed = false;
10   // 初始化environment
11   this.environment = environment;
12   // 初始化解析器
13   this.parser = parser;
14 }

1、XPathParser初始化

  XPathParser是实际上的Mybatis全局配置文件解析器。XPathParser#XPathParser() 核心代码

1 // XPathParser构造函数,xpath对象为XPathFactory
2 public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
3   // 初始化XPathParser相关属性, 
4   commonConstructor(validation, variables, entityResolver);
5   // 初始化document,DocumentBuilderFactory
6   this.document = createDocument(new InputSource(reader));
7 }

2、Configuration初始化

  Configuration初始化时做了哪些操作,Configuration#Configuration() 核心代码

 1 public Configuration() {
 2   // 初始化别名注册器
 3   typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
 4   typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
 5 
 6   typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
 7   typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
 8   typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
 9 
10   typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
11   typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
12   typeAliasRegistry.registerAlias("LRU", LruCache.class);
13   typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
14   typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
15 
16   typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
17 
18   typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
19   typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
20 
21   typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
22   typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
23   typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
24   typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
25   typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
26   typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
27   typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
28 
29   typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
30   typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
31 
32   languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
33   languageRegistry.register(RawLanguageDriver.class);
34 }

  Configuration初始化完成类型别名的注册工作。

3、总结

  通过上述的分析可以看到XMLConfigBuilder完成了XML文件的解析对应XPathParser和Configuration对象的初始化操作,下面来看下parse方法到底是如何解析配置文件的。

1.2、配置文件解析

  具体解析方法,配置内容解析成Configuration对象,XMLConfigBuilder#parse() 核心代码:

 1 // 将配置内容解析成Configuration对象并返回
 2 public Configuration parse() {
 3   // 资源已被解析,抛出异常。根据parsed变量的值判断是否已经完成了对mybatis-config.xml配置文件的解析
 4   if (parsed) {
 5     throw new BuilderException("Each XMLConfigBuilder can only be used once.");
 6   }
 7   // 解析标识设置为已解析
 8   parsed = true;
 9   // 在mybatis-config.xml配置文件中的查找根节点configuration标签,并开始解析
10   parseConfiguration(parser.evalNode("/configuration"));
11   // 返回解析
12   return configuration;
13 }

  解析全局配置文件configuration根标签下的内容,XMLConfigBuilder#parseConfiguration() 核心代码

 1 // 解析全局配置文件根标签configuration下的各子标签
 2 private void parseConfiguration(XNode root) {
 3   try {
 4     // 解析properties标签元素
 5     propertiesElement(root.evalNode("properties"));
 6     // 解析settings标签元素
 7     Properties settings = settingsAsProperties(root.evalNode("settings"));
 8     // 文件读取
 9     loadCustomVfs(settings);
10     // 设置日志信息
11     loadCustomLogImpl(settings);
12     // 解析typeAliases标签元素,类型别名
13     typeAliasesElement(root.evalNode("typeAliases"));
14     // 解析plugins标签元素,插件
15     pluginElement(root.evalNode("plugins"));
16     // 解析objectFactory标签元素,对象工厂
17     objectFactoryElement(root.evalNode("objectFactory"));
18     // 解析objectWrapperFactory标签元素,对象包装工厂
19     objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
20     // 解析reflectorFactory标签元素,反射工厂
21     reflectorFactoryElement(root.evalNode("reflectorFactory"));
22     // settings 子标签赋值,若未配置,使用默认值
23     settingsElement(settings);
24     // 解析environments标签元素,数据库连接信息创建
25     environmentsElement(root.evalNode("environments"));
26     // 解析databaseIdProvider标签元素
27     databaseIdProviderElement(root.evalNode("databaseIdProvider"));
28     // 解析typeHandlers标签元素,类型处理器
29     typeHandlerElement(root.evalNode("typeHandlers"));
30     // 解析mappers标签元素, mapper
31     mapperElement(root.evalNode("mappers"));
32   } catch (Exception e) {
33     // 解析xml配置失败,抛出异常
34     throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
35   }
36 }

1、properties标签解析

  解析peoperties标签,XMLConfigBuilder#propertiesElement() 核心代码
 1 // 解析properties标签
 2 private void propertiesElement(XNode context) throws Exception {
 3   // properties标签元素不为空
 4   if (context != null) {
 5     // 解析properties的子节点的name和value属性,并记录到Properties中
 6     Properties defaults = context.getChildrenAsProperties();
 7     // 解析properties的resource和url属性,这两个属性用于确定properties配置文件的位置
 8     String resource = context.getStringAttribute("resource");
 9     String url = context.getStringAttribute("url");
10     // 若在properties标签中既设置了url 又设置了resource,抛异常
11     if (resource != null && url != null) {
12       throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
13     }
14     // resource属性值不为空
15     if (resource != null) {
16       // 将resource指定的资源文件解析成Properties,添加进properties标签的容器对象defaults中
17       defaults.putAll(Resources.getResourceAsProperties(resource));
18     // url属性不为空
19     } else if (url != null) {
20       // 将指定路径资源文件解析成Properties,添加进properties标签的容器对象defaults中
21       defaults.putAll(Resources.getUrlAsProperties(url));
22     }
23     // 获取configuration的variables属性合并到defaults对象中
24     Properties vars = configuration.getVariables();
25     if (vars != null) {
26       defaults.putAll(vars);
27     }
28    // 填充解析器的variables属性
29     parser.setVariables(defaults);
30     // 更新XPathParser和Configuration的variables字段
31     configuration.setVariables(defaults);
32   }
33 }

解析标签:

1.1、解析标签为Properties对象

  解析标签的name和value属性,读取引入的外部配置文件,可通过resource属性设置文件的相对路径,也可通过url属性设置文件的绝对路径。但resource、url不能同时设置。

1.2、属性信息合并,并将合并的结果重新设置到Configuration中的variables属性

  解析到的所有配置信息设置到名为defaults的Properties对象里面(Hashtable对象,KV存储),将Configuration中的variables属性内容合并到defaults对象中,最后合并的结果重新设置到Configuration中的variables属性中。

1.3、总结

  解析标签,实际上是将标签中的配置信息解析成Properties对象,与Configuration中的variables属性内容合并,重新设置到Configuration的variables属性中。

2、settings标签解析预处理

  解析<settings>标签,XMLConfigBuilder#settingsAsProperties() 核心代码

 1 // ReflectorFactory负责创建和缓存Reflector对象
 2 private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
 3 
 4 // 解析settings标签为Properties
 5 private Properties settingsAsProperties(XNode context) {
 6   // 未配置settings标签
 7   if (context == null) {
 8     return new Properties();
 9   }
10   // 获取settings子节点内容,name和value属性,并返回properties对象
11   Properties props = context.getChildrenAsProperties();
12   // 获取Configuration中所有的已知的元数据信息MetaClass
13   MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
14   // 检测Configuration中是否定义了key指定属性的setter方法
15   for (Object key : props.keySet()) {
16     if (!metaConfig.hasSetter(String.valueOf(key))) {
17       throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
18     }
19   }
20   return props;
21 }

  解析标签,XNode#getChildrenAsProperties() 核心代码:

 1 // 解析<setting>标签的name、value属性并设置进Properties对象中
 2 public Properties getChildrenAsProperties() {
 3   Properties properties = new Properties();
 4   for (XNode child : getChildren()) {
 5     // 获取<setting>标签的name、value
 6     String name = child.getStringAttribute("name");
 7     String value = child.getStringAttribute("value");
 8     if (name != null && value != null) {
 9       properties.setProperty(name, value);
10     }
11   }
12   return properties;
13 }

  解析标签的name、value属性信息,并判断标签的name属性,在Configuration配置类中是否有setter方法,若无setter方法,说明设置的属性无法填充到Configuration配置类中,抛出异常。

  settingsAsProperties()主要将下的子标签都解析成Properties对象,方便后续流程的使用。

3、loadCustomVfs、logImpl的处理

  loadCustomVfs是获取Vitual File System的自定义实现类,比如要读取本地文件,或者FTP远程文件的时候,就可以用到自定义的VFS类。根据标签里面的vfsImpl标签,生成了一个抽象类VFS的子类,在MyBatis中有JBoss6VFS和DefaultVFS两个实现,在io包中。

<settings>
    <setting name="loadCustomVfs" value="xxx"/>
</settings>

XMLConfigBuilder#loadCustomVfs() 核心代码:

 1 // 加载读取本地文件的配置
 2 private void loadCustomVfs(Properties props) throws ClassNotFoundException {
 3     // 获取vfsImpl标签的value值
 4     String value = props.getProperty("vfsImpl");    
 5     if (value != null) {
 6       String[] clazzes = value.split(",");
 7       for (String clazz : clazzes) {
 8         if (!clazz.isEmpty()) {
 9           @SuppressWarnings("unchecked")
10           // 通过反射实例化VFS对象
11           Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
12           // 设置到configuration的vfsImpl属性中
13           configuration.setVfsImpl(vfsImpl);
14         }
15       }
16     }
17 }

  logImpl获取日志的实现类,Mybatis中有很多日志实现,如LOG4J,LOG4J2,SLF4J等,在logging包中。

<settings>
    <setting name="logImpl" value="xxx"/>
</settings>

  XMLConfigBuilder#loadCustomLogImpl() 核心代码:

 1 /**
 2  * 加载自定义日志实现类
 3  * @param props
 4  */
 5 private void loadCustomLogImpl(Properties props) {
 6   // 获取<settings>标签下name为logImpl 的value,自定义日志加载实现
 7   Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
 8   // 设置进configuration的 logImpl 属性
 9   configuration.setLogImpl(logImpl);
10 }

  VFS的Class对象、Log的Class对象最终都会设置进configuration配置类的vfsImpl、logImpl的属性中。

4、typeAlias标签解析

  类型别名的解析,XMLConfigBuilder#typeAliasesElement() 核心代码:

 1 // 解析别名
 2 private void typeAliasesElement(XNode parent) {
 3   // 配置文件中配置了typeAliases标签
 4   if (parent != null) {
 5     // 遍历typeAliases下的子节点
 6     for (XNode child : parent.getChildren()) {
 7       // 处理package节点
 8       if ("package".equals(child.getName())) {
 9         // 获取指定的包名
10         String typeAliasPackage = child.getStringAttribute("name");
11         // 通过TypeAliasRegistry扫描指定包中所有的类,并解析@Alias注解,完成别名注册
12         configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
13       // 处理typeAlias节点
14       } else {
15         // 获取指定的别名
16         String alias = child.getStringAttribute("alias");
17         // 获取别名对应的类型
18         String type = child.getStringAttribute("type");
19         try {
20           Class<?> clazz = Resources.classForName(type);
21           // 根据Class名字来注册类型别名
22           // 调用TypeAliasRegistry.registerAlias
23           if (alias == null) {
24             // 扫描@Alias注解,完成注册
25             typeAliasRegistry.registerAlias(clazz);
26           } else {
27             // 注册别名
28             typeAliasRegistry.registerAlias(alias, clazz);
29           }
30         } catch (ClassNotFoundException e) {
31           throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
32         }
33       }
34     }
35   }
36 }

  别名解析主要通过TypeAliasRegistry来完成,在初始化Configuration时,会创建属性对象typeAliasRegistry。在TypeAliasRegistry的构造函数中,已对基本类型、基本类型包装类、基本类型数组、集合等常用类型做了别名的注册,完成TypeAliasRegistry的TYPE_ALIASES的初始化。

  在全局配置文件中,别名配置有两种方式,一种是基于包路径下的别名解析,一种是基于全限定类名的别名解析。

1、基于包路径下的别名解析

  获取配置的包路径,扫描包中的所有类,并解析@Alias注解,若没有@Alias注解默认将类名作为别名,将别名、Class对象设置到TypeAliasRegistry的TYPE_ALIASES的属性中,完成别名注册。

  TypeAliasRegistry#registerAlias(String packageName) 核心代码:

1 // 注册包路径下的所有类型别名
2 public void registerAliases(String packageName){
3   registerAliases(packageName, Object.class);
4 }
2、基于全限定类名的别名解析

  获取全局配置中的别名、全限定类名,根据全限定类型获取Class对象。

2.1、全局配置中未配置别名

  TypeAliasRegistry#registerAlias(Class type) 核心代码:

 1 // 未在全局配置中配置别名
 2 public void registerAlias(Class<?> type) {
 3   // 获取类名称
 4   String alias = type.getSimpleName();
 5   Alias aliasAnnotation = type.getAnnotation(Alias.class);
 6   // 类是否有Alias注解定义别名
 7   if (aliasAnnotation != null) {
 8     // 使用Alias注解定义的别名
 9     alias = aliasAnnotation.value();
10   }
11   // 注册别名与类型映射关系
12   registerAlias(alias, type);
13 }

若未配置别名,判断类是否被@Alias注解修饰,若被@Alias修饰,获取@Alias中定义的别名,否则将类名作为别名进行注册。

2.2、全局配置中设置别名

TypeAliasRegistry#registerAlias(String alias, Class value) 核心代码:

 1 // 类型别名缓存
 2 private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<>();
 3 
 4 // 注册别名
 5 public void registerAlias(String alias, Class<?> value) {
 6   // 别名null, 抛出异常
 7   if (alias == null) {
 8     throw new TypeException("The parameter alias cannot be null");
 9   }
10   // 别名转小写
11   String key = alias.toLowerCase(Locale.ENGLISH);
12   // 别名在类型别名缓存中已存在,抛出异常
13   if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
14     throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
15   }
16   // 设置TYPE_ALIASES缓存中
17   TYPE_ALIASES.put(key, value);
18 }

  通过上述分析可得出结论,指定包名更倾向于使用注解的方式完成类型别名注册器的设置;指定全限定类名更倾向于使用配置的方式完成类型别名注册器的设置。

  不管是采用哪种方式,别名的注册都是通过TypeAliasRegistry的重载方法registerAlias完成的,最终都会添加到TypeAliasRegistry的TYPE_ALIASES属性缓存中。

5、plugin标签解析

  XMLConfigBuilder#pluginElement() 核心代码:

 1 // MyBatis 允许某一点拦截已映射语句执行的调用。默认情况下,MyBatis 允许使用插件来拦截方法调用
 2 private void pluginElement(XNode parent) throws Exception {
 3   if (parent != null) {
 4     // 遍历全部子节点
 5     for (XNode child : parent.getChildren()) {
 6       // 获取plugins节点的interceptor属性
 7       String interceptor = child.getStringAttribute("interceptor");
 8       // 获取plugins节点下的properties配置的信息,并形成properties对象
 9       Properties properties = child.getChildrenAsProperties();
10       // 实例化Interceptor对象
11       Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
12       // 设置Interceptor的属性
13       interceptorInstance.setProperties(properties);
14       // 记录interceptor对象
15       configuration.addInterceptor(interceptorInstance);
16     }
17   }
18 }

  用到再做具体分析。

6、objectFactory标签解析

  ObjectFactory对象工厂解析,通过反射完成ObjectFactory对象的创建。

1、objectFactory的作用

  Mybatis默认的ObjectFactory是DefaultObjectFactory。DefaultObjectFactory的类图结构如下:

    

 

 

   DefaultObjectFactory继承自ObjectFactory接口,ObjectFactory接口详情:

 1 // Mybatis通过ObjectFactory创建所需要的对象
 2 public interface ObjectFactory {
 3   
 4   // 设置属性信息
 5   void setProperties(Properties properties);
 6   
 7   // 通过默认构造器创建对象
 8   <T> T create(Class<T> type);
 9 
10   // 通过有参构造器创建对象
11   <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
12 
13   // 集合类型的判断
14   <T> boolean isCollection(Class<T> type);
15 }

  通过上述分析,ObjectFactory用于创建返回的对象。

2、objectFactory标签解析
  XMLConfigBuilder#objectFactoryElement() 核心代码:
 1 // 对象工厂,可自定义对象创建的方式,
 2 private void objectFactoryElement(XNode context) throws Exception {
 3   if (context != null) {
 4     // 获取objectFactory节点的type属性
 5     String type = context.getStringAttribute("type");
 6     // 获取objectFactory节点下配置的信息,并形成Properties对象
 7     Properties properties = context.getChildrenAsProperties();
 8     // 通过反射实例化自定义objectFactory实现
 9     ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
10     // 设置自定义objectFactory的属性,完成初始化的相关操作
11     factory.setProperties(properties);
12     // 将自定义ObjectFactory对象记录到Configuration对象的objectFactory字段中
13     configuration.setObjectFactory(factory);
14   }
15 }

  objectFactory标签type属性,可以设置别名,也可以设置全限定类名。

  resolveClass(type): 根据type优先从别名注册器TypeAliasRegistry的缓存属性TYPE_ALIASES中Class对象,若在缓存中存在,则返回别名注册器缓冲中的Class对象;若缓存中不存在,通过Class.forName()创建Class对象。

  最后将实例化好的ObjectFactory对象设置到Configuration的objectFactory属性中。

7、objectWrapperFactory标签解析

  对象包装工厂解析,objectWrapperFactory的创建也是通过反射完成的。实现方式与ObjectFactory相同。

1、objectWrapperFactory的作用

  Mybatis默认的ObjectWrapperFactory是DefaultObjectWrapperFactory,继承自ObjectWrapperFactory接口。

1 // 对象包装器工厂
2 public interface ObjectWrapperFactory {
3 
4   // 是否有对象包装器
5   boolean hasWrapperFor(Object object);
6 
7   // 获取对象包装器
8   ObjectWrapper getWrapperFor(MetaObject metaObject, Object object);
9 }

  ObjectWrapperFactory对象包装器有两个方法,判断对象是否有包装器、对象包装器的获取。在Mybatis中,对象包装器的顶级接口ObjectWrapper,默认实现的包装器MapWrapper、BeanWrapper、CollectionWrapper,有关包装器后续会详细介绍。

  

2、objectWrapperFactory标签的解析
  XMLConfigBuilder#objectWrapperFactoryElement() 核心代码:
 1 // 对象包装工厂
 2 private void objectWrapperFactoryElement(XNode context) throws Exception {
 3   // 全局配置中定义了objectWrapperFactory标签
 4   if (context != null) {
 5     String type = context.getStringAttribute("type");
 6     // 通过反射创建ObjectWrapperFactory
 7     ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
 8     // 设置到configuration中的objectWrapperFactory属性
 9     configuration.setObjectWrapperFactory(factory);
10   }
11 }

  objectWrapperFactory标签解析与objectFactory标签解析方式相同,对象都是通过反射创建设置进configuration配置类中,此处不再赘述。

8、reflectorFactory标签解析

1、反射工厂对象
  reflectorFactory反射工厂,主要用于Reflector对象的Class对象的获取与缓存。Mybtais默认的反射对象对象是DefaultReflectorFactory。ReflectorFactory接口详情:
 1 // 反射工厂 实现了Reflector对象的创建和缓存
 2 public interface ReflectorFactory {
 3 
 4   // ReflectorFactory对象是否会缓存Reflector对象
 5   boolean isClassCacheEnabled();
 6 
 7   // 设置是否缓存Reflector对象
 8   void setClassCacheEnabled(boolean classCacheEnabled);
 9 
10   // 创建指定Class对应的Reflector对象
11   Reflector findForClass(Class<?> type);
12 }

  Mybatis默认的反射工厂DefaultReflectorFactory实现:

 1 // Mybatis默认的反射工厂实现
 2 public class DefaultReflectorFactory implements ReflectorFactory {
 3   // 是否允许缓存标识,默认true
 4   private boolean classCacheEnabled = true;
 5   // Reflector对象的缓存容器
 6   private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
 7 
 8   public DefaultReflectorFactory() {
 9   }
10 
11   @Override
12   public boolean isClassCacheEnabled() {
13     return classCacheEnabled;
14   }
15 
16   @Override
17   public void setClassCacheEnabled(boolean classCacheEnabled) {
18     this.classCacheEnabled = classCacheEnabled;
19   }
20 
21   @Override
22   public Reflector findForClass(Class<?> type) {
23     // 允许缓存
24     if (classCacheEnabled) {
25       // 缓存中存在,优先从reflectorMap缓存中获取;缓存中不存在,创建Reflector对象返回并添加进reflectorMap缓存
26       return reflectorMap.computeIfAbsent(type, Reflector::new);
27     } else {
28       // 不允缓存,创建Reflector对象并返回
29       return new Reflector(type);
30     }
31   }
32 }
2、反射工厂标签解析

  reflectorFactory标签解析与objectFactory标签解析方式相同,通过反射创建ReflectorFactory对象,并设置到configuration配置类中的reflectorFactory属性,XMLConfigBuilder#reflectorFactoryElement() 核心代码:

 1 // 反射工厂, reflectorFactory标签解析
 2 private void reflectorFactoryElement(XNode context) throws Exception {
 3   // 全局配置中定义了reflectorFactory标签
 4   if (context != null) {
 5      String type = context.getStringAttribute("type");
 6      // 通过反射创建ReflectorFactory对象
 7      ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
 8     // 设置到configuration中的reflectorFactory属性
 9      configuration.setReflectorFactory(factory);
10   }
11 }

9、settings标签Properties对象的处理

  在步骤2中实现了settings标签的解析预处理,将标签里面所有子标签都转换成了Properties对象,此处只需要处理对应的Properties对象即可。XMLConfigBuilder#settingsElement() 核心代码:

 1 // <settings>标签对应的Properties对象处理
 2 private void settingsElement(Properties props) {
 3   // 如何自动映射列到字段/属性
 4   configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
 5   // 自动映射不知道的列
 6   configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
 7   // 缓存
 8   configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
 9   // 延迟加载的核心技术就是用代理模式,CGLIB/JAVASSIST两者选一
10   configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
11   // 延迟加载
12   configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
13   // 延迟加载时,每种属性是否还要按需加载
14   configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
15   // 允不允许多种结果集从一个单独 的语句中返回
16   configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
17   // 使用列标签代替列名
18   configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
19   // 允许 JDBC 支持生成的键
20   configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
21   // 配置默认的执行器
22   configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
23   // 超时时间
24   configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
25   // 默认获取的结果条数
26   configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
27   // 是否将DB字段自动映射到驼峰式Java属性(A_COLUMN-->aColumn)
28   configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
29   // 嵌套语句上使用RowBounds
30   configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
31   // 默认用session级别的缓存
32   configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
33   // 为null值设置jdbctype
34   configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
35   // Object的哪些方法将触发延迟加载
36   configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
37   // 使用安全的ResultHandler
38   configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
39   // 动态SQL生成语言所使用的脚本语言
40   configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
41   // 枚举类型处理器
42   configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
43   // 当结果集中含有Null值时是否执行映射对象的setter或者Map对象的put方法。此设置对于原始类型如int,boolean等无效。
44   configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
45   // 是否使用实际参数名称
46   configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
47   configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
48   // logger名字的前缀
49   configuration.setLogPrefix(props.getProperty("logPrefix"));
50   // 配置工厂
51   configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
52 }

  在子标签中,某些属性值若未设置在全部配置文件中,Mybatis会在此步骤自动设置默认值,并将配置信息设置到configuration配置类中。

10、environments标签解析

  environments标签中,主要设置了数据源、事务管理器、数据库连接信息。
<!--数据库连接环境设置-->
<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>

  在初始化Configuration对象时,完成类型别名、Class对象映射关系的注册,详情如下:

  

  XMLConfigBuilder#environmentsElement() 核心代码: 

 1 // 解析environments标签
 2 private void environmentsElement(XNode context) throws Exception {
 3   // 全局配置文件配置了environments标签
 4   if (context != null) {
 5     // 未指定XMLConfigBuilder.environment字段,则使用default属性
 6     if (environment == null) {
 7       environment = context.getStringAttribute("default");
 8     }
 9     // 遍历子节点
10     for (XNode child : context.getChildren()) {
11       String id = child.getStringAttribute("id");
12       // 与XmlConfigBuilder.environment字段匹配
13       if (isSpecifiedEnvironment(id)) {
14         // 创建TransactionFactory  事务工厂
15         TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
16         // 创建数据源工厂
17         DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
18         // 创建数据源
19         DataSource dataSource = dsFactory.getDataSource();
20         // 创建Builder,包含事务工厂与数据源
21         Environment.Builder environmentBuilder = new Environment.Builder(id)
22             .transactionFactory(txFactory)
23             .dataSource(dataSource);
24         // 将Environment对象记录到Configuration.environment字段中
25         configuration.setEnvironment(environmentBuilder.build());
26       }
27     }
28   }
29 }

  一个environment实际上就是对应一个数据源,根据配置的创建一个事务工厂,根据标签创建一个数据源,最后把这两个对象设置成Environment对象的属性,放到Configuration里面。

11、databaseIdProvider标签解析

  解析databaseIdProvider标签,生成DatabaseIdProvider对象,用来支持不同厂商的数据库。

  XMLConfigBuilder#databaseIdProviderElement() 核心代码:

 1 // 解析databaseIdProvider标签,获取数据库id标识,设置进configuration
 2 private void databaseIdProviderElement(XNode context) throws Exception {
 3   DatabaseIdProvider databaseIdProvider = null;
 4   if (context != null) {
 5     String type = context.getStringAttribute("type");
 6     // awful patch to keep backward compatibility
 7     // 与老版本兼容
 8     if ("VENDOR".equals(type)) {
 9         type = "DB_VENDOR";
10     }
11     // 解析子节点配置信息
12     Properties properties = context.getChildrenAsProperties();
13     // 创建DatabaseIdProvider对象
14     databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
15     // 配置DatabaseIdProvider,完成初始化
16     databaseIdProvider.setProperties(properties);
17   }
18   Environment environment = configuration.getEnvironment();
19   if (environment != null && databaseIdProvider != null) {
20     // 通过dataSource获取databaseId并记录到configuration.databaseId字段中
21     String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
22     configuration.setDatabaseId(databaseId);
23   }
24 }

12、typeHandler标签解析

  typeHandler类型处理器,与typeAlias相同,typeHandler有两种配置方式,一种是单独配置一个全限定类名,一种是指定一个package。

  XMLConfigBuilder#typeHandlerElement() 核心代码:

 1 // typeHandler 类型处理器标签解析
 2 private void typeHandlerElement(XNode parent) {
 3   // 全局配置中配置了typeHandler标签
 4   if (parent != null) {
 5     for (XNode child : parent.getChildren()) {
 6       // 指定package
 7       if ("package".equals(child.getName())) {
 8         String typeHandlerPackage = child.getStringAttribute("name");
 9         // 调用TypeHandlerRegistry.register,去包下找所有类
10         typeHandlerRegistry.register(typeHandlerPackage);
11       // 单独配置一个类
12       } else {
13         // 获取java的数据类型名称
14         String javaTypeName = child.getStringAttribute("javaType");
15         // 获取jdbc的数据类型名称
16         String jdbcTypeName = child.getStringAttribute("jdbcType");
17         // 获取类型处理类的全限定名
18         String handlerTypeName = child.getStringAttribute("handler");
19         // 解析java数据类型的Class对象
20         Class<?> javaTypeClass = resolveClass(javaTypeName);
21         // 解析获取JDBC数据类型
22         JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
23         //  解析获取类型处理类的Class对象
24         Class<?> typeHandlerClass = resolveClass(handlerTypeName);
25         // 调用TypeHandlerRegistry.register(以下是3种不同的参数形式)
26         if (javaTypeClass != null) {
27           if (jdbcType == null) {
28             typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
29           } else {
30             typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
31           }
32         } else {
33           typeHandlerRegistry.register(typeHandlerClass);
34         }
35       }
36     }
37   }
38 }

  通过对标签的解析获取JavaType、JdbcType、typeHandlerClass,以及和TypeHandler之间的映射关系,存放在TypeHandlerRegistry对象的属性中。

1、指定包名

  TypeHandlerRegistry#register(String packageName) 核心代码

 1 // 自动扫描指定包下的TypeHandler实现类并完成注册
 2 public void register(String packageName) {
 3   // 创建ResolverUtil对象
 4   ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
 5   // 查找指定包下的TypeHandler接口实现类
 6   resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
 7   Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
 8   for (Class<?> type : handlerSet) {
 9     // 过滤到内部类、接口以及抽象类
10     if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
11       register(type);
12     }
13   }
14 }

  扫描指定包路径中的所有类型为TypeHandler的Class对象,并添加到ResolverUtil的属性matehes集合中。遍历Class对象集合,过滤掉内部类、接口以及抽象类,

  若typeHandler被@MappedTypes注解修饰,获取@MappedTypes中设置的javaType,若未被@MappedTypes注解修饰,javaType为null;若typeHandler被@MappedJdbcTypes注解修饰,获取@MappedJdbcTypes中设置的jdbcType,若未被@MappedJdbcTypes注解修饰jdbcType为null。

  准备好注册参数,开始进行类型处理器注册,TypeHandlerRegistry#register() 核心代码:

 1 // 记录了Java类型向指定JdbcType转换时,需要使用TypeHandler对象
 2 private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<>();
 3 // 空TypeHandler集合的标识
 4 private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
 5 // 记录了全部TypeHandler的类型以及该类型相关的TypeHandler对象
 6 private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<>();
 7 
 8 // 类型处理器注册
 9 private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
10   // 检测是否明确指定了TypeHandler能够处理的Java类型
11   if (javaType != null) {
12     // 获取指定Java类型在typeHandlerMap集合中对应的TypeHandler集合
13     Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
14     if (map == null || map == NULL_TYPE_HANDLER_MAP) {
15       // 创建新的TypeHandler集合
16       map = new HashMap<>();
17       // 将TypeHandler对象注册到typeHandlerMap中
18       TYPE_HANDLER_MAP.put(javaType, map);
19     }
20     // 添加到typeHandlerMap中
21     map.put(jdbcType, handler);
22   }
23   ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
24 }

  根据不同的配置,将JdbcType,TypeHandlerClass、及类型处理器typeHandler的映射关系设置在TypeHandler的不同缓存属性中。

2、指定一个全限定类名

  指定一个全限定类名的注册方式与指定包名的相同,只不过准备工作不同。相较于指定包名,指定全限定名直接从全局配置中的标签中获取类型名称,并解析成相应的Class对象 -> javaType,jdbcType、typeHandlerClass,根据不同的配置,完成类型处理器的注册。

3、总结

  通过上述分析可得出结论,指定包名更倾向于使用注解的方式完成类型处理器的设置;指定全限定类名更倾向于使用配置的方式完成类型处理器的设置。不管是采用哪种方式,类型处理器的注册都是通过TypeHandlerRegistry的重载方法register完成的,最终都会添加到TypeHandlerRegistry的属性中。

13、Mapper标签解析

  详见Mybatis 源码(四):Mapper的解析工作

2、配置解析核心流程图

  

3、总结

  Mybatis配置解析过程,主要完成全局配置conf文件、Mapper.xml文件、Mapper接口中注解的解析工作,将所有的配置信息全部设置进配置类Configuration中。至此,配置文件解析源码已分析完成。

 

posted @ 2023-03-19 20:54  无虑的小猪  阅读(262)  评论(0编辑  收藏  举报