MyBatis启动之XMLConfigBuilder解析配置文件(二)
Python实战社群
Java实战社群
长按识别下方二维码,按需求添加
扫码关注添加客服
进Python社群▲
扫码关注添加客服
进Java社群▲
作者丨ytao
来源丨ytao
前言
XMLConfigBuilder
是BaseBuilder
(解析中会涉及到讲解)的其中一个子类,它的作用是把MyBatis的XML及相关配置解析出来,然后保存到Configuration
中。本文就解析过程按照执行顺序进行分析,掌握常用配置的解析原理。
使用
调用 XMLConfigBuilder
进行解析,要进行两步操作,上篇文章中【MyBatis之启动分析(一)】有提到。
实例化 XMLConfigBuilder
对象。
privateXMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 调用父类的构造方法
super(newConfiguration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
实例化 Configuration
通过 newConfiguration()
的方式实例化:typeAliasRegistry
是一个类型别名注册器,实现原理就是维护一份 HashMap
,别名作为 key
,类的全限定名作为 value
。这里将框架中使用的类注册到类型别名注册器中。TypeAliasRegistry.registerAlias
代码如下:
publicvoid registerAlias(String alias, Class> value) {
if(alias == null) {
thrownewTypeException("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)) {
// 当注册的类型已存在时,抛出异常
thrownewTypeException("The alias '"+ alias + "' is already mapped to the value '"+ TYPE_ALIASES.get(key).getName() + "'.");
}
// TYPE_ALIASES 为定义的一个HashMap
TYPE_ALIASES.put(key, value);
}
在实例化 Configuration
类过程中,在该类里除了实例化了 TypeAliasRegistry
还实例化了另外一个下面用到的的类:
// 类型处理器注册器
protectedfinalTypeAliasRegistry typeAliasRegistry = newTypeAliasRegistry();
TypeHandlerRegistry
和 TypeAliasRegistry
实例化逻辑相似,里面注册了一些常用类型和处理器,代码易懂。TypeHandlerRegistry
的属性
// jdbc类型和TypeHandler的映射关系,key必须是JdbcType的枚举类型,读取结果集数据时,将jdbc类型转换成java类型
privatefinalMap<JdbcType, TypeHandler>> JDBC_TYPE_HANDLER_MAP = newEnumMap<JdbcType, TypeHandler>>(JdbcType.class);
// Java类型与JdbcType类型的键值对,存在一对多的映射关系
privatefinalMap<Type, Map<JdbcType, TypeHandler>>> TYPE_HANDLER_MAP = newConcurrentHashMap<Type, Map<JdbcType, TypeHandler>>>();
// 没有相应的类型处理器时,使用的处理器
privatefinalTypeHandler<Object> UNKNOWN_TYPE_HANDLER = newUnknownTypeHandler(this);
// 类型处理器类类型和类型处理器的映射关系
privatefinalMap<Class>, TypeHandler>> ALL_TYPE_HANDLERS_MAP = newHashMap<Class>, TypeHandler>>();
// 空处理器的值,用来做校验
privatestaticfinalMap<JdbcType, TypeHandler>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
// 默认枚举类型处理器
privateClass extendsTypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
TypeHandlerRegistry
构造函数:
publicTypeHandlerRegistry() {
register(Boolean.class, newBooleanTypeHandler());
register(boolean.class, newBooleanTypeHandler());
register(JdbcType.BOOLEAN, newBooleanTypeHandler());
register(JdbcType.BIT, newBooleanTypeHandler());
register(Byte.class, newByteTypeHandler());
register(byte.class, newByteTypeHandler());
register(JdbcType.TINYINT, newByteTypeHandler());
register(Short.class, newShortTypeHandler());
register(short.class, newShortTypeHandler());
register(JdbcType.SMALLINT, newShortTypeHandler());
register(Integer.class, newIntegerTypeHandler());
register(int.class, newIntegerTypeHandler());
register(JdbcType.INTEGER, newIntegerTypeHandler());
register(Long.class, newLongTypeHandler());
register(long.class, newLongTypeHandler());
register(Float.class, newFloatTypeHandler());
register(float.class, newFloatTypeHandler());
register(JdbcType.FLOAT, newFloatTypeHandler());
register(Double.class, newDoubleTypeHandler());
register(double.class, newDoubleTypeHandler());
register(JdbcType.DOUBLE, newDoubleTypeHandler());
register(Reader.class, newClobReaderTypeHandler());
register(String.class, newStringTypeHandler());
register(String.class, JdbcType.CHAR, newStringTypeHandler());
register(String.class, JdbcType.CLOB, newClobTypeHandler());
register(String.class, JdbcType.VARCHAR, newStringTypeHandler());
register(String.class, JdbcType.LONGVARCHAR, newClobTypeHandler());
register(String.class, JdbcType.NVARCHAR, newNStringTypeHandler());
register(String.class, JdbcType.NCHAR, newNStringTypeHandler());
register(String.class, JdbcType.NCLOB, newNClobTypeHandler());
register(JdbcType.CHAR, newStringTypeHandler());
register(JdbcType.VARCHAR, newStringTypeHandler());
register(JdbcType.CLOB, newClobTypeHandler());
register(JdbcType.LONGVARCHAR, newClobTypeHandler());
register(JdbcType.NVARCHAR, newNStringTypeHandler());
register(JdbcType.NCHAR, newNStringTypeHandler());
register(JdbcType.NCLOB, newNClobTypeHandler());
register(Object.class, JdbcType.ARRAY, newArrayTypeHandler());
register(JdbcType.ARRAY, newArrayTypeHandler());
register(BigInteger.class, newBigIntegerTypeHandler());
register(JdbcType.BIGINT, newLongTypeHandler());
register(BigDecimal.class, newBigDecimalTypeHandler());
register(JdbcType.REAL, newBigDecimalTypeHandler());
register(JdbcType.DECIMAL, newBigDecimalTypeHandler());
register(JdbcType.NUMERIC, newBigDecimalTypeHandler());
register(InputStream.class, newBlobInputStreamTypeHandler());
register(Byte[].class, newByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.BLOB, newBlobByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.LONGVARBINARY, newBlobByteObjectArrayTypeHandler());
register(byte[].class, newByteArrayTypeHandler());
register(byte[].class, JdbcType.BLOB, newBlobTypeHandler());
register(byte[].class, JdbcType.LONGVARBINARY, newBlobTypeHandler());
register(JdbcType.LONGVARBINARY, newBlobTypeHandler());
register(JdbcType.BLOB, newBlobTypeHandler());
register(Object.class, UNKNOWN_TYPE_HANDLER);
register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(Date.class, newDateTypeHandler());
register(Date.class, JdbcType.DATE, newDateOnlyTypeHandler());
register(Date.class, JdbcType.TIME, newTimeOnlyTypeHandler());
register(JdbcType.TIMESTAMP, newDateTypeHandler());
register(JdbcType.DATE, newDateOnlyTypeHandler());
register(JdbcType.TIME, newTimeOnlyTypeHandler());
register(java.sql.Date.class, newSqlDateTypeHandler());
register(java.sql.Time.class, newSqlTimeTypeHandler());
register(java.sql.Timestamp.class, newSqlTimestampTypeHandler());
// 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, newCharacterTypeHandler());
register(char.class, newCharacterTypeHandler());
}
里面调用了两个 register()
重载方法, type+handler
参的 TypeHandlerRegistry.register(Class
和 type+jdbc type+handler
参的 TypeHandlerRegistry.register(Class
// java type + handler
public
voidregister(Class javaType, TypeHandler extends T> typeHandler) {
register((Type) javaType, typeHandler);
}
private
voidregister(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
voidregister(Class type, JdbcType jdbcType, TypeHandler extends T> handler) {
register((Type) type, jdbcType, handler);
}
// type + handler 和 type + jdbc type + handler 最终都调用此方法
privatevoidregister(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 = newHashMap<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@interfaceMappedJdbcTypes{
JdbcType[] value();
boolean includeNullJdbcType() defaultfalse;
}
type+handler
方法:先获取处理器的MappedJdbcTypes
注解(自定义处理器注解),若注解的value
值不为空时,由于该值为JdbcType[]
类型,所以for
循环javaType+jdbcType+TypeHandler
注册,若includeNullJdbcType
(jdbcType
是否包含null
)为true
,默认值为false
,注册到相应映射中。若注解的value
为null
,直接调用注册操作,里面不会注册type+jdbc type+handler
关系。type+jdbc type+handler
方法:该方法将java类强制转换为java.lang.reflect.Type
类型,然后调用最终注册的方法。
调用父类 BaseBuilder
的构造方法
BaseBuilder
定义有三个属性
protectedfinalConfiguration configuration;
// 类型别名注册器
protectedfinalTypeAliasRegistry typeAliasRegistry;
// 类型处理器注册器
protectedfinalTypeHandlerRegistry typeHandlerRegistry;
BaseBuilder
构造方法
publicBaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
这里属性,就是上面讲解到的。
调用 XMLConfigBuilder.parse()
作为解析入口。
parse()
实现配置文件是否解析过
publicConfiguration parse() {
// 若parsed为true,配置文件解析过
if(parsed) {
thrownewBuilderException("Each XMLConfigBuilder can only be used once.");
}
// 标志已解析过
parsed = true;
// 从根节点 configuration 开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
解析 /configuration
里的配置
privatevoid 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) {
thrownewBuilderException("Error parsing SQL Mapper Configuration. Cause: "+ e, e);
}
}
从上面源码中,不难看出这里是解析 /configuration
中的各个子节点。
properties 节点解析
properties
配置方式
"username" value="${jdbc.username}"/>
"xxxConfig.properties">
"file:///D:/xxxConfig.properties">
propertiesElement()
方法
privatevoid propertiesElement(XNode context) throwsException{
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) {
thrownewBuilderException("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));
} elseif(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);
}
}
从上面源码中, resource
和 url
的配置形式不允许同时存在,否则抛出 BuilderException
异常。先解析 propertie
的配置值,再解析 resource
或 url
的值。当 propertie
存在与 resource
或 url
相同的 key
时, propertie
的配置会被覆盖,应为 Properties
实现的原理就是继承的 Hashtable
类来实现的。
settings 节点解析
settings
配置方式
"cacheEnabled" value="true"/>
......
设置中各项的意图、默认值(引用来源:w3cschool)
设置参数 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 该配置影响的所有映射器中配置的缓存的全局开关。 | true,false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 | true,false | false |
aggressiveLazyLoading | 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。 | true,false | |
multipleResultSetsEnabled | 是否允许单一语句返回多结果集(需要兼容驱动)。 | true,false | true |
useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 | true,false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要驱动兼容。如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 | true,false | False |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements);BATCH 执行器将重用语句并执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数。 | Any positive integer | Not Set (null) |
safeRowBoundsEnabled | 允许在嵌套语句中使用分页(RowBounds)。 | true,false | False |
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 | true, false | False |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 | SESSION,STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER | OTHER |
lazyLoadTriggerMethods | 指定哪个对象的方法触发一次延迟加载。 | A method name list separated by commas | equals,clone,hashCode,toString |
defaultScriptingLanguage | 指定动态 SQL 生成的默认语言。 | A type alias or fully qualified class name. | org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver |
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。 | true,false | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。Any String | Not set | |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J, LOG4J, LOG4J2, JDKLOGGING, COMMONSLOGGING, STDOUTLOGGING, NOLOGGING | Not set |
proxyFactory | 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 | CGLIB JAVASSIST | CGLIB |
settingsAsProperties()
方法
privateProperties settingsAsProperties(XNode context) {
if(context == null) {
returnnewProperties();
}
// 获取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))) {
thrownewBuilderException("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()
方法
privatevoid loadCustomVfs(Properties props) throwsClassNotFoundException{
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
中
privatevoid settingsElement(Properties props) throwsException{
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 extendsTypeHandler> typeHandler = (Class extendsTypeHandler>)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 extendsLog> logImpl = (Class extendsLog>)resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
typeAliases 节点解析
typeAliases
配置方式
<package name="com.ytao.main.model"/>
// 或
"com.ytao.main.model.Student" alias="student"/>
"com.ytao.main.model.Person"/>
该节点是配置类和别名的关系
package
节点是配置整个包下的类typeAlias
节点是指定配置单个类,type
为必填值且为类全限定名,alias
为选填。配置后,是该类时,可直接使用别名。
typeAliasesElement()
方法
privatevoid 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) {
thrownewBuilderException("Error registering typeAlias for '"+ alias + "'. Cause: "+ e, e);
}
}
}
}
}
使用 package 配置
当扫描 package
时,获取到包名后 TypeAliasRegistry.registerAliases(typeAliasPackage)
publicvoid registerAliases(String packageName){
registerAliases(packageName, Object.class);
}
publicvoid registerAliases(String packageName, Class> superType){
ResolverUtil<Class>> resolverUtil = newResolverUtil<Class>>();
// 获取 package 下所有已 .class 结尾的文件
resolverUtil.find(newResolverUtil.IsA(superType), packageName);
// 获取扫描出来的类
Set<Class extendsClass>>> 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)
注册到别名注册器中。
publicvoid registerAlias(Class> type) {
// 使用类的 simpleName 作为别名,也就是默认的别名命名规则
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if(aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
// 上面分析的最终注册的方法
registerAlias(alias, type);
}
通过类注册到注册器中时,如果该注册类有使用 @Alias
( org.apache.ibatis.type.Alias
)注解,那么XML配置中配置的别名会被注解配置覆盖。
使用 typeAlias 配置
如果 typeAlias
的 alias
有设置值,使用自定名称方式注册,否则使用默认方式注册,即类的simpleName作为别名。
plugins 节点解析
plugins
配置方式
// 配置自定义插件,可指定在某个点进行拦截
"com.ytao.main.plugin.DemoInterceptor">
// 当前插件属性
"name" value="100"/>
自定义插件需要实现 org.apache.ibatis.plugin.Interceptor
接口,同时在注解上指定拦截的方法。
pluginElement()
方法
privatevoid pluginElement(XNode parent) throwsException{
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);
}
}
}
这里取
节点的 interceptor
可以使用别名设置。从源码中 resolveClass
方法
//
protectedClass> resolveClass(String alias) {
if(alias == null) {
returnnull;
}
try{
return resolveAlias(alias);
} catch(Exception e) {
thrownewBuilderException("Error resolving class. Cause: "+ e, e);
}
}
//
protectedClass> resolveAlias(String alias) {
return typeAliasRegistry.resolveAlias(alias);
}
//
public
Class resolveAlias( String string) {
try{
if(string == null) {
returnnull;
}
// issue #748
// 将传入的 类 名称统一转换
String key = string.toLowerCase(Locale.ENGLISH);
Class
value;
// 验证别名中是否有当前传入的key
if(TYPE_ALIASES.containsKey(key)) {
value = (Class
) TYPE_ALIASES.get(key);
} else{
value = (Class
) Resources.classForName(string);
}
return value;
} catch(ClassNotFoundException e) {
thrownewTypeException("Could not resolve type alias '"+ string + "'. Cause: "+ e, e);
}
}
以上源码为别名解析过程,其他别名的解析也是调用此方法,先去保存的别名中去找,是否有别名,如果没有就通过 Resources.classForName
生成实例。
objectFactory,objectWrapperFactory,reflectorFactory 节点解析
以上都是对实现类都是对MyBatis进行扩展。解析方法也类似,最后都是保存在 configuration
。
// objectFactory 解析
privatevoid objectFactoryElement(XNode context) throwsException{
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 解析
privatevoid objectWrapperFactoryElement(XNode context) throwsException{
if(context != null) {
String type = context.getStringAttribute("type");
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
configuration.setObjectWrapperFactory(factory);
}
}
// reflectorFactory 解析
privatevoid reflectorFactoryElement(XNode context) throwsException{
if(context != null) {
String type = context.getStringAttribute("type");
ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
configuration.setReflectorFactory(factory);
}
}
以上为解析 objectFactory,objectWrapperFactory,reflectorFactory
源码,经过前面的分析后,这里比较容易看懂。
environments 节点解析
environments
配置方式
default="development">
"development">
"JDBC">
"prop" value="100"/>
"UNPOOLED">
"driver" value="com.mysql.jdbc.Driver"/>
"url" value="${jdbc.url}"/>
"username" value="${jdbc.username}"/>
"password" value="${jdbc.password}"/>
......
该节点可设置多个环境,针对不同的环境单独配置。environments
的属性 default
是默认环境,该值对应一个 environment
的属性 id
的值。
transactionManager
为事务管理,属性type
为事务管理类型,上面的介绍的newConfiguration()
有定义类型有:JDBC 和 MANAGED事务管理类型。dataSource
是数据源,type
为数据源类型,与transactionManager
同理,可知内建的数据源类型有:JNDI,POOLED,UNPOOLED数据源类型。
environmentsElement()
方法
privatevoid environmentsElement(XNode context) throwsException{
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 = newEnvironment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
privateboolean isSpecifiedEnvironment(String id) {
if(environment == null) {
thrownewBuilderException("No environment specified.");
} elseif(id == null) {
thrownewBuilderException("Environment requires an id attribute.");
} elseif(environment.equals(id)) {
returntrue;
}
returnfalse;
}
若没有配置 environment
环境或环境没有给 id
属性,则会抛出异常,若当前 id
是要使用的就返回 true
,否则返回 false
。TransactionFactory
实例化过程比较简单,与创建 DataSourceFactory
类似。
数据源的获取
获取数据源,首先得创建 DataSourceFactory
,上面使用 DataSourceFactorydsFactory=dataSourceElement(child.evalNode("dataSource"))
创建
privateDataSourceFactory dataSourceElement(XNode context) throwsException{
if(context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
thrownewBuilderException("Environment declaration requires a DataSourceFactory.");
}
这里就是获取到数据源得 type
后,利用上面所讲到得 resolveClass()
方法获取到 DataSourceFactory
。以 UNPOOLED
为例,对应的 DataSourceFactory
实现类为 UnpooledDataSourceFactory
。实例化过程中就给该类的属性 dataSource
数据源赋值了
/**
* UnpooledDataSourceFactory 类
*/
protectedDataSource dataSource;
publicUnpooledDataSourceFactory() {
this.dataSource = newUnpooledDataSource();
}
@Override
publicDataSource 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
配置方式
"DB_VENDOR">
"SQL Server" value="sqlserver"/>
"DB2" value="db2"/>
"Oracle" value="oracle"/>
"MySQL" value="mysql"/>
select
*
from student
基于映射语句中的 databaseId
属性,可以根据不同数据库厂商执行不同的sql。
databaseIdProviderElement()
方法
privatevoid databaseIdProviderElement(XNode context) throwsException{
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
publicString getDatabaseId(DataSource dataSource) {
if(dataSource == null) {
thrownewNullPointerException("dataSource cannot be null");
}
try{
return getDatabaseName(dataSource);
} catch(Exception e) {
log.error("Could not get a databaseId from dataSource", e);
}
returnnull;
}
privateString getDatabaseName(DataSource dataSource) throwsSQLException{
// 根据数据源获取数据库产品名称
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
returnnull;
}
return productName;
}
privateString getDatabaseProductName(DataSource dataSource) throwsSQLException{
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
配置方式
<package name="com.ytao.main.handler"/>
// 或
"java.util.Date" jdbcType="TIMESTAMP" handler="com.ytao.main.handler.DemoDateHandler"/>
扫描整个包或者指定类型之间的映射, javaType
, jdbcType
非必需, handler
必填项
typeHandlerElement()
方法
privatevoid typeHandlerElement(XNode parent) throwsException{
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+handler
和 type+jdbc type+handler
不同情况注册。另外这里还有个 TypeHandlerRegistry.register(Class>typeHandlerClass)
注册类
publicvoidregister(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
TypeHandler getInstance( Class> javaTypeClass, Class> typeHandlerClass) {
if(javaTypeClass != null) {
try{
// 获取有参构造函数
Constructor> c = typeHandlerClass.getConstructor(Class.class);
// 实例化对象
return(TypeHandler
) c.newInstance(javaTypeClass);
} catch(NoSuchMethodException ignored) {
// ignored
} catch(Exception e) {
thrownewTypeException("Failed invoking constructor for handler "+ typeHandlerClass, e);
}
}
try{
// 获取无参构造函数
Constructor> c = typeHandlerClass.getConstructor();
return(TypeHandler
) c.newInstance();
} catch(Exception e) {
thrownewTypeException("Unable to find a usable constructor for "+ typeHandlerClass, e);
}
}
// 注册实例
public
voidregister(TypeHandler 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 instanceofTypeReference) {
try{
TypeReference
typeReference = ( TypeReference) 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
) null, typeHandler);
}
}
以上的 register
方法中,了解 type+jdbc type+handler
后,其他的 register
重载方法比较容易理解,其他的都是基于它上面的封装。
mappers 节点解析
mappers
配置方式
<package name="com.ytao.main.mapper"/>
// 或
"mapper/studentMapper.xml"/>
// 或
"file:///D:/mybatis-3-mybatis-3.4.6/src/main/resources/mapper/studentMapper.xml"/>
// 或
class="com.ytao.main.mapper.StudentMapper"/>
可通过以上四种形式配置 mappers
节点,
和
为互斥节点。
mapperElement()
方法
该方法是负责解析
节点
privatevoid mapperElement(XNode parent) throwsException{
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 = newXMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析映射配置文件
mapperParser.parse();
} elseif(resource == null&& url != null&& mapperClass == null) {
ErrorContext.instance().resource(url);
// 通过url获取流
InputStream inputStream = Resources.getUrlAsStream(url);
// 和resource解析方式一样,创建XMLMapperBuilder对象,然后解析映射配置文件
XMLMapperBuilder mapperParser = newXMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} elseif(resource == null&& url == null&& mapperClass != null) {
// 加载class属性的接口
Class> mapperInterface = Resources.classForName(mapperClass);
// 将接口注册到configuration的mapperRegistry中
configuration.addMapper(mapperInterface);
} else{
thrownewBuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
的包扫描到的类,然后单个单个注册到configuration的mapperRegistry中,这里和
使用 class
属性是一样逻辑。解析 package
方式
// Configuration 中定义了
protectedfinalMapperRegistry mapperRegistry = newMapperRegistry(this);
/**
* 步骤一
* 该函数于 Configuration 中
*/
publicvoid addMappers(String packageName) {
// mapperRegistry定义在Configuration中的一个属性
mapperRegistry.addMappers(packageName);
}
/**
* 步骤二
* 该函数于 MapperRegistry 中
*/
publicvoid addMappers(String packageName) {
addMappers(packageName, Object.class);
}
/**
* 步骤三
* 该函数于 MapperRegistry 中
*/
publicvoid addMappers(String packageName, Class> superType) {
// 通过 ResolverUtil 获取包下的类
ResolverUtil<Class>> resolverUtil = newResolverUtil<Class>>();
resolverUtil.find(newResolverUtil.IsA(superType), packageName);
Set<Class extendsClass>>> mapperSet = resolverUtil.getClasses();
for(Class> mapperClass : mapperSet) {
// 遍历获取到的类,注册到 MapperRegistry
addMapper(mapperClass);
}
}
/**
* 步骤四
* 该函数于 MapperRegistry 中
*/
public
void addMapper(Class type) {
// mapper 类为 interface 接口
if(type.isInterface()) {
// 判断当前class是否已经注册过
if(hasMapper(type)) {
thrownewBindingException("Type "+ type + " is already known to the MapperRegistry.");
}
// 校验是否加载完成
boolean loadCompleted = false;
try{
// 保存 mapper 接口和 MapperProxyFactory 之间的映射
knownMappers.put(type, newMapperProxyFactory
(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 = newMapperAnnotationBuilder(config, type);
parser.parse();
// 标志加载完成
loadCompleted = true;
} finally{
if(!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
解析 mapper
的 class
属性
// 该函数于 Configuration 中
public
void addMapper(Class type) {
mapperRegistry.addMapper(type);
}
// ... 这里调用上面的【步骤四】
这两中方式是直接注册接口到 mapperRegistry
,另外两种是解析 xml
的方式就是获取映射文件的 namespace
,再注册进来, XMLMapperBuilder
是负责解析映射配置文件的类,今后会单独详细分析这个类,这里不展开讲。
这里对XMLConfigBuilder解析配置文件到此分析完,本文对配置文件解析的流程大致了解流程和原理。相信遇到配置问题异常,大致能排查到根本原因
近期精彩内容推荐: