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) {
}