Mybatis(三)配置文件解析流程

本系列文章:
  Mybatis(一)Mybatis的基本使用
  Mybatis(二)Mybatis的高级使用
  Mybatis(三)配置文件解析流程
  Mybatis(四)映射文件解析流程
  Mybatis(五)SQL执行流程
  Mybatis(六)数据源、缓存机制、插件机制

前言

  本文用到的Mybatis源码版本为3.5.6。mybatis-config.xml示例:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--引入外部配置文件,类似于Spring中的property-placeholder
    resource:从类路径引入
    url:从磁盘路径或者网络路径引入
    -->
    <properties resource="db.properties"></properties>
    <!--用来控制mybatis运行时的行为,是mybatis中的重要配置-->
    <settings>
        <!--设置列名映射的时候是否是驼峰标识-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!--typeAliases表示为我们引用的实体类起别名,默认情况下我们需要写类的完全限定名
    如果在此处做了配置,那么可以直接写类的名称,在type中配置上类的完全限定名,在使用的时候可以忽略大小写
    还可以通过alias属性来表示类的别名
    -->
    <typeAliases>
<!--        <typeAlias type="com.test.bean.Emp" alias="Emp"></typeAlias>-->
        <!--如果需要引用多个类,那么给每一个类起别名肯定会很麻烦,因此可以指定对应的包名,那么默认用的是类名-->
        <package name="com.test.bean"/>
    </typeAliases>
    <!--
    在实际的开发过程中,我们可能分为开发环境,生产环境,测试环境等等,每个环境的配置可以是不一样的
    environment就用来表示不同环境的细节配置,每一个环境中都需要一个事务管理器以及数据源的配置
    我们在后续的项目开发中几乎都是使用spring中配置的数据源和事务管理器来配置,此处不需要研究
    -->
    <!--default:用来选择需要的环境-->
    <environments default="development">
        <!--id:表示不同环境的名称-->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!--配置数据库连接-->
            <dataSource type="POOLED">
                <!--使用${}来引入外部变量-->
                <property name="driver" value="${driverClassname}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--
    在不同的数据库中,可能sql语句的写法是不一样的,为了增强移植性,可以提供不同数据库的操作实现
    在编写不同的sql语句的时候,可以指定databaseId属性来标识当前sql语句可以运行在哪个数据库中
    -->
    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql"/>
        <property name="SQL Server" value="sqlserver"/>
        <property name="Oracle" value="orcl"/>
    </databaseIdProvider>
    
    <!--将sql的映射文件适用mappers进行映射-->
    <mappers>
        <!--
        指定具体的不同的配置文件
        class:直接引入接口的全类名,可以将xml文件放在dao的同级目录下,并且设置相同的文件名称,同时可以使用注解的方式来进行相关的配置
        url:可以从磁盘或者网络路径查找sql映射文件
        resource:在类路径下寻找sql映射文件
        -->
