Mybatis之reflection包源代码解析(一)
一、序言
Mybatis作为ORM,实现了对象与关系数据库间的映射。Mybatis中的映射包含两个方面:
1.将对象中的值(parameterType所指定的对象)映射到具体的sql中,例如:
<insert id="insertAuthor" parameterType="domain.blog.Author">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>
2.将查询出来的结果填充到具体的对象属性中(由resultMap/resultType指定),例如:
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
在使用mybatis时这些传值的对象基本上都是POJO,传入的时候(从对象到sql)就是读对象的属性(调用对象的get/is方法),传出的时候(从sql到对象)就是set对象的属性(调用对象的set方法)。这两种的实现主要是基于Java 的反射机制进行的,只是Mybatis为了更好的满足自己的需要,结合自己的特点进行了二次封装,本文将介绍mybatis的reflection包。为方便介绍,我们默认下面提到的对象都是POJO类型的。

二、各包介绍
从上面的截图可以看到reflection包含了几个子包和一些一级类,我们在介绍时就不按照包的顺序进行介绍,而是按照相互间依赖关系进行介绍,首先介绍被依赖的包和类,而后介绍依赖其他包的包和类,这样介绍的好处是比较容易理解,不需要进行“请见下文”。
2.1 property包
在序言中提到,mybatis中的映射主要就是操作pojo的属性,我们首先来了解下reflection中的property子包的内容。
2.1.1 PropertyCopier类
顾名思义,这个类就是就是将一个对象的属性值赋给另一个对象中对应的属性。
public static void copyBeanProperties(Class<?> type, Object sourceObject,Object distinationObject){
Class<?> parentClass = type;
while(parentClass != null){
try {
Field[] fields = type.getDeclaredFields();
for (Field field : fields) {
//因为getDeclaredFields函数返回的这个类中各种限定符的属性,如果不设置accessible为true,在调用限定符是private的属性时会报错
field.setAccessible(true);
field.set(distinationObject, field.get(sourceObject));
}
} catch (Exception e) {
// 如果发生异常,mybatis中的做法是不做任何的处理,具体的说明如下。但是为了调试用,自己添加的异常打印语句
// Nothing useful to do, will only fail on final fields, which will be ignored.
System.out.println("PropertyCopier's copyBeanProperties is executed! the exception is "+e);
}
// 本类执行完成后,查看父类
parentClass = parentClass.getSuperclass();
}
}
2.1.2 PropertyNamer类
这个类提供了几个用来判断属性特征和从方面名称中获取属性名称的函数,我们首先来看判断一个方法名称是否是操作的一个属性的方法,如注释中所讲的返回true并一定就是一个属性。
/**
* 根据传入的参数判断这个参数是不是应包含属性
* 判断的依据是这个参数是不是以get|set|is开头的。但这个函数的判断依据是比较简单的,这一个必然条件。
* 也就是说如果这个函数返回false,则这个参数肯定部包含属性;反之,如果这个函数返回true,则只能说明这个参数可能包含属性
*2013-9-7 下午12:37:02 by 孙振超
*@param name
*@return
*boolean
*/
public static boolean isProperty(String name) {
return name.startsWith("get") || name.startsWith("set")|| name.startsWith("is");
}
然后我们再看从方面名称中获取属性名称的函数:
public static String methodToProperty(String name) {
//根据java常用语法规则将一个函数转化为属性,如果参数不符合java常用语法规则将会抛出ReflectionException
if (name.startsWith("get") || name.startsWith("set")) {
name = name.substring(3);
}else if (name.startsWith("is")) {
name = name.substring(2);
}else{
throw new ReflectionException("paramter "+name+" cannot convert to a property, because it is not obey to the java base rule;");
}
//对于这个判断为什么这么写,没有彻底弄明白。也许是对于字符串长度大于1且全为大写的数据不做处理吧
if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
name = name.substring(0, 1).toLowerCase(Locale.ENGLISH)+name.substring(1);
}
return name;
}
对于这个类中包含的其他两个函数比较简单,就不在这里罗列了,有兴趣的读者可以查看mybatis的源代码。
2.1.3 PropertyTokenizer类
这个类是property包中的重量级类,该类会被reflection包中其他的类频繁的引用到。这个类实现了Iterable和Iterator这两个接口,但在使用时经常被用到的是Iterator接口中的hasNext这个函数。我们着重了解这个类的属性和构造函数:
//包含四个属性,比较简单
private String name;
private String index;
private String indexedName;
private String children; public PropertyTokenizer(String propertyName) {
// 对参数进行第一次处理,通过“.”分隔符将propertyName分作两部分
int delimiter = propertyName.indexOf(".");
if (delimiter > -1) {
name = propertyName.substring(0, delimiter);
children = propertyName.substring(delimiter + 1);
} else {
name = propertyName;
children = null;
}
indexedName = name;
// 对name进行二次处理,去除“[...]”,并将方括号内的内容赋给index属性,如果name属性中包含“[]”的话
delimiter = propertyName.indexOf("[");
if (delimiter > -1) {
// 先取index内容再截取name更为方便些,要不然还需要一个临时变量,需要三步才能实现
// 这里包含了一个前提:传入的参数如果有有[,则必然存在],并且是属性的最后一个字符
index = name.substring(delimiter + 1, name.length() - 1);
name = name.substring(0, delimiter);
}
}
经常使用的hasNext函数实现比较简单,就是判断children属性是不是为空:
public boolean hasNext() {
// TODO Auto-generated method stub
return children != null;
}
2.2 Invoker包
这个包中对Java的反射调用进行了二次封装,定义了一个Invoker接口和三个具体实现。我们首先来看Invoker接口:
2.2.1 Invoker接口
public interface Invoker {
Object invoke(Object targetObject, Object[] args) throws InvocationTargetException,IllegalAccessException;
Class<?> getType();
}
这个接口定义了两个函数,invoke用来执行具体的调用,getType用来返回调用对象的具体的类型。
2.2.2 SetFieldInvoker和GetFieldInvoker类
这两个类都实现了Invoker接口,都有一个类型为java.lang.reflect.Field的属性,这个属性在初始化时进行设置。
public GetFieldInvoker(Field field){
this.field = field;
}
public SetFieldInvoker(Field field) {
this.field = field;
}
getType函数返回的是Field的类型:
public Class<?> getType() {
// TODO Auto-generated method stub
return field.getType();
}
这两个类最大的不同在于对invoke函数的实现上,一个是调用fieldd的set方法,一个是调用Field的get方法。
public Object invoke(Object targetObject, Object[] args)
throws InvocationTargetException, IllegalAccessException {
field.set(targetObject, args[0]);
return null;
} public Object invoke(Object targetObject, Object[] args)
throws InvocationTargetException, IllegalAccessException {
return field.get(targetObject);
}
2.2.3 MethodInvoker类
这个类相对前面两个类要复杂些,主要复杂的地方在于type的确定,这个type的确定是在构造函数中进行的,我们来看下具体的代码:
//包含的两个属性
private Method method;//基础属性,必备
private Class<?> type; public MethodInvoker(Method method) {
this.method = method;
//method的类型不像Field的类型那样,如果这个method有参数,就取第一个参数的类型;如果没有参数就取这个method的返回值
if (method.getParameterTypes().length >= 1) {
type = method.getParameterTypes()[0];
}else {
type = method.getReturnType();
}
}
2.3 factory包
该包中包含的内容比较少,一个接口,一个实现类。
2.3.1 ObjectFactory接口
POJO类在创建时通常也就两类操作:1)初始化:分带参数和不带参数两种 2)属性赋值。因而ObjectFactory接口也包含了这样的函数,同时考虑到mybatis配置时的特点,添加了一个额外的函数,具体如下:
public interface ObjectFactory {
void setProperties(Properties properties);
//利用默认构造函数创建对象
<T> T createObject(Class<T> type);
//利用带有参数的构造函数创建对象
<T> T createObject(Class<T> type,List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
<T> boolean isCollection(Class<T> type);
}
2.3.2 DefaultObjectFactory接口
上面我们提到初始化对象时可以调用默认构造函数和带有参数的构造函数,DefaultObjectFactory在实现时直接进行了二次包装,将两个函数的实现合二为一。
public <T> T createObject(Class<T> type) {
return createObject(type,null,null);
}
在面向对象的开发中我们会提倡面对接口而不是面向具体实现的编程原则,但是在创建对象时则必须指定一个具体的类,为了解决这个问题,mybatis对常用的集合超类指定了具体的实现类:
protected Class<?> resolveInterface(Class<?> type) {
Class<?> classToCreate = type;
//List
if (type == List.class ||type==Iterable.class || type==Collection.class) {
classToCreate = ArrayList.class;
}else if (type == Map.class) {//Map
classToCreate = HashMap.class;
}else if (type == SortedSet.class) {
classToCreate = SortedSet.class;
}else if (type == Set.class) {
classToCreate = Set.class;
}
return classToCreate;
}
准备工作完成了,下面我们来了解下具体的创建过程,虽然有些复杂,但是对于了解java的反射机制和类安全会有帮助:
try {
//如果参数值或者参数类型为空,则采用默认构造函数进行创建
if (constructorArgTypes == null || constructorArgs== null) {
Constructor<T> constructor = type.getDeclaredConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);//必须设置为true,否则在下面的调用时会抛出IllegalAccessException
}
return constructor.newInstance();
}
//对传入的参数个数进行校验,这个是我自己加的,源代码中没有
if(constructorArgs.size() != constructorArgTypes.size()){
throw new ReflectionException("the size of parameters is not equal to the propeties! constructorArgTypes:"
+constructorArgTypes.size()+" ; constructorArgs:"+constructorArgs.size());
}
//如果参数值或者参数类型不为空,则采用带有参数的构造函数
Constructor<T> constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
} catch (Exception e) {
}
Mybatis之reflection包源代码解析(一)的更多相关文章
- MyBatis官方教程及源代码解析——mapper映射文件
缓存 1.官方文档 MyBatis 包括一个非常强大的查询缓存特性,它能够非常方便地配置和定制. MyBatis 3 中的缓存实现的非常多改进都已经实现了,使得它更加强大并且易于配置. 默认情况下是没 ...
- Mybatis源码学习之parsing包(解析器)(二)
简述 大家都知道mybatis中,无论是配置文件mybatis-config.xml,还是SQL语句,都是写在XML文件中的,那么mybatis是如何解析这些XML文件呢?这就是本文将要学习的就是,m ...
- MyBatis架构设计及源代码分析系列(一):MyBatis架构
如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处. 一.概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 The MyBa ...
- IM通信协议逆向分析、Wireshark自定义数据包格式解析插件编程学习
相关学习资料 http://hi.baidu.com/hucyuansheng/item/bf2bfddefd1ee70ad68ed04d http://en.wikipedia.org/wiki/I ...
- volley源代码解析(七)--终于目的之Response<T>
在上篇文章中,我们终于通过网络,获取到了HttpResponse对象 HttpResponse是android包里面的一个类.然后为了更高的扩展性,我们在BasicNetwork类里面看到.Volle ...
- Cocos2d-x源代码解析(1)——地图模块(3)
接上一章<Cocos2d-x源代码解析(1)--地图模块(2)> 通过前面两章的分析,我们能够知道cocos将tmx的信息结构化到 CCTMXMapInfo.CCTMXTilesetInf ...
- MyBatis 源码分析 - 配置文件解析过程
* 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...
- Android SVG动画PathView源代码解析与使用教程(API 14)
使用的是一个第三方库android-pathview主要是一个自己定义View--PathView.跟全部自己定义View一样,重写了三个构造方法. 而且终于调用三个參数的构造方法,在里面获取自己定义 ...
- Andfix热修复框架原理及源代码解析-上篇
热补丁介绍及Andfix的使用 Andfix热修复框架原理及源代码解析-上篇 Andfix热修复框架原理及源代码解析-下篇 1.不知道怎样使用的同学,建议看看我上一篇写的介绍热补丁和Andfix的使用 ...
随机推荐
- 去除eclipse的validating
删除.project文件中的validator,如 <buildCommand> <name>org.eclipse.wst.jsdt.core.javascriptValid ...
- webstrom 一直反复indexing
从网上找了找答案 好多说 把大的静态文件exclude(在项目文件上右击-->Mark Directory As -->exclude)出去,可是不管用.我刚发生的情况是一直刷新,一遍一遍 ...
- svn update 产生Node remains in conflict的问题
输入:sudo svn revert --depth=infinity frontend/web/js/workplace/organization.js 最后在执行 svn up 就ok了
- Elasticsearch-搜索推荐
对于es中搜索推荐的功能的实现: 1.使用match_phrase_prefix来实现search-time搜索推荐,原理根match_phrase类似,唯一的区别是把最后一个term作为前缀去搜索, ...
- [Python]字典Dictionary、列表List、元组Tuple差异化理解
概述:Python中这三种形式的定义相近,易于混淆,应注意区分. aDict={'a':1, 'b':2, 'c':3, 'd':4, 'e':5} aList=[1,2,3,4,5] aTuple= ...
- Python小白学习之路(二十)—【打开文件的模式二】【文件的其他操作】
打开文件的模式(二) 对于非文本文件,我们只能使用b模式,"b"表示以字节的方式操作(而所有文件也都是以字节的形式存储的,使用这种模式无需考虑文本文件的字符编码.图片文件的jgp格 ...
- Vim编辑器与shell脚本
目录 Vim文本编辑器 Shell脚本 Shell编程变量 流程控制语句 计划任务 ...
- A Node Influence Based Label Propagation Algorithm for Community detection in networks 文章算法实现的疑问
这是我最近看到的一篇论文,思路还是很清晰的,就是改进的LPA算法.改进的地方在两个方面: (1)结合K-shell算法计算量了节点重重要度NI(node importance),标签更新顺序则按照NI ...
- Java Web入门学习(三)Maven的配置与使用国内仓库
一.Maven下载与配置 下载好的压缩包,压缩到D盘,或者其他盘符. 新建系统环境变量 MAVEN_HOME 值是压缩好的Maven目录. 比如: D:\apache-maven- 在path环境变 ...
- springboot打包成jar包后找不到xml,找不到主类的解决方法
springboot打包成jar包后找不到xml,找不到主类的解决方法 请首先保证你的项目能正常运行(即不打包的时候运行无误),我们在打包时经常遇到如下问题: springboot打包成jar包后找不 ...