在前边的博客中分析了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. elasticsearch实战(1)-单机快速部署

    1. 场景描述 elasticsearch只用过,没有部署或者维护过,从头完整走一遍,记录下,原创实战,有需要的朋友参考下. 2 . 解决方案 特别说下,以前win7下安装的3台虚拟机,没有联网,因为 ...

  2. 解决在linux下的eclipse syso Alt+/无法使用

    1.绑定快捷键 2.配置proposal

  3. OpenCV-Python 理解特征 | 三十六

    目标 在本章中,我们将尝试理解什么是特征,为什么拐角重要等等 解释 你们大多数人都会玩拼图游戏.你会得到很多小图像,需要正确组装它们以形成大的真实图像.问题是,你怎么做?将相同的理论投影到计算机程序上 ...

  4. List<Object>转List<T>

    今天遇到一个麻烦,公司有个项目用了一个封装dao的模板,他妈的不管是查一条数据的方法,还是查一个集合数据的方法,全都返回Object或List<Object> 由于对象是Object根本不 ...

  5. Linux下段错误(C语言)

    问题描述:在Linux下编程有时会出现段错误的提醒,出现这种错误有可能是因为以下几种原因 1.数组越界:如果在初始化或者接收输入时内容超过了定义好的数组元素个数时会出现段错误,Linux的数组越界检查 ...

  6. Hadoop 集群的基准测试

    集群搭建完毕之后,需要对集群进行基准测试 第一方面:基本测试 服务启动,是否可用,简单的应用 *hdfs 读写操作 bin/hdfs dfs -mkdir - p /user/chris/tmp/co ...

  7. 【译】Java SE 14 Hotspot 虚拟机垃圾回收调优指南

    原文链接:HotSpot Virtual Machine Garbage Collection Tuning Guide,基于Java SE 14. 本文主要包括以下内容: 优化目标与策略(Ergon ...

  8. linux进程和线程直接通信方式梳理

    对于linux的进程之间.线程直接的通信方式进行梳理,这些都属于基本知识,不过因为知识体系“年久失修”,需要重新总结汇总.

  9. Elasticsearch7.6学习笔记1 Getting start with Elasticsearch

    Elasticsearch7.6学习笔记1 Getting start with Elasticsearch 前言 权威指南中文只有2.x, 但现在es已经到7.6. 就安装最新的来学下. 安装 这里 ...

  10. 【Redis】集群NetCore实战

    环境准备 1. Redis集群(Windows集群搭建) 启动Redis集群,给每个节点加上Title start .conf start .conf start .conf start .conf ...