<!--        <mapper resource="EmpDao.xml"/>
        <mapper resource="UserDao.xml"/>
        <mapper class="com.test.dao.EmpDaoAnnotation"></mapper>-->
        <!--
        当包含多个配置文件或者配置类的时候,可以使用批量注册的功能,也就是引入对应的包,而不是具体的配置文件或者类
        但是需要注意的是,
        1、如果使用的配置文件的形式,必须要将配置文件跟dao类放在一起,这样才能找到对应的配置文件.
            如果是maven的项目的话,还需要添加以下配置,原因是maven在编译的文件的时候只会编译java文件
                <build>
                    <resources>
                        <resource>
                            <directory>src/main/java</directory>
                        <includes>
                            <include>**/*.xml</include>
                        </includes>
                    </resource>
                    </resources>
                </build>

        2、将配置文件在resources资源路径下创建跟dao相同的包名
        -->
        <package name="com.test.dao"/>
    </mappers>
</configuration>

配置文件解析流程

  在使用MyBatis时,第一步要做的事情一般是根据配置文件构建SqlSessionFactory对象。示例:

	String resource = "mybatis-config.xml";
	//使用工具类Resources加载配置文件,获取输入流
	InputStream inputStream = Resources.getResourceAsStream(resource);
	//构建SqlSessionFactory对象
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

  SqlSessionFactory是一个接口,是一个重量级的对象。SqlSessionFactoryBuilder通过读取全局配置文件来创建一个 SqlSessionFactory ,创建这个对象是比较耗时的,主要耗时在对mybatis全局配置文件的解析上面。全局配置文件中包含很多内容,SqlSessionFactoryBuilder通过解析这些内容,创建了一个复杂的SqlSessionFactory对象,这个对象的生命周期一般和应用的生命周期是一样的,随着应
用的启动而创建,随着应用的停止而结束,所以一般是一个全局对象,一般情况下一个db对应一个SqlSessionFactory对象。
  这里的build方法是我们分析配置文件解析过程的入口方法,SqlSessionFactoryBuilder类中的build方法

    public SqlSessionFactory build(InputStream inputStream) {
    	//调用重载方法
        return build(inputStream, null, null);
    }

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            //创建配置文件解析器
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            //调用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) {
            }
        }
    }

    public SqlSessionFactory build(Configuration config) {
        // 创建DefaultSqlSessionFactory
        return new DefaultSqlSessionFactory(config);
    }

  从上面的代码中,大致可以猜出MyBatis配置文件是通过XMLConfigBuilder进行解析的。继续看XMLConfigBuilder的parse方法

   public Configuration parse() {
       if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
       }
       parsed = true;
       //解析配置
       parseConfiguration(parser.evalNode("/configuration"));
       return configuration;
   }

  上述代码中有个xpath表达式—— /configuration。这个表达式代表的是MyBatis配置文件的 <configuration> 节点。这里通过xpath选中这个节点,并传递给 parseConfiguration 方法

    private void parseConfiguration(XNode root) {
        try {
            //解析properties配置
            propertiesElement(root.evalNode("properties"));
            //解析settings配置,并将其转换为Properties对象
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            //加载vfs
            loadCustomVfs(settings);
            loadCustomLogImpl(settings);
            //解析typeAliases配置
            typeAliasesElement(root.evalNode("typeAliases"));
            //解析plugins配置
      		pluginElement(root.evalNode("plugins"));
      		//解析objectFactory配置
      		objectFactoryElement(root.evalNode("objectFactory"));
      		//解析objectWrapperFactory配置
      		objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      		//解析reflectorFactory配置
      		reflectorFactoryElement(root.evalNode("reflectorFactory"));
      		//settings中的信息设置到Configuration对象中
      		settingsElement(settings);
      		//解析environments配置
      		environmentsElement(root.evalNode("environments"));
      		//解析databaseIdProvider,获取并设置databaseId到Configuration对象
      		databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      		//解析typeHandlers配置
      		typeHandlerElement(root.evalNode("typeHandlers"));
      		//解析mappers配置
      		mapperElement(root.evalNode("mappers"));
    	} catch (Exception e) {
      		throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    	}
  	}

  至此,一个完整的配置解析过程就呈现出来了,每个节点的的解析逻辑均封装在了相应的方法中。

1、解析< properties >节点

  <properties>节点的解析工作由XMLConfigBuilder类中的propertiesElement这个方法完成的。在看方法的源码前,先来看一下<properties>节点是如何配置的:

	<properties resource="jdbc.properties">
		<property name="jdbc.username" value="coolblog"/>
		<property name="hello" value="world"/>
	</properties>

  上面配置包含了一个resource属性和两个子节点。接下面,参照上面的配置,来分析 propertiesElement方法的逻辑:

  	private void propertiesElement(XNode context) throws Exception {
    	if (context != null) {
      		//解析propertis的子节点,并将这些节点内容转换为属性对象Properties
      		Properties defaults = context.getChildrenAsProperties();
      		//获取propertis节点中的resource和url属性值
      		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) {
        		//通过url加载并解析属性文件
        		defaults.putAll(Resources.getUrlAsProperties(url));
     		}
      		Properties vars = configuration.getVariables();
      		if (vars != null) {
        		defaults.putAll(vars);
      		}
      		parser.setVariables(defaults);
      		//将属性值设置到configuration中
      		configuration.setVariables(defaults);
    	}
  	}

  上面的代码调用了XNode中的getChildrenAsProperties和getChildren方法:

  	public Properties getChildrenAsProperties() {
    	Properties properties = new Properties();
    	//获取并遍历子节点
    	for (XNode child : getChildren()) {
      		//获取property节点的name和value属性
      		String name = child.getStringAttribute("name");
      		String value = child.getStringAttribute("value");
      		if (name != null && value != null) {
        		//设置属性到属性对象中
        		properties.setProperty(name, value);
      		}
    	}
    	return properties;
  	}

  	public List<XNode> getChildren() {
    	List<XNode> children = new ArrayList<>();
    	//获取子节点列表
    	NodeList nodeList = node.getChildNodes();
    	if (nodeList != null) {
      		for (int i = 0, n = nodeList.getLength(); i < n; i++) {
        		Node node = nodeList.item(i);
        		if (node.getNodeType() == Node.ELEMENT_NODE) {
          			// 将节点对象封装到 XNode 中,并将 XNode 对象放入 children 列表中
          			children.add(new XNode(xpathParser, node, variables));
        		}
      		}
    	}
    	return children;
  	}

  <properties>节点解析过程主要包含三个步骤:

  • 1、解析<properties>节点的子节点,并将解析结果设置到Properties对象中。
  • 2、从文件系统或通过网络读取属性配置,这取决于<properties>节点的resource和url是否为空。
  • 3、将包含属性信息的Properties对象设置到XPathParser和Configuration中。

  需要注意的是,propertiesElement方法是先解析<properties>节点的子节点内容,然后再从文件系统或者网络读取属性配置,并将所有的属性及属性值都放入到defaults属性对象中。这会导致同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性和属性值会覆盖掉<properties>子节点中同名的属性和值。

2、解析< settings >节点

  <settings>相关配置是MyBatis中非常重要的配置,这些配置用于调整MyBatis运行时的行为。settings配置繁多,在对这些配置不熟悉的情况下,保持默认配置即可。看一个比较简单的配置:

	<settings>
		<setting name="cacheEnabled" value="true"/>
		<setting name="lazyLoadingEnabled" value="true"/>
		<setting name="autoMappingBehavior" value="PARTIAL"/>
	</settings>

  最开始是XMLConfigBuilder中的settingsAsProperties方法:

  	private Properties settingsAsProperties(XNode context) {
    	if (context == null) {
      		return new Properties();
    	}
    	//获取settings子节点中的内容
    	Properties props = context.getChildrenAsProperties();
    	//创建Configuration类的“元信息”对象
    	MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    	for (Object key : props.keySet()) {
      		//检测Configuration中是否存在相关属性,不存在则抛出异常
      		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;
  	}

  在上面的代码中出现了一个类MetaClass,这个类是用来做什么的呢?答案是用来解析目标类的一些元信息,比如类的成员变量,getter/setter方法等。简单总结一下上面代码的逻辑:

  • 1、解析settings子节点的内容,并将解析结果转成Properties对象。
  • 2、为Configuration创建元信息对象。
  • 3、通过MetaClass检测Configuration中是否存在某个属性的setter方法,不存在则抛异常。
  • 4、若通过MetaClass的检测,则返回Properties对象,方法逻辑结束。

  需要重点关注一下第2步和第3步的流程。

2.1 元信息对象创建过程

  元信息类MetaClass的构造方法为私有类型,所以不能直接创建,必须使用其提供的forClass方法进行创建:

public class MetaClass {

  	private final ReflectorFactory reflectorFactory;
  	private final Reflector reflector;

  	private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
    	this.reflectorFactory = reflectorFactory;
    	//根据类型创建Reflector
    	this.reflector = reflectorFactory.findForClass(type);
  	}

  	public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
  		//调用构造方法
    	return new MetaClass(type, reflectorFactory);
  	}
  	//...
}

  上面代码出现了两个新的类ReflectorFactory和Reflector,MetaClass通过引入这些新类帮助它完成功能。关于这些类,可以先看下hasSetter方法:

  	public boolean hasSetter(String name) {
  		//属性分词器,用于解析属性名
    	PropertyTokenizer prop = new PropertyTokenizer(name);
    	//hasNext返回true,则表明name是一个复合属性
    	if (prop.hasNext()) {
      		if (reflector.hasSetter(prop.getName())) {
      			//为属性创建创建MetaClass
        		MetaClass metaProp = metaClassForProperty(prop.getName());
        		//再次调用hasSetter
        		return metaProp.hasSetter(prop.getChildren());
      		} else {
        		return false;
      		}
    	} else {
      		// 调用 reflector 的 hasSetter 方法
      		return reflector.hasSetter(prop.getName());
    	}
  	}

  可以看出MetaClass中的hasSetter方法最终调用了Reflector的hasSetter方法。上面代码中出现的几个类:

  1. ReflectorFactory:Reflector的工厂类,兼有缓存Reflector对象的功能。
  2. Reflector:反射器,用于解析和存储目标类中的元信息。
  3. PropertyTokenizer:属性名分词器,用于处理较为复杂的属性名。
2.2 DefaultReflectorFactory

  DefaultReflectorFactory用于创建 Reflector,同时兼有缓存的功能:

public class DefaultReflectorFactory implements ReflectorFactory {
  	private boolean classCacheEnabled = true;
  	//目标类和反射器映射缓存
  	private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();

  	//...

  	@Override
  	public Reflector findForClass(Class<?> type) {
  		//classCacheEnabled默认为true
    	if (classCacheEnabled) {
      		//如果type对应的Reflector不存在,就创建一个新的Reflector实例
      		return reflectorMap.computeIfAbsent(type, Reflector::new);
    	} else {
      		// 创建一个新的 Reflector 实例
      		return new Reflector(type);
    	}
  	}
}
2.3 Reflector

  Reflector这个类的用途主要是是通过反射获取目标类的getter方法及其返回值类型,setter 方法及其参数值类型等元信息。并将获取到的元信息缓存到相应的集合中,供后续使用。该类的部分逻辑:

  1. Reflector 构造方法及成员变量分析。
  2. getter 方法解析过程。
  3. setter 方法解析过程。
  • 1、Reflector 构造方法及成员变量分析
      Reflector构造方法中包含了很多初始化逻辑,目标类的元信息解析过程也是在构造方法中完成的,这些元信息最终会被保存到Reflector的成员变量中:
public class Reflector {

  	private final Class<?> type;
  	private final String[] readablePropertyNames;
  	private final String[] writablePropertyNames;
  	private final Map<String, Invoker> setMethods = new HashMap<>();
  	private final Map<String, Invoker> getMethods = new HashMap<>();
  	private final Map<String, Class<?>> setTypes = new HashMap<>();
  	private final Map<String, Class<?>> getTypes = new HashMap<>();
  	private Constructor<?> defaultConstructor;

  	private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();

  	public Reflector(Class<?> clazz) {
    	type = clazz;
    	//解析目标类的默认构造方法,并赋值给defaultConstructor变量
    	addDefaultConstructor(clazz);
    	// 解析getter方法,并将解析结果放入getMethods中
    	addGetMethods(clazz);
    	//解析setter方法,并将解析结果放入setMethods中
    	addSetMethods(clazz);
    	//解析属性字段,并将解析结果添加到setMethods或getMethods中
   	 	addFields(clazz);
    	//从getMethods 映射中获取可读属性名数组
    	readablePropertyNames = getMethods.keySet().toArray(new String[0]);
    	//从setMethods 映射中获取可写属性名数组
    	writablePropertyNames = setMethods.keySet().toArray(new String[0]);
    	//将所有属性名的大写形式作为键,属性名作为值,存入到caseInsensitivePropertyMap中
    	for (String propName : readablePropertyNames) {
      		caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    	}
    	for (String propName : writablePropertyNames) {
      		caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    	}
  	}
  	//...
}

  Reflector部分成员变量的用途:

变量名用途
readablePropertyNames可读属性名称数组,用于保存getter方法对应的属性名称
writeablePropertyNames可写属性名称数组,用于保存setter方法对应的属性名称
setMethods用于保存属性名称到Invoke的映射。setter方法会被封装到MethodInvoker对象中
getMethods用于保存属性名称到Invoke的映射。同上,getter方法也会被封装到MethodInvoker对象中
setTypes用于保存setter对应的属性名与参数类型的映射
getTypes用于保存getter对应的属性名与返回值类型的映射
caseInsensitivePropertyMap用于保存大写属性名与属性名之间的映射,比如 <NAME,name>
  • 2、Reflector getter方法解析过程
      getter方法解析的逻辑被封装在了addGetMethods方法中,这个方法除了会解析形如getXXX的方法,同时也会解析isXXX方法:
  	private void addGetMethods(Class<?> clazz) {
    	Map<String, List<Method>> conflictingGetters = new HashMap<>();
    	//获取当前类、接口以及父类中的方法
    	Method[] methods = getClassMethods(clazz);
    	//getter方法不应该有参数,若存在参数,则忽略当前方法
    	Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
      	//将getXXX或isXXX等方法名转成相应的属性,比如getName -> name
      	//addMethodConflict方法用于解决冲突。 将冲突的方法添加到conflictingGetters中。
      	//考虑这样一种情况:getTitle和isTitle两个方法经过methodToProperty处理,均得到name = title,这会导致冲突。
      	//对于冲突的方法,这里先统一起存起来,后续再解决冲突
      	.forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
    	//解决getter冲突
    	resolveGetterConflicts(conflictingGetters);
  	}

  addGetMethods方法的逻辑:

  1. 获取当前类、接口,以及父类中的方法。
  2. 遍历上一步获取的方法数组,并过滤出以get和is开头的方法。
  3. 将方法名转换成相应的属性名。
  4. 将属性名和方法对象添加到冲突集合中。
  5. 解决冲突。

  第4步和第5步的具体逻辑:

  	//添加属性名和方法对象到冲突集合中
  	private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
    	if (isValidPropertyName(name)) {
      		List<Method> list = conflictingMethods.computeIfAbsent(name, k -> new ArrayList<>());
      		list.add(method);
    	}
  	}

  	//解决冲突
  	private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
    	for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
      		Method winner = null;
      		String propName = entry.getKey();
      		boolean isAmbiguous = false;
      		for (Method candidate : entry.getValue()) {
        		if (winner == null) {
          			winner = candidate;
          			continue;
        		}
        		//获取返回值类型
        		Class<?> winnerType = winner.getReturnType();
        		Class<?> candidateType = candidate.getReturnType();
        		/*
				 * 两个方法的返回值类型一致,若两个方法返回值类型均为boolean,
		 		 * 则选取isXXX方法为winner。否则无法决定哪个方法更为合适,
				 * 只能抛出异常
				 */
        		if (candidateType.equals(winnerType)) {
          			if (!boolean.class.equals(candidateType)) {
            			isAmbiguous = true;
            			break;
          			/*
		   			 * 如果方法返回值类型为boolean,且方法名以 "is" 开头,
		   		 	 * 则认为候选方法candidate更为合适
		  			 */
          			} else if (candidate.getName().startsWith("is")) {
            			winner = candidate;
          			}
        			/*
		 			 * winnerType是candidateType的子类,类型上更为具体,
		 			 * 则认为当前的winner仍是合适的,无需做什么事情
		 			 */
        			} else if (candidateType.isAssignableFrom(winnerType)) {
          				// OK getter type is descendant
        				/*
	     				 * candidateType是winnerType的子类,此时认为candidate方法
         				 * 更为合适,故将winner更新为candidate
         				 */
        			} else if (winnerType.isAssignableFrom(candidateType)) {
          				winner = candidate;
        			} else {
          				isAmbiguous = true;
          				break;
        			}
     	 		}
      			// 将筛选出的方法添加到getMethods中,并将方法返回值添加到getTypes中
      			addGetMethod(propName, winner, isAmbiguous);
    		}
  	}

  	private void addGetMethod(String name, Method method, boolean isAmbiguous) {
    	MethodInvoker invoker = isAmbiguous
        	? new AmbiguousMethodInvoker(method, MessageFormat.format(
            "Illegal overloaded getter method with ambiguous type for property ''{0}'' in class ''{1}''. This breaks the JavaBeans specification and can cause unpredictable results.",
            name, method.getDeclaringClass().getName()))
        	: new MethodInvoker(method);
    	getMethods.put(name, invoker);
    	//解析返回值类型
    	Type returnType = TypeParameterResolver.resolveReturnType(method, type);
    	//将返回值类型由Type转为Class,并将转换后的结果缓存到setTypes中
    	getTypes.put(name, typeToClass(returnType));
  	}

  以上就是解除冲突的过程,只要记住解决冲突的规则即可理解上面代码的逻辑。相关规则如下:

  1. 冲突方法返回值类型具有继承关系,子类返回值对应方法被认为是更合适的选择。
  2. 冲突方法的返回值类型相同,如果返回值类型为boolean,那么以is开头的方法则是更合适的选择。
  3. 冲突方法的返回值类型相同,但类型非boolean,此时出现歧义,抛出异常。
  4. 冲突方法的返回值类型不相关,无法确定哪个是更好的选择,此时直接抛异常分析完getter方法的解析过程,下面继续分析setter方法的解析过程。
  • 3、Reflector setter方法解析过程
      与getter方法解析过程相比,setter方法的解析过程与此有一定的区别。主要体现在冲突出现的原因,以及冲突的解决方法上:
  	private void addSetMethods(Class<?> clazz) {
   	 	Map<String, List<Method>> conflictingSetters = new HashMap<>();
    	// 获取当前类、接口,以及父类中的方法
    	Method[] methods = getClassMethods(clazz);
    	//过滤出setter方法
    	//setter方法发生冲突原因是:可能存在重载情况,
    	//比如:void setSex(int sex);void setSex(SexEnum sex);
    	Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 1 && PropertyNamer.isSetter(m.getName()))
      	.forEach(m -> addMethodConflict(conflictingSetters, PropertyNamer.methodToProperty(m.getName()), m));
    	//解决setter冲突
    	resolveSetterConflicts(conflictingSetters);
  	}

  setter方法之间出现冲突的原因。即方法存在重载,方法重载导致methodToProperty方法解析出的属性名完全一致。而getter方法之间出现冲突的原因是getXXX和isXXX对应的属性名一致。解决冲突的具体逻辑:

  	private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
    	for (Entry<String, List<Method>> entry : conflictingSetters.entrySet()) {
      		String propName = entry.getKey();
      		List<Method> setters = entry.getValue();
      		/*
	   	 	 * 获取 getter 方法的返回值类型,由于 getter 方法不存在重载的情况,
	     	 * 所以可以用它的返回值类型反推哪个 setter 的更为合适
         	 */
      		Class<?> getterType = getTypes.get(propName);
      		boolean isGetterAmbiguous = getMethods.get(propName) instanceof AmbiguousMethodInvoker;
      		boolean isSetterAmbiguous = false;
      		Method match = null;
      		for (Method setter : setters) {
        		if (!isGetterAmbiguous && setter.getParameterTypes()[0].equals(getterType)) {
          			//参数类型和返回类型一致,则认为是最好的选择,并结束循环
          			match = setter;
          			break;
        		}
        		if (!isSetterAmbiguous) {
          			//选择一个更为合适的方法
          			match = pickBetterSetter(match, setter, propName);
          			isSetterAmbiguous = match == null;
        		}
      		}
      		//将筛选出的方法放入setMethods中,并将方法参数值添加到setTypes中
      		if (match != null) {
        		addSetMethod(propName, match);
      		}
    	}
  	}

  	//从两个setter方法中选择一个更为合适方法
  	private Method pickBetterSetter(Method setter1, Method setter2, String property) {
    	if (setter1 == null) {
      	return setter2;
    	}
   	 	Class<?> paramType1 = setter1.getParameterTypes()[0];
    	Class<?> paramType2 = setter2.getParameterTypes()[0];
    	//如果参数2可赋值给参数1,即参数2是参数1的子类,
    	//则认为参数2对应的setter方法更为合适
    	if (paramType1.isAssignableFrom(paramType2)) {
     		return setter2;
    	} else if (paramType2.isAssignableFrom(paramType1)) {
      		return setter1;
    	}
   	 	MethodInvoker invoker = new AmbiguousMethodInvoker(setter1,
        	MessageFormat.format(
            "Ambiguous setters defined for property ''{0}'' in class ''{1}'' with types ''{2}'' and ''{3}''.",
            property, setter2.getDeclaringClass().getName(), paramType1.getName(), paramType2.getName()));
    	setMethods.put(property, invoker);
    	Type[] paramTypes = TypeParameterResolver.resolveParamTypes(setter1, type);
    	setTypes.put(property, typeToClass(paramTypes[0]));
    	return null;
  	}

  	private void addSetMethod(String name, Method method) {
    	MethodInvoker invoker = new MethodInvoker(method);
    	setMethods.put(name, invoker);
    	//解析参数类型列表
    	Type[] paramTypes = TypeParameterResolver.resolveParamTypes(method, type);
    	//将参数类型由Type转为Class,并将转换后的结果缓存到setTypes
    	setTypes.put(name, typeToClass(paramTypes[0]));
  	}

  setter方法冲突的解析规则:

  1. 冲突方法的参数类型与getter的返回类型一致,则认为是最好的选择。
  2. 冲突方法的参数类型具有继承关系,子类参数对应的方法被认为是更合适选择。
  3. 冲突方法的参数类型不相关,无法确定哪个是更好的选择,此时直接抛异常。
