内容概要
reflection是MyBatis关于反射的工具包,是实现其它功能的基石之一。这里我不准备贴上源码然而逐行解释,而是从需求分析的角度来复现。
ObjectFactory
现在有这样的需求:给你一个Class对象,要求你创建它的实例,不单单可以通过无参构造器来构造实例。
我们先实现一下无参构造版本,通过class.newInstance的方法
public class ObjectFactory {public static void main(String[] args) throws InstantiationException, IllegalAccessException {ArrayList s = create(ArrayList.class);System.out.println(s);}public static <T> T create(Class<?> c) throws InstantiationException, IllegalAccessException {return (T) c.newInstance();}
}
还可以通过获取无参构造器来生成对象
public static <T> T create(Class<?> c) throws Exception {Constructor<?> constructor = c.getDeclaredConstructor();T o = (T)constructor.newInstance();return o;}
上面的方法很局限,如果我想要调用有参构造器怎么办?我想给构造器传参数怎么办?
看下面的版本
c表示你要实例化的对象的Class
constructorArgsType包含了构造器中所有需要参数的Class对象,拿到这个就能指定你需要什么构造器。
constructorArgs是在实例化时给构造器传的实参
public class ObjectFactory {public static <T> T create(Class<?> c, List<Class<?>> constructorArgsType, List<Object> constructorArgs)throws Exception {// 如果没传这两个参数就调用无参构造实例化if(constructorArgsType == null || constructorArgs == null){Constructor<?> constructor = c.getDeclaredConstructor();return (T) constructor.newInstance();}// List的toArray方法可以将一个List转化成Array,往里传一个指定的Array类型,它可以做类型推断//这里就是告诉toArray方法,帮我转换成一个Class类型的ArrayConstructor<?> constructor = c.getConstructor(constructorArgsType.toArray(new Class[0]));//传入参数,同样需要toArrayreturn (T) constructor.newInstance(constructorArgs.toArray(new Object[0]));}
}
通过上面的方法你给我任意一个Class类型还有相关参数我都可以构造出一个实例来。以下面String类型为例
public static void main(String[] args) throws Exception{ArrayList<Class<?>> constructorArsgsType = new ArrayList<>();ArrayList<Object> constructorArsgs = new ArrayList<>();constructorArsgsType.add(String.class);constructorArsgs.add("666");String s = (String)create(String.class, constructorArsgsType, constructorArsgs);System.out.println(s);}
有了上面的基础我们看Mybatis的ObjectFactory源码就一目了然了
public class DefaultObjectFactory implements ObjectFactory, Serializable {private static final long serialVersionUID = -8855120656740914948L;@Overridepublic <T> T create(Class<T> type) {return create(type, null, null);}@SuppressWarnings("unchecked")@Overridepublic <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {// 它这里先做了一个类型转换,防止你传入的是抽象类,比如传了一个List进来肯定无法实例化//所以就先做一层转换,比如将List,Collection这种统一用ArrayList,Map都用HashMap//不得不说写框架就要考虑的很周全Class<?> classToCreate = resolveInterface(type);// we know types are assignablereturn (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);}private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {try {Constructor<T> constructor;if (constructorArgTypes == null || constructorArgs == null) {constructor = type.getDeclaredConstructor();try {return constructor.newInstance();} catch (IllegalAccessException e) {if (Reflector.canControlMemberAccessible()) {constructor.setAccessible(true);return constructor.newInstance();} else {throw e;}}}constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[0]));try {return constructor.newInstance(constructorArgs.toArray(new Object[0]));} catch (IllegalAccessException e) {if (Reflector.canControlMemberAccessible()) {constructor.setAccessible(true);return constructor.newInstance(constructorArgs.toArray(new Object[0]));} else {throw e;}}} catch (Exception e) {String argTypes = Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList).stream().map(Class::getSimpleName).collect(Collectors.joining(","));String argValues = Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList).stream().map(String::valueOf).collect(Collectors.joining(","));throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);}}protected Class<?> resolveInterface(Class<?> type) {Class<?> classToCreate;if (type == List.class || type == Collection.class || type == Iterable.class) {classToCreate = ArrayList.class;} else if (type == Map.class) {classToCreate = HashMap.class;} else if (type == SortedSet.class) { // issue #510 Collections SupportclassToCreate = TreeSet.class;} else if (type == Set.class) {classToCreate = HashSet.class;} else {classToCreate = type;}return classToCreate;}@Override//判断当前class是不是一个Collection,通过isAssianableFrom实现public <T> boolean isCollection(Class<T> type) {return Collection.class.isAssignableFrom(type);}}
仔细消化一下上面的内容,我们继续。
PropertyTokenizer
我们知道通过Object.attribute的方法可以获得对象的属性,并且如果,attribute如果是个对象,这个语法还可以继续。如果数组的每一项是个对象,我们也可以通过array[index].attribute拿到其对象。
现在,如果只给你字符串形式的数据,你怎么拿到解析对象的名称和属性的名称呢?
给个例子,我想解析下面的字符串怎么办呢?
String s = "a[0].b.c[0]"
先从简单的开始,靠 . 来分割变量名称
class ProperyTokenizer{// 代表对象private String name;// 代表属性private String children;public ProperyTokenizer(String fullname) {// 先确定.所在的下标int delim = fullname.indexOf('.');// 如果查找到了if(delim > -1){// 截取前面的部分name = fullname.substring(0, delim);//剩下的就是它的属性children = fullname.substring(delim + 1);}else{name = fullname;children = null;}}public String getName() {return name;}public String getChildren() {return children;}
}
上面的实现适用的情况非常的局限
String s1 = a.b //适用
String s2 = a.b.c // 不适用
为了实现这种嵌套的结构可以考虑实现Iterator接口,每迭代一次就解析出来一部分。Iterator必须实现next和hasNext方法。
class ProperyTokenizer implements Iterator<ProperyTokenizer> {// 省略重复的代码@Overridepublic boolean hasNext() {return children != null;}@Overridepublic ProperyTokenizer next() {return new ProperyTokenizer(children);}
}
我们测试一下
public static void main(String[] args){String s = "a.b.c";ProperyTokenizer properyTokenizer = new ProperyTokenizer(s);while (properyTokenizer.hasNext()){System.out.println(properyTokenizer.getName());properyTokenizer = properyTokenizer.next();}System.out.println(properyTokenizer.getName());}
对于Object.attribute我们已经能够逐步解析出来了,但是还没有解决数组下标的问题。
我们加入数组下标的逻辑,并用indexedName保存可能带数组下标的名称,index表示数组索引
public ProperyTokenizer(String fullname){int delim = fullname.indexOf('.');if(delim > -1){name = fullname.substring(0, delim);children = fullname.substring(delim + 1);}else{name = fullname;children = null;}// 表示可能带下标的名称indexedName = name;//找是否有数组下标delim = name.indexOf("[");if(delim > -1){//有的话先把索引取出来index = name.substring(delim + 1, name.length() - 1);//然后取数组名name = name.substring(0, delim);}}
我们测试一下
public static void main(String[] args){String s = "a[0].b.c";ProperyTokenizer properyTokenizer = new ProperyTokenizer(s);while (properyTokenizer.hasNext()){System.out.println(properyTokenizer.getName());if(!properyTokenizer.getName().equals(properyTokenizer.getIndexedName())){System.out.print(properyTokenizer.getIndexedName());System.out.print(" index: ");System.out.println(properyTokenizer.getIndex());}properyTokenizer = properyTokenizer.next();}System.out.println(properyTokenizer.getName());}
Invoker
下面先看一个接口
public interface Invoker{Object invoke(Object target, Object [] args);Class<?> getType();
}
这里的Invoker是个调用的管家,它有一个invoke方法,target表示待调用的对象,args是要传入的参数。
下面我们实现一个功能,提供一个对象的方法的名称就能够调用这个方法。实现字符串到具体方法实现的映射。
先给一个测试类吧,一个类中有两个public方法
class Test{void test1(){System.out.println("test1");}String test2(String s){System.out.println(s);return "ok";}
}
现在的一种做法就是,你给我方法的名称,我遍历Test.class的method,找到以后调用它。你有100个方法,我就要遍历100次。这个显然不太好。那么先用一个HashMap将方法保存下来可不可以呢?
public class TestReflect {public static void main(String[] args) throws Exception{HashMap<String, Method> methodHashMap = new HashMap<String, Method>();Method[] methods = Test.class.getDeclaredMethods();for(Method method : methods){methodHashMap.put(method.getName(), method);}Method test1Method = methodHashMap.get("test1");test1Method.invoke(new Test());Method test2Method = methodHashMap.get("test2");Object result = test2Method.invoke(new Test(), "666");}}
这样做确实可以能够解决一些问题,但还有不少缺陷。首先,丢失了方法返回值的类型,只能当作Object类型来处理。当然,你可以增加一个HashMap来保存名称和返回类型的映射。其次,这里的invoke是有问题的,getDecaledMethods获得的是所有方法,对于非public方法,直接调用可能报错。综合以上,我们单独编写一个类来实现相关功能。
class MethodInvoker{// 保存方法的返回值类型private final Class<?> returnType;// 保存方法对象private final Method method;public MethodInvoker( Method method) {this.returnType = method.getReturnType();this.method = method;}// 为method做了一层包装,处理了访问修饰符不是public时的问题public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {try {return method.invoke(target, args);}catch (IllegalAccessException e){method.setAccessible(true);return method.invoke(target, args);}}// 获取返回值类型public Class<?> getType(){return returnType;}
}
基于这个类我们只需建立字符串和MethodInvoker对象的映射,而不是字符串和Method对象的映射。
下面看用法
public class TestReflect {public static void main(String[] args) throws Exception{HashMap<String, MethodInvoker> methodHashMap = new HashMap();Method[] methods = Test.class.getDeclaredMethods();for(Method method : methods){methodHashMap.put(method.getName(), new MethodInvoker(method));}MethodInvoker test1 = methodHashMap.get("test1");Object result1 = test1.invoke(new Test());System.out.println(test1.getType().cast(result1));MethodInvoker test2 = methodHashMap.get("test2");Object result2 = test2.invoke(new Test(), "666");System.out.println(test2.getType().cast(result2));}}
细心的读者可能已经发现了,MethodInvoker不正好实现了Invoker的相关接口吗。通过这样的小例子让大家明白Invoker这个接口的意义。
好了,收拾一下行囊我们前往下一站,继续沿着Invoker的思路继续探究。
现在,我们又来了一个新需求,就是通过字符串来访问一个类的属性。
我们知道,一般的对象都是满足javaBean的要求的,就是属性私有化,然后提供getter和setter方法,但是你不能要求用户在所有细节都满足你的要求,作为框架的设计者,需要去兼容不容类型的用户。
有了MethodHandler的经验,下面的代码应该很好理解
class FieldInvoker implements Invoker{Field field;Class<?> type;public FieldInvoker(Field field) {this.field = field;type = field.getType();}@Overridepublic Object invoke(Object target, Object... args) throws IllegalAccessException {try {return field.get(target);} catch (IllegalAccessException e) {field.setAccessible(true);return field.get(target);}}@Overridepublic Class<?> getType() {return type;}
}
只不过是将Method换成了Field
Rflector
前面关于Invoker的内容铺垫了这么多,终于可以开启Mybatis中关于反射的一个及其重要的类Rflector。
它实现的功能是,可以拿到一个对象的所有字段以及getter和setter方法,并将它们与字符串之间建立起了映射。下面是Reflector类的一个用法
public void testReflector() throws InvocationTargetException, InstantiationException, IllegalAccessException {Reflector reflector = new Reflector(Person.class);// 获取reflector的所有可以获取的属性名称String[] getablePropertyNames = reflector.getGetablePropertyNames();//实例化一个person,它有三个属性Person person = new Person(1, "zs", 12);for (String property: getablePropertyNames){// 根据属性名称逐个获取属性的值System.out.println(reflector.getGetInvoker(property).invoke(person, null));}}```java
package flect;import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;public class Reflector {private Class<?> type;private Set<String> properties = new HashSet<>();private Map<String, Invoker> getMethods = new HashMap<>();private Map<String, Invoker> setMethods = new HashMap<>();public Reflector(Class<?> type) {this.type = type;// 获取所有的方法Method[] methods = type.getDeclaredMethods();// 添加get方法到HashMap中addGetMethod(methods);// 添加set方法到HashMap中addSetMethod(methods);Field[] fields = type.getDeclaredFields();// 添加没有setter,getter方法的字段addFileds(fields);}public String[] getProperyNames(){return properties.toArray(new String[0]);}public Invoker getGetInvoker(String propery){return getMethods.get(propery);}public Invoker getSetInvoker(String propery){return setMethods.get(propery);}private void addFileds(Field[] fields) {for(Field field:fields){String name = field.getName();if(properties.contains(name))continue;properties.add(name);getMethods.put(name, new FieldGetInvoker(field));setMethods.put(name, new FieldSetInvoker(field));}}private void addSetMethod(Method[] methods) {for(Method method:methods){String name = method.getName();if(name.startsWith("get") && name.length() > 3){String property = name.substring(3);String sb = property.substring(0, 1).toLowerCase() +property.substring(1);getMethods.put(sb, new MethodInvoker(method));properties.add(sb);}}}private void addGetMethod(Method[] methods){for(Method method:methods){String name = method.getName();if(name.startsWith("set") && name.length() > 3){String property = name.substring(3);String sb = property.substring(0, 1).toLowerCase() +property.substring(1);setMethods.put(sb, new MethodInvoker(method));}}}
}
上面的并不是Mybatis中Relfector的实现,而是我实现的简化版本,去掉了处理一些特殊情况的代码。但是大体功能依然的能实现。