mybatis源码配置文件解析之二:解析settings标签
在前边的博客中分析了mybatis解析properties标签,《mybatis源码配置文件解析之一:解析properties标签》。下面来看解析settings标签的过程。
一、概述
在mybatis的核心配置文件(mybatis-config.xml)文件中,有关于settings标签的配置,如下
<settings>
<!-- 设置日志输出为LOG4J -->
<setting name="logImpl" value="STDOUT_LOGGING" />
<!--将以下画线方式命名的数据库列映射到 Java 对象的驼峰式命名属性中-->
<setting name= "mapUnderscoreToCamelCase" value="true" />
</settings>
上面只简单的给出settings标签的配置,settings标签配置在<configuration>标签中,是<configuration>标签的子标签。在settings标签中可以配置setting子标签,上面是我的一个配置,是以name-value键值对的放式进行配置。这里有个问题setting标签中的name怎么配置,共有多少配置?
二、详述
上面,看到了settings标签的配置方式,下面看其解析过程,在XMLConfigBuilder类中的parseConfiguration方法中有关于该标签的解析,
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//解析properties标签
propertiesElement(root.evalNode("properties"));
//解析settings标签,1、把<setting>标签解析为Properties对象
Properties settings = settingsAsProperties(root.evalNode("settings"));
/*2、对<settings>标签中的<setting>标签中的内容进行解析,这里解析的是<setting name="vfsImpl" value=",">
* VFS是mybatis中用来表示虚拟文件系统的一个抽象类,用来查找指定路径下的资源。上面的key为vfsImpl的value可以是VFS的具体实现,必须
* 是权限类名,多个使用逗号隔开,如果存在则设置到configuration中的vfsImpl属性中,如果存在多个,则设置到configuration中的仅是最后一个
* */
loadCustomVfs(settings);
//解析别名标签,例<typeAlias alias="user" type="cn.com.bean.User"/>
typeAliasesElement(root.evalNode("typeAliases"));
//解析插件标签
pluginElement(root.evalNode("plugins"));
//解析objectFactory标签,此标签的作用是mybatis每次创建结果对象的新实例时都会使用ObjectFactory,如果不设置
//则默认使用DefaultObjectFactory来创建,设置之后使用设置的
objectFactoryElement(root.evalNode("objectFactory"));
//解析objectWrapperFactory标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析reflectorFactory标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//解析environments标签
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//解析<mappers>标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
上面便是parseConfiguration方法,在此方法中下面的方法对settings进行了解析,
//解析settings标签,1、把<setting>标签解析为Properties对象
Properties settings = settingsAsProperties(root.evalNode("settings"));
调用settingsAsProperties方法,从方法名中可以看出要把settings标签中的内容解析到Proerties对象中,因为settings标签中是name-value的配置,刚好解析到Properties中以键值对的形式存储。下面是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
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
//如果获取的配置的<setting name="" value="">信息,name不在metaConfig中,则会抛出异常
//这里metaConfig中的信息是从Configuration类中解析出来的,包含set方法的属性
//所以在配置<setting>标签的时候,其name值可以参考configuration类中的属性,配置为小写
for (Object key : props.keySet()) {
//从metaConfig的relector中的setMethods中判断是否存在该属性,setMethods中存储的是可写的属性,
//所以这里要到setMethods中进行判断
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;
}
1、解析子标签
解析子标签也就是settings标签中的setting标签,使用下面的方法进行解析,
//把<setting name="" value="">标签解析为Properties对象
Properties props = context.getChildrenAsProperties();
调用了getChildrenAsProperties方法,
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;
}
该方法就是解析<settings></settings>标签中的<setting></setting>标签,取出标签中的name和value属性,存储到Properties对象中且返回。
我们再看上面的settingsAsProperties方法,调用上述getChildrenAsProperties方法获得Properties对象后又进行了其他操作。
2、校验setting标签中的name值是否存在
2.1、获得setting标签中的所有name值
在本文开篇提到一个问题,setting标签中的name值怎么配置,答案是可以参考mybatis的官方文档,在官方文档中有详细的解释,再有就是分析源码,继续往下看。
在settingsAsProperties方法中看下面一行代码,
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
上面这行代码就解析了setting标签中的name可以配置的所有值。再看代码上的注释,是不是豁然开朗。该方法有两个参数,一个是Configuration.class,一个是localReflectorFactory,看localReflectorFactory,
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
使用了DefaultReflectorFactory,看其默认构造方法
默认构造方法仅初始化了classCacheEnabled和relectorMap两个属性。后过来继续看MetaClass.forClass方法,
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
return new MetaClass(type, reflectorFactory);
}
该方法返回的是一个MetaClass的对象,
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
this.reflectorFactory = reflectorFactory;
this.reflector = reflectorFactory.findForClass(type);
}
重点看reflectorFactory.findForClass方法,这里reflectorFactory是DefaultReflectorFactory的一个实例。下面是DefaultReflectorFactory的findForClass方法,
@Override
public Reflector findForClass(Class<?> type) {
if (classCacheEnabled) {
// synchronized (type) removed see issue #461
Reflector cached = reflectorMap.get(type);
if (cached == null) {
cached = new Reflector(type);
reflectorMap.put(type, cached);
}
return cached;
} else {
return new Reflector(type);
}
}
上面方法中,重点看new Reflector(type)这句方法,
public Reflector(Class<?> clazz) {
type = clazz;
//解析默认的构造方法,及无参构造方法
addDefaultConstructor(clazz);
//解析clazz中的get方法,这里的clazz指的是Configuration.class
addGetMethods(clazz);
//解析clazz中的set方法,这里的clazz指的是Configuration.class
addSetMethods(clazz);
addFields(clazz);
readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writeablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
此方法完成的功能是解析clazz(包含其父类)的构造方法、getXX方法、setXX方法、字段,通过一个类的Class对象获取。
addDefaultConstructor(clazz)如下,
private void addDefaultConstructor(Class<?> clazz) {
//获得该类的声明的构造方法
Constructor<?>[] consts = clazz.getDeclaredConstructors();
//对构造方法进行循环
for (Constructor<?> constructor : consts) {
//判断构造方法的参数是否为0,为0代表为默认的无参构造方法
if (constructor.getParameterTypes().length == 0) {
//如果是私有的(修饰符为private),这里需要设置可见。
if (canAccessPrivateMethods()) {
try {
constructor.setAccessible(true);
} catch (Exception e) {
// Ignored. This is only a final precaution, nothing we can do.
}
}
if (constructor.isAccessible()) {
this.defaultConstructor = constructor;
}
}
}
}
上面方法获得传入的Class对象所以构造方法,把默认的无参构造方法赋给defaultConstructor。
addGetMethods(clazz)如下,
private void addGetMethods(Class<?> cls) {
Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
//使用反射的放上获得cls的所有方法
Method[] methods = getClassMethods(cls);
//把所有的方法放入conflictingGetters中,key为属性名,value为List<Method>
for (Method method : methods) {
//方法的参数大于0,则结束本次循环,因为这里解析的是get方法,get方法默认不应该有参数
if (method.getParameterTypes().length > 0) {
continue;
}
String name = method.getName();
//如果以get或is开头,且方法名称分别大于3和2,则说明是get方法
if ((name.startsWith("get") && name.length() > 3)
|| (name.startsWith("is") && name.length() > 2)) {
//通过方法名转化为属性名,如,getUserName--userName
name = PropertyNamer.methodToProperty(name); addMethodConflict(conflictingGetters, name, method);
}
}
/**处理一个属性多个get方法的情况,即conflictingGetter方法中一个key对应的value的长度大于1的情况,如下
*key propertyName
*value list<Method> 其长度大于1
*/
resolveGetterConflicts(conflictingGetters);
}
获取所有以get和is开头的方法,调用addMethodConflict方法,这里的方法名直译过来是添加冲突的方法,这里冲突怎么理解,我们看addMethodConflict方法,
private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
//根据字段名取方法
List<Method> list = conflictingMethods.get(name);
if (list == null) {
list = new ArrayList<Method>();
conflictingMethods.put(name, list);
}
list.add(method);
}
这里是根据get和is开头的方法获取属性名作为键值,并且使用list作为value进行存储,为什么使用list那,我们看下面的方法
public void getUser(){}
public User getuser(){}
public List<User> getUser(){}
public void getUser(String id){}
上面三个方法都会以user为键进行存储,但是其方法名是一样的,所以这里要存储为list,即存储多个Method对象。
我们知道一个字段的属性的get或set方法,不可能出现上面的情况,所以针对上面的情况需要做处理,这里调用resolveGetterConflicts(conflicttingGetters),
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
//遍历conflictingGetters
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
Method winner = null;
String propName = entry.getKey();
//循环value这里value是一个List<Method>类型
for (Method candidate : entry.getValue()) {
if (winner == null) {
winner = candidate;
continue;
}
//获得get方法的返回值类型
Class<?> winnerType = winner.getReturnType();
Class<?> candidateType = candidate.getReturnType();
//如果winnerType和candidateType相等,
if (candidateType.equals(winnerType)) {
if (!boolean.class.equals(candidateType)) {
throw new ReflectionException(
"Illegal overloaded getter method with ambiguous type for property "
+ propName + " in class " + winner.getDeclaringClass()
+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
} else if (candidate.getName().startsWith("is")) {
winner = candidate;
}
} else if (candidateType.isAssignableFrom(winnerType)) {
// OK getter type is descendant
} else if (winnerType.isAssignableFrom(candidateType)) {
winner = candidate;
} else {
throw new ReflectionException(
"Illegal overloaded getter method with ambiguous type for property "
+ propName + " in class " + winner.getDeclaringClass()
+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
}
}
addGetMethod(propName, winner);
}
}
上面的方法处理了上面提到的一个属性存在多个get方法的情况,最后调用addGetMethod方法,
private void addGetMethod(String name, Method method) {
if (isValidPropertyName(name)) {
getMethods.put(name, new MethodInvoker(method));
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
getTypes.put(name, typeToClass(returnType));
}
}
上面的方法把信息放到了getMethods和getTyps中,分别存储了get方法和返回值。
上面分析了Reflector中的addGetMethods方法,addSetMethods方法和其处理过程类似,最终把set方法和返回值放到了setMethods和setTypes中。
addFileds(clazz)方法即是处理clazz中的属性,
private void addFields(Class<?> clazz) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (canAccessPrivateMethods()) {
try {
field.setAccessible(true);
} catch (Exception e) {
// Ignored. This is only a final precaution, nothing we can do.
}
}
if (field.isAccessible()) {
//检查是否存在set方法,如果不存在添加该field
if (!setMethods.containsKey(field.getName())) {
// issue #379 - removed the check for final because JDK 1.5 allows
// modification of final fields through reflection (JSR-133). (JGB)
// pr #16 - final static can only be set by the classloader
int modifiers = field.getModifiers();
if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
addSetField(field);
}
}
//检查是否存在get方法,如果不存在添加该field
if (!getMethods.containsKey(field.getName())) {
addGetField(field);
}
}
}
//添加父类的field
if (clazz.getSuperclass() != null) {
addFields(clazz.getSuperclass());
}
}
获得field之后,判断是否在getMethods和setMethods中,如果不在则进行添加,只看addSetField方法,
private void addSetField(Field field) {
if (isValidPropertyName(field.getName())) {
setMethods.put(field.getName(), new SetFieldInvoker(field));
Type fieldType = TypeParameterResolver.resolveFieldType(field, type);
setTypes.put(field.getName(), typeToClass(fieldType));
}
}
从上面看到如果一个field不存在set方法,则生成一个SetFieldInvoker把该对象放入setMethods,从这里可以看出一个setting配置的name值在configuration中可以没有set方法。同理也可以没有get方法。
上面分析完了settingsAsProperties方法中的下面这行代码,
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
把Configuration中的构造方法、get方法、set方法、field放入了metaConfig中的reflector对象中的下列属性
private final String[] readablePropertyNames;
private final String[] writeablePropertyNames;
private final Map<String, Invoker> setMethods = new HashMap<String, Invoker>();
private final Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
private final Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>();
private final Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>();
private Constructor<?> defaultConstructor;
2.2、校验配置的setting标签中的name是否存在
上面分析完了MetaClass.forClass方法,下面看如何对setting标签配置的name进行校验
for (Object key : props.keySet()) {
//从metaConfig的relector中的setMethods中判断是否存在该属性,setMethods中存储的是可写的属性,
//所以这里要到setMethods中进行判断
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
遍历从setting标签解析出来的Properties对象,调用metaConfig.hasSetter方法,
public boolean hasSetter(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
if (reflector.hasSetter(prop.getName())) {
MetaClass metaProp = metaClassForProperty(prop.getName());
return metaProp.hasSetter(prop.getChildren());
} else {
return false;
}
} else {
return reflector.hasSetter(prop.getName());
}
}
看hasSetter的定义
public boolean hasSetter(String propertyName) {
return setMethods.keySet().contains(propertyName);
}
可以看到是判断setMethods是否存在该key,也就是已set方法为表标准,只要在setMethods中,便可以在<setting>标签的name中配置,具体配置值还需要看其类型。
三、总结
上面分析了mybatis的核心配置文件中<settings>标签的解析及子标签中name属性的配置值是怎么取的。如果要扩展核心文件配置中的setting标签的name属性值,需要在configuration中进行配置,及其他操作。
原创不易,有不正之处欢迎指正。
mybatis源码配置文件解析之二:解析settings标签的更多相关文章
- mybatis源码配置文件解析之三:解析typeAliases标签
在前边的博客在分析了mybatis解析settings标签,<mybatis源码配置文件解析之二:解析settings标签>.下面来看解析typeAliases标签的过程. 一.概述 在m ...
- mybatis源码配置文件解析之五:解析mappers标签(解析XML映射文件)
在上篇文章中分析了mybatis解析<mappers>标签,<mybatis源码配置文件解析之五:解析mappers标签>重点分析了如何解析<mappers>标签中 ...
- mybatis源码配置文件解析之五:解析mappers标签
在上篇博客中分析了plugins标签,<mybatis源码配置文件解析之四:解析plugins标签>,了解了其使用方式及背后的原理.现在来分析<mappers>标签. 一.概述 ...
- mybatis源码配置文件解析之四:解析plugins标签
在前边的博客在分析了mybatis解析typeAliases标签,<mybatis源码配置文件解析之三:解析typeAliases标签>.下面来看解析plugins标签的过程. 一.概述 ...
- mybatis源码配置文件解析之一:解析properties标签
mybatis作为日常开发的常用ORM框架,在开发中起着很重要的作用,了解其源码对日常的开发有很大的帮助.源码版本为:3-3.4.x,可执行到github进行下载. 从这篇文章开始逐一分析mybati ...
- mybatis源码配置文件解析之五:解析mappers标签流程图
前面几篇博客分析了mybatis解析mappers标签的过程,主要分为解析package和mapper子标签.补充一张解析的总体过程流程图,画的不好,多多谅解,感谢.
- 浩哥解析MyBatis源码(十一)——Parsing解析模块之通用标记解析器(GenericTokenParser)与标记处理器(TokenHandler)
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6724223.html 1.回顾 上面的几篇解析了类型模块,在MyBatis中类型模块包含的 ...
- mybatis源码阅读-MappedStatement各个属性解析过程(八)
调用方 类org.apache.ibatis.builder.xml.XMLMapperBuilder private void configurationElement(XNode context) ...
- Mybatis源码分析之Mapper文件解析
感觉CSDN对markdown的支持不够友好,总是伴随各种问题,很恼火! xxMapper.xml的解析主要由XMLMapperBuilder类完成,parse方法来完成解析: public void ...
随机推荐
- [枚举] HDU 2019 Multi-University Training Contest 8 - Calabash and Landlord
Calabash and Landlord Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/ ...
- Hook集合----SSDTHook(x86 Win7)
最近在学习Ring0层Hook的一些知识点,很久就写完SSDTHook的代码了,但是一直没有整理成笔记,最近有时间也就整理整理. 介绍: SSDTHook 实质是利用Ntoskrnl.exe 中全局导 ...
- win 7 系统过期处理办法
超级尴尬,刚装的win7 系统居然过期了.下次再也不装盗版了,吼吼吼 处理方法就是下载oem7F7 软件安装,不用再怎么操作就好了. 就是这么简单,并不用像网上那样找什么激活码.
- Dropout的前世与今生
Dropout 是一类用于神经网络训练或推理的随机化技术,这类技术已经引起了研究者们的广泛兴趣,并且被广泛地应用于神经网络正则化.模型压缩等任务.虽然 Dropout 最初是为密集的神经网络层量身定制 ...
- java——构造器理解
构造器理解 什么是构造器 构造器也叫构造方法:用于对象的初始化: 写构造器注意事项 构造器名与类名一致:有返回值但是不能定义返回类型(返回值类型是本类,可以加一个空的return): 构造器的调用 通 ...
- Java&Spring过时的经典语录
字符串拼接:请用StringBuffer代替String直接相加提高性能 过去的理论 有没有人告诉过你开发中不要 String newString = "牛郎"+"织 ...
- 从零开始发布一个ArcGIS Server地图服务
@ 目录 一.软件环境搭建 1.数据库安装 1.1.Oracle(可选) 1.1.1.安装Oracle服务端 1.1.2.安装Oracle客户端 1.2.PostgreSQL(可选) 1.2.1.安装 ...
- Selenium系列(十五) - Web UI 自动化基础实战(2)
如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...
- Ardupilot(PX4)飞控驱动蜂鸣器和RGB细节
Ardupilot(PX4)飞控驱动蜂鸣器细节 飞控代码细节 任务调用频率50HZ(20ms),buzzer.update() 内部将频率减少到10HZ(100ms) 单响(SINGLE_BUZZ) ...
- go语言学习基础-编译文件
1.创建工程在go的src目录下,比如我的go目录为/Users/yinxin/go,我创建文件夹 test路径为/Users/yinxin/go/src/test; 2.创建文件 main.go , ...