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的使用 ...
随机推荐
- Flask的WTforms
一.简单介绍 WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证. 类似于Django中的modelform 安装: pip3 install wtforms 二.简 ...
- super()的作用
super能够用来訪问父类的构造方法和被子类所隐藏的方法.假设子类中有方法与父类中的方法名称和參数同样,则父类中的方法就被隐藏起来,也就是说在子类中重载了父类中的方法. 引用父类中所隐藏的语法格式例如 ...
- 使用Maven命令行快速创建项目骨架(archetype)
> mvn archetype:generate 接下来就会输出一些列带索引变化的archetype项可供我们选择,然后提示我们选择一个编号,可以直接回车选择默认的编号(392),然后就跟着 ...
- VSTO学习(四)——自定义Excel UI 转载
本专题概要 引言 自定义任务窗体(Task Pane) 自定义选项卡,即Ribbon 自定义上下文菜单 小结 引言 在上一个专题中为大家介绍如何创建Excel的解决方案,相信大家通过从上面一个专题之后 ...
- (转)MySQL中show语法
MySQL中show语法 1. show tables或show tables from database_name; -- 显示当前数据库中所有表的名称. 2. show databases; -- ...
- Apache JMeter2.13 实战
安装目录下 设置浏览器代理127.0.0.1 8080,以chrome为例 开始录制脚本,进入应用点击相应的功能,可以捕获到如下地址 去除无用地址,保留需要测试的地址 注:上图编号列表中11为获取co ...
- 【Java并发编程】:线程挂起、恢复与终止
挂起和恢复线程 Thread 的API中包含两个被淘汰的方法,它们用于临时挂起和重启某个线程,这些方法已经被淘汰,因为它们是不安全的,不稳定的.如果在不合适的时候挂起线程(比如,锁定共享资源时 ...
- php如何使用rabbitmq实现发布消息和消费消息(一对多)(tp框架)(第二篇)
一个publisher发布消息 多个个customer接受消息 1:准备工作参照: http://www.cnblogs.com/spicy/p/7886820.html 2,:路由: 3: 方法: ...
- CSS3中的pointer-events
今天做项目中偶然误把元素加上了pointer-events属性,结果导致后来在js中给该元素加点击事件不能用,检查了半天才发现是这个属性的问题.之前没有好好研究,于是决定仔细研究一下. 一.定义及语法 ...
- Android Layout 01_activity_Login.xml
activity_login.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android ...