MyBatis 启动之 XMLConfigBuilder 解析配置文件(二)

用户头像
ytao
关注
发布于: 2020 年 06 月 04 日
MyBatis启动之XMLConfigBuilder解析配置文件(二)

前言

BaseBuilder所有子类

XMLConfigBuilderBaseBuilder(解析中会涉及到讲解)的其中一个子类,它的作用是把MyBatis的XML及相关配置解析出来,然后保存到Configuration中。本文就解析过程按照执行顺序进行分析,掌握常用配置的解析原理。

使用

调用XMLConfigBuilder进行解析,要进行两步操作,上篇文章中【MyBatis之启动分析(一)】有提到。

实例化XMLConfigBuilder对象。

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 调用父类的构造方法
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}

实例化Configuration

通过new Configuration()的方式实例化:

typeAliasRegistry是一个类型别名注册器,实现原理就是维护一份HashMap,别名作为key,类的全限定名作为value。这里将框架中使用的类注册到类型别名注册器中。TypeAliasRegistry.registerAlias代码如下:

public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
// 在验证是否存在key和保存kv前,统一将key转换成小写
String key = alias.toLowerCase(Locale.ENGLISH);
if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
// 当注册的类型已存在时,抛出异常
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
}
// TYPE_ALIASES 为定义的一个HashMap
TYPE_ALIASES.put(key, value);
}

在实例化Configuration类过程中,在该类里除了实例化了TypeAliasRegistry还实例化了另外一个下面用到的的类:

// 类型处理器注册器
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

TypeHandlerRegistryTypeAliasRegistry实例化逻辑相似,里面注册了一些常用类型和处理器,代码易懂。TypeHandlerRegistry的属性

// jdbc类型和TypeHandler的映射关系,key必须是JdbcType的枚举类型,读取结果集数据时,将jdbc类型转换成java类型
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
// Java类型与JdbcType类型的键值对,存在一对多的映射关系
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
// 没有相应的类型处理器时,使用的处理器
private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
// 类型处理器类类型和类型处理器的映射关系
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
// 空处理器的值,用来做校验
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
// 默认枚举类型处理器
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;

TypeHandlerRegistry构造函数:

public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
register(Byte.class, new ByteTypeHandler());
register(byte.class, new ByteTypeHandler());
register(JdbcType.TINYINT, new ByteTypeHandler());
register(Short.class, new ShortTypeHandler());
register(short.class, new ShortTypeHandler());
register(JdbcType.SMALLINT, new ShortTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler());
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());
register(Float.class, new FloatTypeHandler());
register(float.class, new FloatTypeHandler());
register(JdbcType.FLOAT, new FloatTypeHandler());
register(Double.class, new DoubleTypeHandler());
register(double.class, new DoubleTypeHandler());
register(JdbcType.DOUBLE, new DoubleTypeHandler());
register(Reader.class, new ClobReaderTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.CLOB, new ClobTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CLOB, new ClobTypeHandler());
register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(JdbcType.NVARCHAR, new NStringTypeHandler());
register(JdbcType.NCHAR, new NStringTypeHandler());
register(JdbcType.NCLOB, new NClobTypeHandler());
register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
register(JdbcType.ARRAY, new ArrayTypeHandler());
register(BigInteger.class, new BigIntegerTypeHandler());
register(JdbcType.BIGINT, new LongTypeHandler());
register(BigDecimal.class, new BigDecimalTypeHandler());
register(JdbcType.REAL, new BigDecimalTypeHandler());
register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
register(InputStream.class, new BlobInputStreamTypeHandler());
register(Byte[].class, new ByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
register(byte[].class, new ByteArrayTypeHandler());
register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.BLOB, new BlobTypeHandler());
register(Object.class, UNKNOWN_TYPE_HANDLER);
register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(Date.class, new DateTypeHandler());
register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
register(JdbcType.TIMESTAMP, new DateTypeHandler());
register(JdbcType.DATE, new DateOnlyTypeHandler());
register(JdbcType.TIME, new TimeOnlyTypeHandler());
register(java.sql.Date.class, new SqlDateTypeHandler());
register(java.sql.Time.class, new SqlTimeTypeHandler());
register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
// mybatis-typehandlers-jsr310
// 是否包含日期,时间相关的Api,通过判断是否加载java.time.Clock作为依据
if (Jdk.dateAndTimeApiExists) {
this.register(Instant.class, InstantTypeHandler.class);
this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
this.register(LocalDate.class, LocalDateTypeHandler.class);
this.register(LocalTime.class, LocalTimeTypeHandler.class);
this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
this.register(OffsetTime.class, OffsetTimeTypeHandler.class);
this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
this.register(Month.class, MonthTypeHandler.class);
this.register(Year.class, YearTypeHandler.class);
this.register(YearMonth.class, YearMonthTypeHandler.class);
this.register(JapaneseDate.class, JapaneseDateTypeHandler.class);
}
// issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler());
}

