Mybatis源码分析(第一章)------配置文件的解析(2)
书接上文
1.4 设置 settings 配置到 Configuration 中
settings 配置设置到 Configuration 对象中的过程源码分析如下:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → settingsElement
1 private void settingsElement(Properties props) throws Exception { 2 // 设置 autoMappingBehavior 属性,默认值为 PARTIAL 3 configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); 4 configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE"))); 5 // 设置 cacheEnabled 属性,默认值为 true 6 configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); 7 8 // 省略部分代码 9 10 // 解析默认的枚举处理器 11 Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler")); 12 // 设置默认枚举处理器 13 configuration.setDefaultEnumTypeHandler(typeHandler); 14 configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); 15 configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true)); 16 17 // 省略部分代码 18 }
//以上代码与实际源码不一致,不知道是不是Mybatis版本更新问题,继续往下看
调用Configuration的各种setter,下面来看resolveClass()
1 protected Class<?> resolveClass(String alias) { 2 if (alias == null) { 3 return null; 4 } 5 try { 6 // 通过别名解析 7 return resolveAlias(alias); 8 } catch (Exception e) { 9 throw new BuilderException("Error resolving class. Cause: " + e, e); 10 } 11 } 12 13 protected final TypeAliasRegistry typeAliasRegistry; 14 15 protected Class<?> resolveAlias(String alias) { 16 // 通过别名注册器解析别名对于的类型 Class 17 return typeAliasRegistry.resolveAlias(alias); 18 }
TypeAliasRegistry 的用途就是将别名和类型进行映射,下面来看看别名的配置(typeAliases)解析过程
1.5 解析typeAliases配置
在 MyBatis 中,可以为我们自己写的有些类定义一个别名。这样在使用的时候,我们只需要输入别名即可,无需再把全限定的类名写出来。在 MyBatis 中,我们有两种方式进行别名配置。第一种是仅配置包名,让 MyBatis 去扫描包中的类型,并根据类型得到相应的别名。这种方式可配合 Alias 注解使用,即通过注解为某个类配置别名,而不是让 MyBatis 按照默认规则生成别名。
可以通过<package name=" "/>标签为某个包下所有类配置别名,或者使用<typeAlias alias=" " type=" "/>为某个类配置别名。
下面来看如何解析别名配置:
1 private void typeAliasesElement(XNode parent) { 2 if (parent != null) { 3 for (XNode child : parent.getChildren()) { 4 // 从指定的包中解析别名和类型的映射 5 if ("package".equals(child.getName())) { 6 String typeAliasPackage = child.getStringAttribute("name"); 7 configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); 8 9 // 从 typeAlias 节点中解析别名和类型的映射 10 } else { 11 // 获取 alias 和 type 属性值,alias 不是必填项,可为空 12 String alias = child.getStringAttribute("alias"); 13 String type = child.getStringAttribute("type"); 14 try { 15 // 加载 type 对应的类型 16 Class<?> clazz = Resources.classForName(type); 17 18 // 注册别名到类型的映射 19 if (alias == null) { 20 typeAliasRegistry.registerAlias(clazz); 21 } else { 22 typeAliasRegistry.registerAlias(alias, clazz); 23 } 24 } catch (ClassNotFoundException e) { 25 throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); 26 } 27 } 28 } 29 } 30 }
上面代码中以一个if-else分支来处理这两种配置别名的方法,下面分别看一下两个方法的详细源码:
1.5.1 从 typeAlias 节点中解析并注册别名
在别名的配置中,type属性是必须要配置的,而alias属性则不是必须的。这个在配置文件的 DTD 中有规定。如果使用者未配置 alias 属性,则需要 MyBatis 自行为目标类型生成别名。对于别名为空的情况,注册别名的任务交由void registerAlias(Class<?>)方法处理。若不为空,则由void registerAlias(String, Class<?>)进行别名注册:
1 private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>(); 2 3 public void registerAlias(Class<?> type) { 4 // 获取全路径类名的简称 5 String alias = type.getSimpleName(); 6 Alias aliasAnnotation = type.getAnnotation(Alias.class); 7 if (aliasAnnotation != null) { 8 // 从注解中取出别名 9 alias = aliasAnnotation.value(); 10 } 11 // 调用重载方法注册别名和类型映射 12 registerAlias(alias, type); 13 } 14 15 public void registerAlias(String alias, Class<?> value) { 16 if (alias == null) { 17 throw new TypeException("The parameter alias cannot be null"); 18 } 19 // 将别名转成小写 20 String key = alias.toLowerCase(Locale.ENGLISH); 21 /* 22 * 如果 TYPE_ALIASES 中存在了某个类型映射,这里判断当前类型与映射中的类型是否一致, 23 * 不一致则抛出异常,不允许一个别名对应两种类型 24 */ 25 if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) { 26 throw new TypeException( 27 "The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'."); 28 } 29 // 缓存别名到类型映射 30 TYPE_ALIASES.put(key, value); 31 }
如上,若用户为明确配置 alias 属性,MyBatis 会使用类名的小写形式作为别名。比如,全限定类名xyz.coolblog.model.Author的别名为author。若类中有@Alias注解,则从注解中取值作为别名。
1.5.2 从指定的包中解析并注册别名
1 public void registerAliases(String packageName) { 2 // 调用重载方法注册别名 3 registerAliases(packageName, Object.class); 4 } 5 6 public void registerAliases(String packageName, Class<?> superType) { 7 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); 8 /* 9 * 查找某个包下的父类为 superType 的类。从调用栈来看,这里的 10 * superType = Object.class,所以 ResolverUtil 将查找所有的类。 11 * 查找完成后,查找结果将会被缓存到内部集合中。 12 */ 13 resolverUtil.find(new ResolverUtil.IsA(superType), packageName); 14 // 获取查找结果 15 Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); 16 for (Class<?> type : typeSet) { 17 // 忽略匿名类,接口,内部类 18 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { 19 // 为类型注册别名 20 registerAlias(type); 21 } 22 } 23 }
- 查找指定包下的所有类
- 通过 VFS(虚拟文件系统)获取指定包下的所有文件的路径名,比如
com/emiyaa/model/Article.class - 筛选以
.class结尾的文件名 - 将路径名转成全限定的类名,通过类加载器加载类名
- 对类型进行匹配,若符合匹配规则,则将其放入内部集合中
- 遍历查找到的类型集合,为每个类型注册别名
1.5.3 注册 MyBatis 内部类及常见类型的别名
Configuration类中typeAliasRegistry的使用
1 public Configuration() { 2 // 注册事务工厂的别名 3 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); 4 // 省略部分代码,下同 5 6 // 注册数据源的别名 7 typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); 8 9 // 注册缓存策略的别名 10 typeAliasRegistry.registerAlias("FIFO", FifoCache.class); 11 typeAliasRegistry.registerAlias("LRU", LruCache.class); 12 13 // 注册日志类的别名 14 typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); 15 typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); 16 17 // 注册动态代理工厂的别名 18 typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); 19 typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); 20 } 21 22 // -☆- TypeAliasRegistry 23 public TypeAliasRegistry() { 24 // 注册 String 的别名 25 registerAlias("string", String.class); 26 27 // 注册基本类型包装类的别名 28 registerAlias("byte", Byte.class); 29 // 省略部分代码,下同 30 31 // 注册基本类型包装类数组的别名 32 registerAlias("byte[]", Byte[].class); 33 34 // 注册基本类型的别名 35 registerAlias("_byte", byte.class); 36 37 // 注册基本类型包装类的别名 38 registerAlias("_byte[]", byte[].class); 39 40 // 注册 Date, BigDecimal, Object 等类型的别名 41 registerAlias("date", Date.class); 42 registerAlias("decimal", BigDecimal.class); 43 registerAlias("object", Object.class); 44 45 // 注册 Date, BigDecimal, Object 等数组类型的别名 46 registerAlias("date[]", Date[].class); 47 registerAlias("decimal[]", BigDecimal[].class); 48 registerAlias("object[]", Object[].class); 49 50 // 注册集合类型的别名 51 registerAlias("map", Map.class); 52 registerAlias("hashmap", HashMap.class); 53 registerAlias("list", List.class); 54 registerAlias("arraylist", ArrayList.class); 55 registerAlias("collection", Collection.class); 56 registerAlias("iterator", Iterator.class); 57 58 // 注册 ResultSet 的别名 59 registerAlias("ResultSet", ResultSet.class); 60 }
1.6 解析 plugins 配置
插件是 MyBatis 提供的一个拓展机制,通过插件机制我们可在 SQL 执行过程中的某些点上做一些自定义操作。实现一个插件需要比简单,首先需要让插件类实现Interceptor接口。
然后在插件类上添加@Intercepts和@Signature注解,用于指定想要拦截的目标方法。MyBatis 允许拦截下面接口中的一些方法:常见的有分页插件,分表插件等。
- Executor: update 方法,query 方法,flushStatements 方法,commit 方法,rollback 方法, getTransaction 方法,close 方法,isClosed 方法
- ParameterHandler: getParameterObject 方法,setParameters 方法
- ResultSetHandler: handleResultSets 方法,handleOutputParameters 方法
- StatementHandler: prepare 方法,parameterize 方法,batch 方法,update 方法,query 方法
一般插件可以用
1 <plugins> 2 <plugin interceptor=" "> 3 <propety name=" " value=" "/> 4 </plugin> 5 </plugins>
标签组来配置,以下是解析过程:
1 private void pluginElement(XNode parent) throws Exception { 2 if (parent != null) { 3 for (XNode child : parent.getChildren()) { 4 String interceptor = child.getStringAttribute("interceptor"); 5 // 获取配置信息 6 Properties properties = child.getChildrenAsProperties(); 7 // 解析拦截器的类型,并创建拦截器 8 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); 9 // 设置属性 10 interceptorInstance.setProperties(properties); 11 // 添加拦截器到 Configuration 中 12 configuration.addInterceptor(interceptorInstance); 13 } 14 } 15 }
首先是获取配置,然后再解析拦截器类型,并实例化拦截器。最后向拦截器中设置属性,并将拦截器添加到 Configuration 中。
1.7 解析 environments 配置
在 MyBatis 中,事务管理器和数据源配置在 environments 中:
1 <environments default="development"> 2 <environment id="development"> 3 <transactionManager type="JDBC"/> 4 <dataSource type="POOLED"> 5 <property name="driver" value="${jdbc.driver}"/> 6 <property name="url" value="${jdbc.url}"/> 7 <property name="username" value="${jdbc.username}"/> 8 <property name="password" value="${jdbc.password}"/> 9 </dataSource> 10 </environment> 11 </environments>
详细解析过程如下:
1 private String environment; 2 3 private void environmentsElement(XNode context) throws Exception { 4 if (context != null) { 5 if (environment == null) { 6 // 获取 default 属性 7 environment = context.getStringAttribute("default"); 8 } 9 for (XNode child : context.getChildren()) { 10 // 获取 id 属性 11 String id = child.getStringAttribute("id"); 12 /* 13 * 检测当前 environment 节点的 id 与其父节点 environments 的属性 default 14 * 内容是否一致,一致则返回 true,否则返回 false 15 */ 16 if (isSpecifiedEnvironment(id)) { 17 // 解析 transactionManager 节点,逻辑和插件的解析逻辑很相似,不在赘述 18 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); 19 // 解析 dataSource 节点,逻辑和插件的解析逻辑很相似,不在赘述 20 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); 21 // 创建 DataSource 对象 22 DataSource dataSource = dsFactory.getDataSource(); 23 Environment.Builder environmentBuilder = new Environment.Builder(id) 24 .transactionFactory(txFactory) 25 .dataSource(dataSource); 26 // 构建 Environment 对象,并设置到 configuration 中 27 configuration.setEnvironment(environmentBuilder.build()); 28 } 29 } 30 } 31 }
1.8 解析 typeHandlers 配置
在向数据库存储或读取数据时,我们需要将数据库字段类型和 Java 类型进行一个转换。比如数据库中有CHAR和VARCHAR等类型,但 Java 中没有这些类型,不过 Java 有String类型。所以我们在从数据库中读取 CHAR 和 VARCHAR 类型的数据时,就可以把它们转成 String 。在 MyBatis 中,数据库类型和 Java 类型之间的转换任务是委托给类型处理器TypeHandler去处理的。MyBatis 提供了一些常见类型的类型处理器,除此之外,我们还可以自定义类型处理器以非常见类型转换的需求。
类型处理器配置方式也有两种:
1 <typeHandlers> 2 <package name="com.emiyaa.handlers"/> 3 </typeHandlers> 4 5 <typeHandlers> 6 <typeHandler jdbcType="TINYINT" 7 javaType="com.emiyaa.constant.ArticleTypeEnum" 8 handler="com.emiyaa.mybatis.ArticleTypeHandler"/> 9 </typeHandlers>
使用第一种方式注册类型处理器时,应使用@MappedTypes和@MappedJdbcTypes注解配置javaType和jdbcType,代码如下:
1 private void typeHandlerElement(XNode parent) throws Exception { 2 if (parent != null) { 3 for (XNode child : parent.getChildren()) { 4 // 从指定的包中注册 TypeHandler 5 if ("package".equals(child.getName())) { 6 String typeHandlerPackage = child.getStringAttribute("name"); 7 // 注册方法 ① 8 typeHandlerRegistry.register(typeHandlerPackage); 9 10 // 从 typeHandler 节点中解析别名到类型的映射 11 } else { 12 // 获取 javaType,jdbcType 和 handler 等属性值 13 String javaTypeName = child.getStringAttribute("javaType"); 14 String jdbcTypeName = child.getStringAttribute("jdbcType"); 15 String handlerTypeName = child.getStringAttribute("handler"); 16 17 // 解析上面获取到的属性值 18 Class<?> javaTypeClass = resolveClass(javaTypeName); 19 JdbcType jdbcType = resolveJdbcType(jdbcTypeName); 20 Class<?> typeHandlerClass = resolveClass(handlerTypeName); 21 22 // 根据 javaTypeClass 和 jdbcType 值的情况进行不同的注册策略 23 if (javaTypeClass != null) { 24 if (jdbcType == null) { 25 // 注册方法 ② 26 typeHandlerRegistry.register(javaTypeClass, typeHandlerClass); 27 } else { 28 // 注册方法 ③ 29 typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); 30 } 31 } else { 32 // 注册方法 ④ 33 typeHandlerRegistry.register(typeHandlerClass); 34 } 35 } 36 } 37 } 38 }
上面的代码中调用了4个不同的类型处理器注册方法,重载方法如图所示:

蓝色为起始方法,绿色为中间方法,红色为终点方法,起始方法编号对应以上代码相应方法的编号,接下来按③→②→④→①顺序开始分析。
1.8.1 register(Class, JdbcType, Class) 方法分析
当代码执行到此方法时,表示javaTypeClass != null && jdbcType != null条件成立,即使用者明确配置了javaType和jdbcType属性的值,注册过程也就是把类型和处理器进行映射。
1 public void register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass) { 2 // 调用终点方法 3 register(javaTypeClass, jdbcType, getInstance(javaTypeClass, typeHandlerClass)); 4 } 5 6 /** 类型处理器注册过程的终点 */ 7 private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) { 8 if (javaType != null) { 9 // JdbcType 到 TypeHandler 的映射 10 Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType); 11 if (map == null || map == NULL_TYPE_HANDLER_MAP) { 12 map = new HashMap<JdbcType, TypeHandler<?>>(); 13 // 存储 javaType 到 Map<JdbcType, TypeHandler> 的映射 14 TYPE_HANDLER_MAP.put(javaType, map); 15 } 16 map.put(jdbcType, handler); 17 } 18 19 // 存储所有的 TypeHandler 20 ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler); 21 }
1.8.2 register(Class, Class) 方法分析
当代码执行到此方法时,表示javaTypeClass != null && jdbcType == null条件成立,即使用者仅设置了javaType属性的值,主要做的事情是尝试从注解中获取JdbcType的值:
1 public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) { 2 // 调用中间方法 register(Type, TypeHandler) 3 register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass)); 4 } 5 6 private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) { 7 // 获取 @MappedJdbcTypes 注解 8 MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class); 9 if (mappedJdbcTypes != null) { 10 // 遍历 @MappedJdbcTypes 注解中配置的值 11 for (JdbcType handledJdbcType : mappedJdbcTypes.value()) { 12 // 调用终点方法,参考上一小节的分析 13 register(javaType, handledJdbcType, typeHandler); 14 } 15 if (mappedJdbcTypes.includeNullJdbcType()) { 16 // 调用终点方法,jdbcType = null 17 register(javaType, null, typeHandler); 18 } 19 } else { 20 // 调用终点方法,jdbcType = null 21 register(javaType, null, typeHandler); 22 } 23 }
1.8.3 register(Class) 方法分析
当代码执行到此方法时,表示javaTypeClass == null && jdbcType != null条件成立,即使用者未配置javaType和jdbcType属性的值:
1 public void register(Class<?> typeHandlerClass) { 2 boolean mappedTypeFound = false; 3 // 获取 @MappedTypes 注解 4 MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); 5 if (mappedTypes != null) { 6 // 遍历 @MappedTypes 注解中配置的值 7 for (Class<?> javaTypeClass : mappedTypes.value()) { 8 // 调用注册方法 ② 9 register(javaTypeClass, typeHandlerClass); 10 mappedTypeFound = true; 11 } 12 } 13 if (!mappedTypeFound) { 14 // 调用中间方法 register(TypeHandler) 15 register(getInstance(null, typeHandlerClass)); 16 } 17 } 18 19 public <T> void register(TypeHandler<T> typeHandler) { 20 boolean mappedTypeFound = false; 21 // 获取 @MappedTypes 注解 22 MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class); 23 if (mappedTypes != null) { 24 for (Class<?> handledType : mappedTypes.value()) { 25 // 调用中间方法 register(Type, TypeHandler) 26 register(handledType, typeHandler); 27 mappedTypeFound = true; 28 } 29 } 30 // 自动发现映射类型 31 if (!mappedTypeFound && typeHandler instanceof TypeReference) { 32 try { 33 TypeReference<T> typeReference = (TypeReference<T>) typeHandler; 34 // 获取参数模板中的参数类型,并调用中间方法 register(Type, TypeHandler) 35 register(typeReference.getRawType(), typeHandler); 36 mappedTypeFound = true; 37 } catch (Throwable t) { 38 } 39 } 40 if (!mappedTypeFound) { 41 // 调用中间方法 register(Class, TypeHandler) 42 register((Class<T>) null, typeHandler); 43 } 44 } 45 46 public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) { 47 // 调用中间方法 register(Type, TypeHandler) 48 register((Type) javaType, typeHandler); 49 }
不管是通过注解的方式,还是通过反射的方式,它们最终目的是为了解析出javaType的值。解析完成后,这些方法会调用中间方法register(Type, TypeHandler),这个方法负责解析jdbcType。一个负责解析 javaType,另一个负责解析 jdbcType,这就是上述代码的逻辑。
1.8.4 register(String) 方法分析
本节代码的主要是用于自动扫描类型处理器,并调用其他方法注册扫描结果:
1 public void register(String packageName) { 2 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); 3 // 从指定包中查找 TypeHandler 4 resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName); 5 Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses(); 6 for (Class<?> type : handlerSet) { 7 // 忽略内部类,接口,抽象类等 8 if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { 9 // 调用注册方法 ④ 10 register(type); 11 } 12 } 13 }
1.9 解析 mappers 配置
篇幅原因放在下一篇文章解析。

浙公网安备 33010602011771号