2.4 PropertyTokenizer

  PropertyTokenizer对数组和复合属性均处理:

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
  	private String name;
  	private final String indexedName;
  	private String index;
  	private final String children;

  	public PropertyTokenizer(String fullname) {
  		//检测传入的参数中是否包含字符 '.'
    	int delim = fullname.indexOf('.');
    	if (delim > -1) {
      		/*
	   		 * 以点位为界,进行分割。比如:
       		 * fullname = www.coolblog.xyz
       		 * 以第一个点为分界符:
       		 * name = www
       		 * children = coolblog.xyz
       		 */
      		name = fullname.substring(0, delim);
      		children = fullname.substring(delim + 1);
    	} else {
      		// fullname 中不存在字符 '.'
      		name = fullname;
      		children = null;
    	}
    	indexedName = name;
    	// 检测传入的参数中是否包含字符 '['
    	delim = name.indexOf('[');
    	if (delim > -1) {
      		/*
       		 * 获取中括号里的内容,比如:
      		 * 1. 对于数组或 List 集合:[] 中的内容为数组下标,
      		 * 比如 fullname = articles[1],index = 1
       		 * 2. 对于 Map:[] 中的内容为键,
       		 * 比如 fullname = xxxMap[keyName],index = keyName
      		 */
      		index = name.substring(delim + 1, name.length() - 1);
      		//获取分解符前面的内容,比如:fullname = articles[1],name = articles
      		name = name.substring(0, delim);
    	}
  	}

  	@Override
  	public boolean hasNext() {
    	return children != null;
  	}

  	@Override
  	public PropertyTokenizer next() {
    	//对children进行再次切分,用于解析多重复合属性
    	return new PropertyTokenizer(children);
  	}
}