里面调用了两个register()重载方法, type + handler 参的TypeHandlerRegistry.register(Class<T> javaType, TypeHandler<? extends T> typeHandler)type + jdbc type + handler 参的TypeHandlerRegistry.register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler)

// java type + handler
public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
register((Type) javaType, typeHandler);
}
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
if (mappedJdbcTypes != null) {
for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
register(javaType, handledJdbcType, typeHandler);
}
if (mappedJdbcTypes.includeNullJdbcType()) {
register(javaType, null, typeHandler);
}
} else {
register(javaType, null, typeHandler);
}
}
// java type + jdbc type + handler
public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {
register((Type) type, jdbcType, handler);
}
// type + handler 和 type + jdbc type + handler 最终都调用此方法
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
// 当 javaType 不为空时, 获取 java 类型的的映射
Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
// 若映射为空,新建一个映射关系
map = new HashMap<JdbcType, TypeHandler<?>>();
// 保存至类型处理器映射关系中
TYPE_HANDLER_MAP.put(javaType, map);
}
// 保存jdbcType和处理器关系,完成 java类型,jdbc类型,处理器三者之间的注册
map.put(jdbcType, handler);
}
// 保存处理器信息中
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
// MappedJdbcTypes 注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MappedJdbcTypes {
JdbcType[] value();
boolean includeNullJdbcType() default false;
}
  • type + handler方法:先获取处理器的MappedJdbcTypes注解(自定义处理器注解),若注解的value值不为空时,由于该值为JdbcType[]类型,所以for循环 javaType+jdbcType+TypeHandler注册,若includeNullJdbcTypejdbcType是否包含null)为true,默认值为false,注册到相应映射中。若注解的valuenull,直接调用注册操作,里面不会注册type + jdbc type + handler关系。

  • type + jdbc type + handler方法:该方法将java类强制转换为java.lang.reflect.Type类型,然后调用最终注册的方法。

调用父类BaseBuilder的构造方法

BaseBuilder定义有三个属性

protected final Configuration configuration;
// 类型别名注册器
protected final TypeAliasRegistry typeAliasRegistry;
// 类型处理器注册器
protected final TypeHandlerRegistry typeHandlerRegistry;

BaseBuilder构造方法

public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}

这里属性,就是上面讲解到的。

调用 XMLConfigBuilder.parse() 作为解析入口。

parse()实现配置文件是否解析过

public Configuration parse() {
// 若parsed为true,配置文件解析过
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 标志已解析过
parsed = true;
// 从根节点 configuration 开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

解析/configuration里的配置

private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

从上面源码中,不难看出这里是解析/configuration中的各个子节点。

properties 节点解析

properties配置方式
<!-- 方法一 -->
<properties>
<property name="username" value="${jdbc.username}" />
</properties>
<!-- 方法二 -->
<properties resource="xxxConfig.properties">
</properties>
<!-- 方法三 -->
<properties url="file:///D:/xxxConfig.properties">
</properties>
propertiesElement()方法
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 获取 propertie 节点,并保存 Properties 中
Properties defaults = context.getChildrenAsProperties();
// 获取 resource 的值
String resource = context.getStringAttribute("resource");
// 获取 url 的值
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) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
// 将解析的值保存到 XPathParser 中
parser.setVariables(defaults);
// 将解析的值保存到 Configuration 中
configuration.setVariables(defaults);
}
}

从上面源码中,resourceurl的配置形式不允许同时存在,否则抛出BuilderException异常。先解析propertie的配置值,再解析resourceurl的值。当propertie存在与resourceurl相同的key时,propertie的配置会被覆盖,应为Properties实现的原理就是继承的Hashtable类来实现的。

settings 节点解析

settings配置方式
<settings>
<setting name="cacheEnabled" value="true" />
......
</settings>

设置中各项的意图、默认值



settingsAsProperties()方法
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
// 获取setting节点的name和value,并保存至Properties返回
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
// 创建Configuration的MetaClass
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
// 校验Configuration中是否有setting设置的name值
for (Object key : props.keySet()) {
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;
}

这里获取到setting的值,并返回Properties对象。然后做配置的name是否合法。org.apache.ibatis.reflection.MetaClass类是保存着一个利用反射获取到的类信息,metaConfig.hasSetter(String.valueOf(key))是判断metaConfig对象中是否包含key属性。

vfsImpl()方法
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
String value = props.getProperty("vfsImpl");
if (value != null) {
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}

该方法是解析虚拟文件系统配置,用来加载自定义虚拟文件系统的资源。类保存在Configuration.vfsImpl中。

settingsElement()方法

这个方法的作用就是将解析的settings设置到 configuration

private void settingsElement(Properties props) throws Exception {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
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.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")));
@SuppressWarnings("unchecked")
Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
configuration.setDefaultEnumTypeHandler(typeHandler);
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"));
@SuppressWarnings("unchecked")
Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}

typeAliases 节点解析

typeAliases配置方式
<typeAliases>
<package name="com.ytao.main.model"/>
// 或
<typeAlias type="com.ytao.main.model.Student" alias="student"/>
<typeAlias type="com.ytao.main.model.Person"/>
</typeAliases>

该节点是配置类和别名的关系

  1. package节点是配置整个包下的类

  2. typeAlias节点是指定配置单个类,type为必填值且为类全限定名,alias为选填。配置后,是该类时,可直接使用别名。

typeAliasesElement()方法
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 以 package 方式配置
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
// 以 alias 方式配置
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}

使用 package 配置

当扫描package时,获取到包名后TypeAliasRegistry.registerAliases(typeAliasPackage)

public void registerAliases(String packageName){
registerAliases(packageName, Object.class);
}
public void registerAliases(String packageName, Class<?> superType){
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
// 获取 package 下所有已 .class 结尾的文件
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 获取扫描出来的类
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for(Class<?> type : typeSet){
// Ignore inner classes and interfaces (including package-info.java)
// Skip also inner classes. See issue #6
// 过滤类
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
}

扫描到指定package下所有以.class结尾文件的类,并保存至Set集合中,然后遍历集合,过滤掉没有名称,接口,和底层特定类。最后TypeAliasRegistry.registerAlias(Class<?> type)注册到别名注册器中。

public void registerAlias(Class<?> type) {
// 使用类的 simpleName 作为别名,也就是默认的别名命名规则
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
// 上面分析的最终注册的方法
registerAlias(alias, type);
}

通过类注册到注册器中时,如果该注册类有使用@Aliasorg.apache.ibatis.type.Alias)注解,那么XML配置中配置的别名会被注解配置覆盖。

使用 typeAlias 配置

如果typeAliasalias有设置值,使用自定名称方式注册,否则使用默认方式注册,即类的simpleName作为别名。

plugins 节点解析

plugins配置方式
<plugins>
// 配置自定义插件,可指定在某个点进行拦截
<plugin interceptor="com.ytao.main.plugin.DemoInterceptor">
// 当前插件属性
<property name="name" value="100"/>
</plugin>
</plugins>

自定义插件需要实现org.apache.ibatis.plugin.Interceptor接口,同时在注解上指定拦截的方法。

pluginElement()方法
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
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 设置插件属性到插件中
interceptorInstance.setProperties(properties);
// 将插件保存在 configuration 中
configuration.addInterceptor(interceptorInstance);
}
}
}

