建造者模式是Java基本设计模式的一种,是经常被使用到的。比如在开发过程中经常使用的lambok插件,在实体类上加上@Builder
就可以使用建造者模式方式构建实体对象。这mybatis中同样也使用到了这种设计模式,但是不是使用的lambok插件。在读取配置文件的时候,需要读取各种类型的标签,让构造器和标签对应,可以将标签的属性及相关的值放到构建器中,然后由构建器构建一个对象,这个对象中封装了标签的信息,提供后续使用。
其实里面还使用到装饰者模式,这篇文章先不说,后续出文再具体说装饰者模式。可以剧透一下,在二级缓存中,使用装饰器模式给二级缓存加各种功能。
mybatis读取配置
涉及到mybatis的配置文件不多,主要就是mybatis-config.xml
核心配置文件,然后就是mapper.xml
映射文件。mybatis在读取配置文件的时候也是按照这个顺序,先读核心配置文件,将核心配置文件中的信息封装到核心配置类Configuration
中。核心配置文件中配置了mapper.xml
的位置,然后根据配置的路径读取mapper.xml
。整个过程思路很清晰,其中涉及到三个核心的类XMLConfigBuilder
、XMLMapperBuilder
、XMLStatementBuilder
。来看下面的图。
从图里面可以看的出来,就是将相关配置文件的信息经过封装,然后统一放到Configuration
配置类中。
XMLConfigBuilder源码
接下来看XMLConfigBuilder
加载核心配置文件的代码,这个过程其实不难,流程也很清晰。
parse
parse
方法是入口,在配置文件被读入到parser
解析器后,会调用这个方法开始解析工作。
//配置类开始解析的方法
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
//设置开始解析的标识,防止重复解析
parsed = true;
//调用解析核心配置文件的方法
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
上面这个方法首先是判断当前配置文件是否已经在解析了,保证同一个文件不会被重复的解析。
parseConfiguration
这里就是开始真正的解析配置文件中的内容啦。
//解析mybatis核心配置文件mybatis-config.xml,configuration是此文件的根目录
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//获取properties标签信息
propertiesElement(root.evalNode("properties"));
//获取settings标签设置信息
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
//获取别名信息
typeAliasesElement(root.evalNode("typeAliases"));
//获取插件信息
pluginElement(root.evalNode("plugins"));
//objectFactory、objectWrapperFactory、reflectorFactory信息(一般项目上用不上这些信息)
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"));
//mapper.xml文件
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
流程很清晰,在之前的基础文章中也写过在核心配置文件中的主要标签,这里基本都体现到了,就是对这些主要的标签进行依次的解析。
这个方法的流程就不介绍了,上面的源码中也有注释。
settingsAsProperties
这里是mybatis-config.xml
核心配置文件中<settings>
标签的解析。
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();//获取所有子标签信息
// Check that all settings are known to the configuration class
//检查settings里面加的配置信息是否是可配置项
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
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;
}
这里就是读取<settings>
标签下的所有子标签,并将子标签信息封装到pros
中返回。读取的过程是在第五行代码。
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
遍历子标签,读取子标签的name
和value
属性,然后封装。没有复杂的逻辑。
到这里这个<settings>
标签下的所有内容就都读取成功啦。
settingsElement
这个方法很实用的,平时在开发过程中都会设置各种配置参数,配置参数也有对应的默认值,什么时候需要配置,什么时候不需要配置使用默认就好。都是在这里面啦。
//将settings标签的子标签配置信息设置到Configuration内,部分配置信息有对应的默认值
private void settingsElement(Properties props) throws Exception {
//autoMappingBehavior,默认值PARTIAL
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
//autoMappingUnknownColumnBehavior,默认值NONE
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
//cacheEnabled,二级缓存全局开关,默认值true
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
//proxyFactory,代理工厂,无默认值
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
//lazyLoadingEnabled,是否可以延迟加载,默认值false
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
//aggressiveLazyLoading,侵略性懒加载属性,也就是按需加载的意思,默认值false
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
//multipleResultSetsEnabled,默认值true
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
//useColumnLabel,默认值true
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
//useGeneratedKeys,使用生成键,默认值false
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
//defaultExecutorType,默认执行器类型,默认值是SIMPLE,可选值SIMPLE, REUSE, BATCH
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
//defaultStatementTimeout,默认值null
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
//defaultFetchSize,默认值null
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
//mapUnderscoreToCamelCase,驼峰规则自动映射,默认是false
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
//safeRowBoundsEnabled,默认值false
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
//localCacheScope,缓存返回,默认是SESSION,可选值SESSION,STATEMENT
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
//jdbcTypeForNull,默认值OTHER,可选值很多,参考JdbcType类
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
//lazyLoadTriggerMethods,懒加载触发的方法,equals,clone,hashCode,toString
//如果重写了这些方法,不涉及关联对象,懒加载也不会被触发
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
//safeResultHandlerEnabled,默认值true
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
//defaultScriptingLanguage,默认脚本语言,无默认值
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
//defaultEnumTypeHandler,枚举类型处理器,无默认值
@SuppressWarnings("unchecked")
Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>) resolveClass(props.getProperty("defaultEnumTypeHandler"));
configuration.setDefaultEnumTypeHandler(typeHandler);
//callSettersOnNulls,默认值false
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
//useActualParamName,默认值true
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
//returnInstanceForEmptyRow,默认值false
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
//logPrefix,日志前缀,无默认值
configuration.setLogPrefix(props.getProperty("logPrefix"));
//logImpl,日志实现类,无默认值
@SuppressWarnings("unchecked")
Class<? extends Log> logImpl = (Class<? extends Log>) resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
//configurationFactory,无默认值
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
里面都是将刚才读取的配置信息放到configuration
中,没有对应值的赋予默认值(缺省值)。
其他标签的解析就不具体去说了,基本都是差不多的套路。