3、设置< settings >内容到Configuration中

  <settings>节点内容解析出来后,需要有一个存放的地方,以使其他代码可以找到这些配置。这个存放地方就是 Configuration 对象,本节来看一下将<settings>节点内容设置到Configuration对象中的过程。以下的代码仍在XMLConfigBuilder类中:

  	private void settingsElement(Properties props) {
    	//设置autoMappingBehavior属性,默认值为PARTIAL
    	configuration.setAutoMappingBehavior(AutoMappingBehavior.
        	valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    	configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    	//设置cacheEnabled属性,默认值为true
    	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"), false));
    	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.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    	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.setDefaultEnumTypeHandler(
    	resolveClass(props.getProperty("defaultEnumTypeHandler")));
    	configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    	configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    	configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    	configuration.setLogPrefix(props.getProperty("logPrefix"));
    	configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
    	configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
    	configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
  	}

  在上面的代码中,调用了BaseBuilder类中的resolveClass方法:

  	protected <T> Class<? extends T> resolveClass(String alias) {
   	 	if (alias == null) {
      		return null;
    	}
    	try {
      		//通过别名解析
      		return resolveAlias(alias);
    	} catch (Exception e) {
      		throw new BuilderException("Error resolving class. Cause: " + e, e);
    	}
  	}

  	protected final TypeAliasRegistry typeAliasRegistry;
  	protected <T> Class<? extends T> resolveAlias(String alias) {
    	//通过别名注册器解析别名对于的类型Class
    	return typeAliasRegistry.resolveAlias(alias);
  	}

  TypeAliasRegistry的用途就是将别名和类型进行映射,这样就可以用别名表示某个类了,方便使用。既然聊到了别名,那下面就看看别名的配置的解析过程。

4、解析< typeAliases >节点

  MyBatis中,我们可以为自己写的一些类定义一个别名。这样在使用的时候,只需要输入别名即可,无需再把全限定的类名写出来。在MyBatis中,我们有两种方式进行别名配置。第一种是仅配置包名,让 MyBatis去扫描包中的类型,并根据类型得到相应的别名。这种方式可配合Alias注解使用,即通过注解为某个类配置别名,而不是让MyBatis按照默认规则生成别名:

	<typeAliases>
		<package name="xyz.coolblog.test1.model1"/>
		<package name="xyz.coolblog.test2.model2"/>
	</typeAliases>

  第二种方式是通过手动的方式,明确为某个类型配置别名。这种方式的配置示例:

	<typeAliases>
		<typeAlias alias="article" type="xyz.coolblog.model.Article" />
		<typeAlias alias="author" type="xyz.coolblog.model.Author" />
	</typeAliases>

  第一种自动扫描的方式配置起来比较简单,第二种方式比较繁琐,特别是配置项比较多时。
  上面是使用,接下来就看下具体在代码中是怎么解析的,即XMLConfigBuilder中的typeAliasesElement方法:

  	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);
        		//从typeAlias节点中解析别名和类型的映射
        		} else {
          			//获取alias和type属性值,alias不是必填项,可为空
          			String alias = child.getStringAttribute("alias");
          			String type = child.getStringAttribute("type");
          			try {
            			//加载type对应的类型
            			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);
          			}
        		}
      		}
    	}
  	}

  上面的代码通过一个if-else条件分支来处理两种不同的配置。

  • 1、从< typeAlias >节点中解析并注册别名
      在别名的配置中,type属性是必须要配置的,而alias属性则不是必须的。如果使用者未配置alias属性,则需要MyBatis自行为目标类型生成别名。对于别名为空的情况,注册别名的任务交由 registerAlias(Class<?>) 方法处理。若不为空,则由 registerAlias(String,Class<?>) 进行别名注册。这两个方法均在TypeAliasRegistry类中。
  	public void registerAlias(Class<?> type) {
    	//获取全路径类名的简称
    	String alias = type.getSimpleName();
    	Alias aliasAnnotation = type.getAnnotation(Alias.class);
    	if (aliasAnnotation != null) {
      		//从注解中取出别名
      		alias = aliasAnnotation.value();
    	}
    	//调用重载方法注册别名和类型映射
    	registerAlias(alias, type);
  	}

  	public void registerAlias(String alias, Class<?> value) {
    	if (alias == null) {
      		throw new TypeException("The parameter alias cannot be null");
    	}
    	//将别名转成小写
    	String key = alias.toLowerCase(Locale.ENGLISH);
    	//如果TYPE_ALIASES中存在了某个类型映射,这里判断当前类型与映射中的类型
    	//是否一致,不一致则抛出异常,不允许一个别名对应两种类型
    	if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
      		throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    	}
    	//缓存别名到类型映射
    	typeAliases.put(key, value);
  	}

  若用户未明确配置alias属性,MyBatis会使用类名的小写形式作为别名。比如,全限定类名 xyz.test.model.Author的别名为author。若类中有@Alias 注解,则从注解中取值作为别名。

  • 2、从指定的包中解析并注册别名
      从指定的包中解析并注册别名过程主要由别名的解析和注册两步组成,这两个方法均在TypeAliasRegistry类中。
  	public void registerAliases(String packageName) {
    	//调用重载方法注册别名
    	registerAliases(packageName, Object.class);
  	}

  	public void registerAliases(String packageName, Class<?> superType) {
    	ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    	//查找某个包下的父类为 superType 的类。从调用栈来看,这里的
    	//superType = Object.class,所以ResolverUtil将查找所有的类。
    	//查找完成后,查找结果将会被缓存到内部集合中。
    	resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    	//获取查找结果
    	Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    	for (Class<?> type : typeSet) {
      		//忽略匿名类,接口,内部类
      		if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
        		//为类型注册别名
        		registerAlias(type);
      		}
    	}
  	}

  面的代码不多,相关流程也不复杂,可简单总结为下面两个步骤:一是查找指定包下的所有类;二是遍历查找到的类型集合,为每个类型注册别名。第1步的流程总结:

  1. 通过 VFS(虚拟文件系统)获取指定包下的所有文件的路径名
  2. 比如 xyz/test/model/Article.class
  3. 筛选以.class结尾的文件名
  4. 将路径名转成全限定的类名,通过类加载器加载类名
  5. 对类型进行匹配,若符合匹配规则,则将其放入内部集合中
  • 3、注册MyBatis内部类及常见类型的别名
      最后,我们来看一下一些MyBatis内部类及一些常见类型的别名注册过程,以下代码在Configuration类中:
  	public Configuration() {
    	//注册事务工厂的别名
    	typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    	//...
    	//注册数据源的别名
    	typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    	//注册缓存策略的别名
   	 	typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    	typeAliasRegistry.registerAlias("LRU", LruCache.class);
    	//注册日志类的别名
    	typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    	typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    	//注册动态代理工厂的别名
    	typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    	typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
  	}

  以下代码在TypeAliasRegistry类中:

  	public TypeAliasRegistry() {
    	//注册 String 的别名
    	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);
    	//注册Date,BigDecimal,Object等类型的别名
    	registerAlias("date", Date.class);
    	registerAlias("decimal", BigDecimal.class);
    	registerAlias("bigdecimal", BigDecimal.class);
    	registerAlias("biginteger", BigInteger.class);
    	registerAlias("object", Object.class);
    	//注册Date,BigDecimal,Object等数组类型的别名
    	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);
    	//注册ResultSet的别名
    	registerAlias("ResultSet", ResultSet.class);
  	}

  以上就是别名解析的全部流程。