这里取<plugin>节点的interceptor可以使用别名设置。从源码中resolveClass方法

//
protected Class<?> 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 Class<?> resolveAlias(String alias) {
return typeAliasRegistry.resolveAlias(alias);
}
//
public <T> Class<T> resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// issue #748
// 将传入的 类 名称统一转换
String key = string.toLowerCase(Locale.ENGLISH);
Class<T> value;
// 验证别名中是否有当前传入的key
if (TYPE_ALIASES.containsKey(key)) {
value = (Class<T>) TYPE_ALIASES.get(key);
} else {
value = (Class<T>) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}

以上源码为别名解析过程,其他别名的解析也是调用此方法,先去保存的别名中去找,是否有别名,如果没有就通过Resources.classForName生成实例。

objectFactory,objectWrapperFactory,reflectorFactory 节点解析

以上都是对实现类都是对MyBatis进行扩展。解析方法也类似,最后都是保存在configuration

// objectFactory 解析
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties properties = context.getChildrenAsProperties();
ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
factory.setProperties(properties);
configuration.setObjectFactory(factory);
}
}
// objectWrapperFactory 解析
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
configuration.setObjectWrapperFactory(factory);
}
}
// reflectorFactory 解析
private void reflectorFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
configuration.setReflectorFactory(factory);
}
}

以上为解析objectFactory,objectWrapperFactory,reflectorFactory源码,经过前面的分析后,这里比较容易看懂。

environments 节点解析

environments配置方式
<environments default="development">
<environment id="development">
<!-- 事务管理 -->
<transactionManager type="JDBC">
<property name="prop" value="100"/>
</transactionManager>
<!-- 数据源 -->
<dataSource type="UNPOOLED">
<!-- JDBC 驱动 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!-- 数据库的 url -->
<property name="url" value="${jdbc.url}"/>
<!-- 数据库登录名 -->
<property name="username" value="${jdbc.username}"/>
<!-- 数据库登录密码 -->
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<!-- 一个环境,对应一个environment -->
......
</environments>

该节点可设置多个环境,针对不同的环境单独配置。environments的属性default是默认环境,该值对应一个environment的属性id的值。

  • transactionManager为事务管理,属性type为事务管理类型,上面的介绍的new Configuration()有定义类型有:JDBC 和 MANAGED事务管理类型。

  • dataSource是数据源,type为数据源类型,与transactionManager同理,可知内建的数据源类型有:JNDI,POOLED,UNPOOLED数据源类型。

environmentsElement()方法
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
// 验证 id
if (isSpecifiedEnvironment(id)) {
// 解析 transactionManager, 并实例化 TransactionFactory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 解析 dataSource,并实例化 DataSourceFactory
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
// 获取 dataSource
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
private boolean isSpecifiedEnvironment(String id) {
if (environment == null) {
throw new BuilderException("No environment specified.");
} else if (id == null) {
throw new BuilderException("Environment requires an id attribute.");
} else if (environment.equals(id)) {
return true;
}
return false;
}

若没有配置environment环境或环境没有给id属性,则会抛出异常,若当前id是要使用的就返回true,否则返回falseTransactionFactory实例化过程比较简单,与创建DataSourceFactory类似。

数据源的获取

获取数据源,首先得创建DataSourceFactory,上面使用DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"))创建

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

这里就是获取到数据源得type后,利用上面所讲到得resolveClass()方法获取到DataSourceFactory。以UNPOOLED为例,对应的DataSourceFactory实现类为UnpooledDataSourceFactory。实例化过程中就给该类的属性dataSource数据源赋值了

/**
* UnpooledDataSourceFactory 类
*/
protected DataSource dataSource;
public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
@Override
public DataSource getDataSource() {
return dataSource;
}

UnpooledDataSource类里面有静态代码块所以数据源被加载

/**
* UnpooledDataSource 类
*/
static {
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}

databaseIdProvider 节点解析

databaseIdProvider配置方式
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
<property name="MySQL" value="mysql"/>
</databaseIdProvider>
<select id="select" resultType="com.ytao.main.model.Student" databaseId="mysql">
select
*
from student
</select>

基于映射语句中的databaseId属性,可以根据不同数据库厂商执行不同的sql。

databaseIdProviderElement()方法
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
// 保持向后兼容
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
Properties properties = context.getChildrenAsProperties();
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}

