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的使用 ...
随机推荐
- 前端ajax传数据成功发送,但后端接收不到
前几天遇到这样的问题,找了好久,是在ajax contentType属性设置的问题. contentType默认是application/x-www-form-urlencoded 但是 ...
- Flask从入门到精通之Flask表单渲染成HTML
表单字段是可调用的,在模板中调用后会渲染成HTML.假设视图函数把一个NameForm 实例通过参数form 传入模板,在模板中可以生成一个简单的表单,如下所示: <form method=&q ...
- [AIR] StageWebView可以和js通信
StageWebView 类别提供简单方法,在不支援HTMLLoader 类别的装置上显示HTML 内容.除了StageWebView 类别本身的方法与属性之外,此类别不提供ActionScript ...
- [JavaScript] 获取昨日前天的日期
var day = new Date(); day.setDate(day.getDate()-1); console(day.pattern('yyyy-MM-dd'));//昨天的日期 day.s ...
- python多态和鸭子类型
多态与多态性 多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承). 比如:文件分为文本文件,可执行文件(在定义角度) 比如 我们按下 F1 键这个动作: 如果当前在 Fl ...
- redis升级注意事项
新版本redis读取 旧版本redis的rdb , aof 使用新版本redis备份rdb , aof操作系统级别备份 rdb , aof重启redis测试 新版本redisrdb , aof 能否读 ...
- PriorityQueue实现大顶堆
在做一道算法时需要使用大顶堆,所以查了一下记录. 使用PriorityQueue实现大顶堆 PriorityQueue默认是一个小顶堆,然而可以通过传入自定义的Comparator函数来实现大顶堆.如 ...
- centos7.2 get pid by process name with python3.6
centos7.2 get pid by process name with python3.6 #-*- encoding:UTF-8 -*- import os import sys import ...
- github的本地配置和项目创建
之前完成了github的安装和账号的注册,接下来要进行项目的创建和本地代码仓库的建立 1.创建项目 2.填写项目相关信息 注意:在给项目起名时,尽量起一些有意义的名字,否则会被管理员删除.因为服务器上 ...
- Javac语法糖之增强for循环
加强的for循环有两种,遍历数组和实现了Iterable接口的容器.javac通过visitForeachLoop()方法来实现解语法糖,代码如下: /** Translate away the fo ...