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 }
  1. 查找指定包下的所有类
    1. 通过 VFS(虚拟文件系统)获取指定包下的所有文件的路径名,比如com/emiyaa/model/Article.class
    2. 筛选以.class结尾的文件名
    3. 将路径名转成全限定的类名,通过类加载器加载类名
    4. 对类型进行匹配,若符合匹配规则,则将其放入内部集合中
  2. 遍历查找到的类型集合,为每个类型注册别名

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 类型进行一个转换。比如数据库中有CHARVARCHAR等类型,但 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注解配置javaTypejdbcType,代码如下:

 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条件成立,即使用者明确配置了javaTypejdbcType属性的值,注册过程也就是把类型和处理器进行映射。

 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条件成立,即使用者未配置javaTypejdbcType属性的值:

 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 配置

篇幅原因放在下一篇文章解析。

posted @ 2019-08-07 20:30  Emiyaa  阅读(150)  评论(0)    收藏  举报