根据匹配的数据库厂商类型匹配数据源databaseIdProvider.getDatabaseId(environment.getDataSource())

@Override
public String getDatabaseId(DataSource dataSource) {
if (dataSource == null) {
throw new NullPointerException("dataSource cannot be null");
}
try {
return getDatabaseName(dataSource);
} catch (Exception e) {
log.error("Could not get a databaseId from dataSource", e);
}
return null;
}
private String getDatabaseName(DataSource dataSource) throws SQLException {
// 根据数据源获取数据库产品名称
String productName = getDatabaseProductName(dataSource);
if (this.properties != null) {
for (Map.Entry<Object, Object> property : properties.entrySet()) {
// 判断是否包含,选择使用的数据库产品
if (productName.contains((String) property.getKey())) {
return (String) property.getValue();
}
}
// no match, return null
return null;
}
return productName;
}
private String getDatabaseProductName(DataSource dataSource) throws SQLException {
Connection con = null;
try {
// 数据库连接
con = dataSource.getConnection();
// 获取连接元数据
DatabaseMetaData metaData = con.getMetaData();
// 获取数据库产品名称
return metaData.getDatabaseProductName();
} finally {
if (con != null) {
try {
con.close();
} catch (SQLException e) {
// ignored
}
}
}
}

这里需要注意的是配置:比如使用mysql,我踩过这里的坑,这里Name为MySQL,我把y写成大写,结果匹配不上。另外这里写个My也能匹配上,应为是使用的String.contains方法,只要包含就会符合,这里代码应该不够严谨。

typeHandlers 节点解析

typeHandlers配置方式
<typeHandlers>
<package name="com.ytao.main.handler"/>
// 或
<typeHandler javaType="java.util.Date" jdbcType="TIMESTAMP" handler="com.ytao.main.handler.DemoDateHandler" />
</typeHandlers>

扫描整个包或者指定类型之间的映射,javaType, jdbcType非必需,handler必填项

typeHandlerElement()方法
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 获取包名
String typeHandlerPackage = child.getStringAttribute("name");
// 注册包下所有的类型处理器
typeHandlerRegistry.register(typeHandlerPackage);
} 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);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}

源码分析会根据包下所有处理器或者指定处理器进行解析,最后会根据上面分析到的type + handlertype + jdbc type + handler不同情况注册。另外这里还有个TypeHandlerRegistry.register(Class<?> typeHandlerClass)注册类

public void register(Class<?> typeHandlerClass) {
// 标志是否从 MappedTypes 注解中获取 javaType 注册
boolean mappedTypeFound = false;
// 获取 MappedTypes 的值
MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> javaTypeClass : mappedTypes.value()) {
// 已 type + handler 的方式注册
register(javaTypeClass, typeHandlerClass);
// 标志已通过注解注册类型
mappedTypeFound = true;
}
}
if (!mappedTypeFound) {
// 通过 TypeHandler 注册
register(getInstance(null, typeHandlerClass));
}
}
// 实例化
public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
if (javaTypeClass != null) {
try {
// 获取有参构造函数
Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
// 实例化对象
return (TypeHandler<T>) c.newInstance(javaTypeClass);
} catch (NoSuchMethodException ignored) {
// ignored
} catch (Exception e) {
throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
}
}
try {
// 获取无参构造函数
Constructor<?> c = typeHandlerClass.getConstructor();
return (TypeHandler<T>) c.newInstance();
} catch (Exception e) {
throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
}
}
// 注册实例
public <T> void register(TypeHandler<T> typeHandler) {
boolean mappedTypeFound = false;
MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> handledType : mappedTypes.value()) {
register(handledType, typeHandler);
mappedTypeFound = true;
}
}
// @since 3.1.0 - try to auto-discover the mapped type
if (!mappedTypeFound && typeHandler instanceof TypeReference) {
try {
TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
register(typeReference.getRawType(), typeHandler);
mappedTypeFound = true;
} catch (Throwable t) {
// maybe users define the TypeReference with a different type and are not assignable, so just ignore it
}
}
if (!mappedTypeFound) {
register((Class<T>) null, typeHandler);
}
}

