在前边的博客中分析了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标签的更多相关文章

  1. mybatis源码配置文件解析之三:解析typeAliases标签

    在前边的博客在分析了mybatis解析settings标签,<mybatis源码配置文件解析之二:解析settings标签>.下面来看解析typeAliases标签的过程. 一.概述 在m ...

  2. mybatis源码配置文件解析之五:解析mappers标签(解析XML映射文件)

    在上篇文章中分析了mybatis解析<mappers>标签,<mybatis源码配置文件解析之五:解析mappers标签>重点分析了如何解析<mappers>标签中 ...

  3. mybatis源码配置文件解析之五:解析mappers标签

    在上篇博客中分析了plugins标签,<mybatis源码配置文件解析之四:解析plugins标签>,了解了其使用方式及背后的原理.现在来分析<mappers>标签. 一.概述 ...

  4. mybatis源码配置文件解析之四:解析plugins标签

    在前边的博客在分析了mybatis解析typeAliases标签,<mybatis源码配置文件解析之三:解析typeAliases标签>.下面来看解析plugins标签的过程. 一.概述 ...

  5. mybatis源码配置文件解析之一:解析properties标签

    mybatis作为日常开发的常用ORM框架,在开发中起着很重要的作用,了解其源码对日常的开发有很大的帮助.源码版本为:3-3.4.x,可执行到github进行下载. 从这篇文章开始逐一分析mybati ...

  6. mybatis源码配置文件解析之五:解析mappers标签流程图

    前面几篇博客分析了mybatis解析mappers标签的过程,主要分为解析package和mapper子标签.补充一张解析的总体过程流程图,画的不好,多多谅解,感谢.

  7. 浩哥解析MyBatis源码(十一)——Parsing解析模块之通用标记解析器(GenericTokenParser)与标记处理器(TokenHandler)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6724223.html 1.回顾 上面的几篇解析了类型模块,在MyBatis中类型模块包含的 ...

  8. mybatis源码阅读-MappedStatement各个属性解析过程(八)

    调用方 类org.apache.ibatis.builder.xml.XMLMapperBuilder private void configurationElement(XNode context) ...

  9. Mybatis源码分析之Mapper文件解析

    感觉CSDN对markdown的支持不够友好,总是伴随各种问题,很恼火! xxMapper.xml的解析主要由XMLMapperBuilder类完成,parse方法来完成解析: public void ...

随机推荐

  1. C# NAudio录音和播放音频文件及实时绘制音频波形图(从音频流数据获取,而非设备获取)

    下午写了一篇关于NAudio的录音.播放和波形图的博客,不太满意,感觉写的太乱,又总结了下 NAudio是个相对成熟.开源的C#音频开发工具,它包含录音.播放录音.格式转换.混音调整等功能.本次介绍主 ...

  2. 1+1>2:MIT&IBM提出结合符号主义和连接主义的高效、准确新模型

    自人工智能的概念提出以来,关于符号主义和连接主义的争论就不绝于耳.究竟哪一种方式可以实现更好的人工智能?这一问题目前还没有定论.深度学习的快速发展让我们看到连接主义在构建 AI 系统中的优势,但其劣势 ...

  3. 一夜搞懂 | JVM 类加载机制

    前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习导图 一.为什么要学习类加载机制? 今天想跟大家唠嗑唠嗑Java的类加载机制,这是Java的一个很重要的创 ...

  4. python学习要点(一)

    我的个人博客排版更舒服: https://www.luozhiyun.com/archives/264 列表和元组 列表是动态的,长度大小不固定,可以随意地增加.删减或者改变元素(mutable). ...

  5. pre-commit + imagemin 实现图片自动压缩

    我们日常开发的前端项目中,图片资源会占到项目资源的很大比例,因此在考虑到性能优化,页面加载速度的时候,如何更好地处理图片就非常重要了. 首先我们可以想到的方案是:使用webpack的image-web ...

  6. JS 剑指Offer(四) 从尾到头打印链表

    题目:输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回). 首先定义一下链表中的节点,关于链表这个数据结构在另外一篇文章中会详细讲 function ListNode(val) { t ...

  7. 解决Python pip安装第三方包慢的问题

    解决Python pip安装第三方包慢的问题 主要是修改源,国内的源有几个 阿里云 http://mirrors.aliyun.com/pypi/simple/ 中国科技大学 https://pypi ...

  8. CentOS下的Docker离线安装

    Linux下离线安装Docker 一.基础环境 1.操作系统:CentOS 7.3 2.Docker版本:18.06.1 官方下载地址(打不开可能很慢) 3.百度云Docker 18.06.1地址:h ...

  9. Azure安装win2016的服务器,并下载安装mysql数据库心得

    随便写写 第一部分:新建虚拟机创建win2016服务器 这部分内容跟着微软云提示操作即可, 基本步骤:创建一堆名字,选择一个地区的服务器,配置一些基本信息,然后azure就会自动创建虚拟机并安装你选择 ...

  10. vue中使用阿里图标库iconfont和在旧有的iconfont中添加新的图标

    第一步 下载样式http://www.iconfont.cn/选择图表,点击加入购物车 第二步 解压下载文件 第三步 修改文件名称 与 iconfont.css 名路径 第四步 将@font-face ...