5、解析< 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

  比较常见的插件有分页插件、分表插件。在分析插件的配置的解析过程之前,我们先来了解一下插件的配置:

	<plugins>
		<plugin interceptor="xyz.test.mybatis.ExamplePlugin">
			<property name="key" value="value"/>
		</plugin>
	</plugins>

  关于该配置的解析在XMLConfigBuilder中:

  	private void pluginElement(XNode parent) throws Exception {
    	if (parent != null) {
     	 	for (XNode child : parent.getChildren()) {
        		String interceptor = child.getStringAttribute("interceptor");
        		//获取配置信息
        		Properties properties = child.getChildrenAsProperties();
        		//解析拦截器的类型,并创建拦截器
        		Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        		//设置属性
        		interceptorInstance.setProperties(properties);
        		//添加拦截器到Configuration中
        		configuration.addInterceptor(interceptorInstance);
      		}
    	}
  	}

  插件解析的过程还是比较简单的。首先是获取配置,然后再解析拦截器类型,并实例化拦截器。最后向拦截器中设置属性,并将拦截器添加到Configuration中。

6、解析< environments >节点

  事务管理器和数据源是配置在<environments>节点中的。它们的配置大致如下:

	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC"/>
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driver}"/>
				<property name="url" value="${jdbc.url}"/>
				<property name="username" value="${jdbc.username}"/>
				<property name="password" value="${jdbc.password}"/>
			</dataSource>
			</environment>
	</environments>

  对于上述配置的解析过程也在在XMLConfigBuilder中:

  	private void environmentsElement(XNode context) throws Exception {
    	if (context != null) {
      		if (environment == null) {
        		//获取default属性
        		environment = context.getStringAttribute("default");
      		}
      		for (XNode child : context.getChildren()) {
        		//获取id属性
        		String id = child.getStringAttribute("id");
        		//检测当前environment节点的id与其父节点environments的
        		//属性default内容是否一致,一致则返回true,否则返回false
        		if (isSpecifiedEnvironment(id)) {
          			//解析transactionManager节点
          			TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          			//解析dataSource节点,逻辑和插件的解析逻辑很相似
          			DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          			//创建DataSource对象
          			DataSource dataSource = dsFactory.getDataSource();
          			Environment.Builder environmentBuilder = new Environment.Builder(id)
              			.transactionFactory(txFactory)
              			.dataSource(dataSource);
          			//构建Environment对象,并设置到configuration中
          			configuration.setEnvironment(environmentBuilder.build());
        		}
      		}
    	}
  	}