以上的register方法中,了解type + jdbc type + handler后,其他的register重载方法比较容易理解,其他的都是基于它上面的封装。

mappers 节点解析

mappers配置方式
<mappers>
<package name="com.ytao.main.mapper"/>
// 或
<mapper resource="mapper/studentMapper.xml"/>
// 或
<mapper url="file:///D:/mybatis-3-mybatis-3.4.6/src/main/resources/mapper/studentMapper.xml"/>
// 或
<mapper class="com.ytao.main.mapper.StudentMapper"/>
</mappers>

可通过以上四种形式配置mappers节点,<package><mapper>为互斥节点。

mapperElement()方法

该方法是负责解析<mappers>节点

private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 如果配置 package 节点,则扫描
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
// 解析包下类Mapper接口,并注册到configuration的mapperRegistry中
configuration.addMappers(mapperPackage);
} else {
// 获取mapper节点的resource,url,class属性
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 根据resource解析,并且url,class值必须为空,也就不能配置值。url,class同理,其它两个属性也不能配置值
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
// 通过resource获取流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建XMLMapperBuilder对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析映射配置文件
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
// 通过url获取流
InputStream inputStream = Resources.getUrlAsStream(url);
// 和resource解析方式一样,创建XMLMapperBuilder对象,然后解析映射配置文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 加载class属性的接口
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 将接口注册到configuration的mapperRegistry中
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}

<package>的包扫描到的类,然后单个单个注册到configuration的mapperRegistry中,这里和<mapper>使用class属性是一样逻辑。解析package方式

// Configuration 中定义了
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
/**
* 步骤一
* 该函数于 Configuration 中
*/
public void addMappers(String packageName) {
// mapperRegistry定义在Configuration中的一个属性
mapperRegistry.addMappers(packageName);
}
/**
* 步骤二
* 该函数于 MapperRegistry 中
*/
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
/**
* 步骤三
* 该函数于 MapperRegistry 中
*/
public void addMappers(String packageName, Class<?> superType) {
// 通过 ResolverUtil 获取包下的类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
// 遍历获取到的类,注册到 MapperRegistry
addMapper(mapperClass);
}
}
/**
* 步骤四
* 该函数于 MapperRegistry 中
*/
public <T> void addMapper(Class<T> type) {
// mapper 类为 interface 接口
if (type.isInterface()) {
// 判断当前class是否已经注册过
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
// 校验是否加载完成
boolean loadCompleted = false;
try {
// 保存 mapper 接口和 MapperProxyFactory 之间的映射
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// 解析xml和注解
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
// 标志加载完成
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

解析mapperclass属性

// 该函数于 Configuration 中
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
// ... 这里调用上面的【步骤四】

这两中方式是直接注册接口到mapperRegistry,另外两种是解析xml的方式就是获取映射文件的namespace,再注册进来,XMLMapperBuilder是负责解析映射配置文件的类,今后会单独详细分析这个类,这里不展开讲。



这里对XMLConfigBuilder解析配置文件到此分析完,本文对配置文件解析的流程大致了解流程和原理。相信遇到配置问题异常,大致能排查到根本原因。



推荐阅读

《MyBatis 之启动分析(一)》

《初探 Tomcat 的架构设计》

《Java 线程基础,从这篇开始》

《Java 线程通信之 wait/notify 机制》

《WebSocket 实现 Web 端即时通信》

《基于 Docker 实现 MySQL 主从复制》

《Dubbo 扩展点加载机制:从 Java SPI 到 Dubbo SPI》

发布于: 2020 年 06 月 04 日 阅读数: 36
用户头像

ytao

关注

Java 开发 2018.05.03 加入

公众号:『ytao』❤ 个人博客:https://ytao.top 一个技术人的成长过程中技术,观点分享。专注但不限于java相关技术。

评论

发布
暂无评论
MyBatis启动之XMLConfigBuilder解析配置文件(二)