7、解析< typeHandlers >节点

  在向数据库存储或读取数据时,我们需要将数据库字段类型和Java类型进行一个转换。比如数据库中有CHAR和VARCHAR等类型,但Java中没有这些类型,不过Java有 String类型。所以在从数据库中读取CHAR和VARCHAR类型的数据时,就可以把它们转成String。
  在MyBatis中,数据库类型和Java类型之间的转换任务是委托给类型处理器TypeHandler去处理的。MyBatis提供了一些常见类型的类型处理器,除此之外,还可以自定义类型处理器以非常见类型转换的需求。
  下类型处理器的配置方法:

	<!-- 自动扫描 -->
	<typeHandlers>
		<package name="xyz.test.handlers"/>
	</typeHandlers>
	<!-- 手动配置 -->
	<typeHandlers>
		<typeHandler jdbcType="TINYINT"
			javaType="xyz.test.constant.ArticleTypeEnum"
			handler="xyz.test.mybatis.ArticleTypeHandler"/>
	</typeHandlers>

  使用自动扫描的方式注册类型处理器时,应使用@MappedTypes和@MappedJdbcTypes注解配置javaType 和 jdbcType。其对应的解析代码也在XMLConfigBuilder中:

  	private void typeHandlerElement(XNode parent) {
    	if (parent != null) {
      		for (XNode child : parent.getChildren()) {
        		//从指定的包中注册TypeHandler
        		if ("package".equals(child.getName())) {
          			String typeHandlerPackage = child.getStringAttribute("name");
          			//注册方法①
          			typeHandlerRegistry.register(typeHandlerPackage);
          		//从typeHandler节点中解析别名到类型的映射
        		} else {
          			String javaTypeName = child.getStringAttribute("javaType");
          			String jdbcTypeName = child.getStringAttribute("jdbcType");
          			String handlerTypeName = child.getStringAttribute("handler");
          			//解析上面获取到的属性值
          			Class<?> javaTypeClass = resolveClass(javaTypeName);
          			JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          			Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          			//根据javaTypeClass和jdbcType值的情况进行不同的注册策略
          			if (javaTypeClass != null) {
            			if (jdbcType == null) {
              				//注册方法②
              				typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            			} else {
              				//注册方法③
              				typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            			}
          			} else {
            			//注册方法④
            			typeHandlerRegistry.register(typeHandlerClass);
          			}
        		}
      		}
    	}
  	}

  在上面的代码中,用照①②③④标注了四个重载方法,这些代码均在TypeHandlerRegistry类中。

  • 1、register(Class,JdbcType,Class)方法
      当代码执行到此方法时,表示javaTypeClass!= null && jdbcType != null条件成立,即开发者明确配置了javaType和jdbcType属性的值。
  	public void register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass) {
    	//调用终点方法
    	register(javaTypeClass, jdbcType, getInstance(javaTypeClass, typeHandlerClass));
  	}

  	//类型处理器注册过程的终点
  	private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    	if (javaType != null) {
      		//JdbcType到TypeHandler的映射
      		Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
      		if (map == null || map == NULL_TYPE_HANDLER_MAP) {
        		map = new HashMap<>();
      		}
      		map.put(jdbcType, handler);
      		//存储javaType到Map<JdbcType, TypeHandler>的映射
      		typeHandlerMap.put(javaType, map);
    	}
    	//存储所有的TypeHandler
    	allTypeHandlersMap.put(handler.getClass(), handler);
  	}

  所谓的注册过程也就是把类型和处理器进行映射而已。

  • 2、register(Class,Class)方法
      当代码执行到此方法时,表示javaTypeClass != null && jdbcType == null条件成立,即使用者仅设置了javaType属性的值。
  	public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    	//调用中间方法register(Type, TypeHandler)
    	register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));
  	}

  	private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    	//获取@MappedJdbcTypes注解
    	MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    	if (mappedJdbcTypes != null) {
      		//遍历@MappedJdbcTypes注解中配置的值
      		for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
        		//调用终点方法
        		register(javaType, handledJdbcType, typeHandler);
      		}
      		if (mappedJdbcTypes.includeNullJdbcType()) {
        		//调用终点方法,jdbcType = null
        		register(javaType, null, typeHandler);
      		}
    	} else {
      		//调用终点方法,jdbcType = null
      		register(javaType, null, typeHandler);
    	}
  	}

  上面的逻辑也比较简单,主要做的事情是尝试从注解中获取JdbcType的值。

  • 3、register(Class)方法
      当代码执行到此方法时,表示 javaTypeClass==null&&jdbcType!=null 条件成立,即使用者未配置javaType和jdbcType属性的值。
  	public void register(Class<?> typeHandlerClass) {
    	boolean mappedTypeFound = false;
    	//获取@MappedTypes注解
    	MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    	if (mappedTypes != null) {
      		//遍历@MappedTypes注解中配置的值
      		for (Class<?> javaTypeClass : mappedTypes.value()) {
        		//调用注册方法②
        		register(javaTypeClass, typeHandlerClass);
        		mappedTypeFound = true;
      		}
    	}
    	if (!mappedTypeFound) {
      		//调用中间方法register(TypeHandler)
      		register(getInstance(null, typeHandlerClass));
    	}
  	}

  	public <T> void register(TypeHandler<T> typeHandler) {
    	boolean mappedTypeFound = false;
    	//获取@MappedTypes注解
    	MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    	if (mappedTypes != null) {
      		for (Class<?> handledType : mappedTypes.value()) {
        		//调用中间方法register(Type, TypeHandler)
        		register(handledType, typeHandler);
        		mappedTypeFound = true;
      		}
    	}
    	//自动发现映射类型
    	if (!mappedTypeFound && typeHandler instanceof TypeReference) {
      		try {
        		TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
        		//获取参数模板中的参数类型,并调用中间方法register(Type, TypeHandler)
        		register(typeReference.getRawType(), typeHandler);
        		mappedTypeFound = true;
      		} catch (Throwable t) {
      		}
    	}
    	if (!mappedTypeFound) {
      		//调用中间方法register(Class, TypeHandler)
      		register((Class<T>) null, typeHandler);
    	}
  	}

public <T> void register(
	Class<T> javaType, TypeHandler<? extends T> typeHandler) {
	// 调用中间方法 register(Type, TypeHandler)
	register((Type) javaType, typeHandler);
}

  不管是通过注解的方式,还是通过反射的方式,它们最终目的是为了解析出javaType的值。解析完成后,这些方法会调用中间方法register(Type,TypeHandler),这个方法负责解析jdbcType,该方法上一节已经分析过。一个负责解析javaType,另一个负责解析jdbcType,逻辑比较清晰了。

  • 4、register(String)方法
      本节代码的主要是用于自动扫描类型处理器,并调用其他方法注册扫描结果。
  	public void register(String packageName) {
    	ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    	//从指定包中查找TypeHandler
    	resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
    	Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
    	for (Class<?> type : handlerSet) {
      		//忽略内部类,接口,抽象类等
      		if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
        		//调用注册方法④
        		register(type);
      		}
    	}
  	}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值