mybatis 源码分析四、反射
一、mybatis反射基础Reflector类
mybatis中反射的应用非常多,在参数和结果集映射的过程很多地方都用到了反射。为了程序的可读性,简化反射的应用,mybatis针对对jdk自带的反射包进行了封装。接下来我们就看看mybatis是如何对反射进行封装的。
在mybatis 源码中我们反射操作都集中在org.apache.ibatis.reflection 这个包下面。我们看一下大概的包结构。
其中Reflector类是mybatis的基础,里面包含了很多信息,接下来我们看一下Reflector这个类的属性信息
// 对应的Class 类型
private final Class<?> type;
// 可读属性的名称集合 可读属性就是存在 getter方法的属性
private final String[] readablePropertyNames;
// 可写属性的名称集合 可写属性就是存在 setter方法的属性
private final String[] writablePropertyNames;
// 记录了属性相应的setter方法,key是属性名称,value是Invoker方法
// 他是对setter方法对应Method对象的封装
private final Map<String, Invoker> setMethods = new HashMap<>();
// 属性相应的getter方法
private final Map<String, Invoker> getMethods = new HashMap<>();
// 记录了相应setter方法的参数类型,key是属性名称 value是setter方法的参数类型
private final Map<String, Class<?>> setTypes = new HashMap<>();
// 和上面的对应
private final Map<String, Class<?>> getTypes = new HashMap<>();
// 记录了默认的构造方法
private Constructor<?> defaultConstructor;
// 记录了所有属性名称的集合
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
我们初始化这些信息都是在Reflector的构造器中进行的。
public Reflector(Class<?> clazz) {
//初始化type
type = clazz;
//初始化默认的构造器
addDefaultConstructor(clazz);
//初始化属性的get方法
addGetMethods(clazz);
//初始话属性的set方法
addSetMethods(clazz);
//初始话字段集合
addFields(clazz);
//getter方法的属性 可读属性
readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
//setter方法的属性 可写属性
writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
//记录所有属性的集合
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writeablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
mybatis 提供了反射工厂用于加载Reflector,提供了一个ReflectorFactory工厂接口,和两个实现类一个是DefaultReflectorFactory 默认的实现,CustomReflectorFactory不过这个目前还没有什么实现。我们看一下DefaultReflectorFactory的源码。这里其实就是用来获取Reflector反射信息。
public class DefaultReflectorFactory implements ReflectorFactory {
private boolean classCacheEnabled = true;
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<Class<?>, Reflector>();
public DefaultReflectorFactory() {
}
@Override
public boolean isClassCacheEnabled() {
return classCacheEnabled;
}
@Override
public void setClassCacheEnabled(boolean classCacheEnabled) {
this.classCacheEnabled = classCacheEnabled;
}
@Override
public Reflector findForClass(Class<?> type) {
if (classCacheEnabled) {
//用Map进行缓存
Reflector cached = reflectorMap.get(type);
if (cached == null) {
cached = new Reflector(type);
reflectorMap.put(type, cached);
}
return cached;
} else {
//获取反射的信息
return new Reflector(type);
}
}
}
二、addDefaultConstructor如何获取默认构造器
通过源码我们可以看出其是将无参构造器设置为默认的构造器
private void addDefaultConstructor(Class<?> clazz) {
//获取这个类所有的构造器
Constructor<?>[] consts = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : consts) {
//找无参构造
if (constructor.getParameterTypes().length == 0) {
//判断是否有权限
if (canAccessPrivateMethods()) {
try {
//破坏封装private修饰的
constructor.setAccessible(true);
} catch (Exception e) {
// Ignored. This is only a final precaution, nothing we can do.
}
}
//无参构造为默认的构造器
if (constructor.isAccessible()) {
this.defaultConstructor = constructor;
}
}
}
}
三、addGetMethods如何获取的get方法
- 主方法 addGetMethods解析,匹配符合添加的get方法
/**
* 添加get方法
* 安装javaBean规范 get方法可以为get开头boolean类型
* 的方法可以以is开头
* @param cls class
*/
private void addGetMethods(Class<?> cls) {
//保存满足条件的get方法 ,以方法名称为key,
Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
//获取所有的方法
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
//排除有参构造
if (method.getParameterTypes().length > 0) {
continue;
}
//获取方法名称判断是否是以get 或者 is开头
String name = method.getName();
if ((name.startsWith("get") && name.length() > 3)
|| (name.startsWith("is") && name.length() > 2)) {
//解析方法名称,比如 getName 会解析为 Name
name = PropertyNamer.methodToProperty(name);
//将相同key的方法放到value集合
addMethodConflict(conflictingGetters, name, method);
}
}
//解析合法的get方法
resolveGetterConflicts(conflictingGetters);
}
- getClassMethods获取所有的方法,是递归进行获取的
private Method[] getClassMethods(Class<?> cls) {
//保存所有的方法和接口包含父类和接口的
Map<String, Method> uniqueMethods = new HashMap<String, Method>();
Class<?> currentClass = cls;
//终止条件 单前类是object或者为null
while (currentClass != null && currentClass != Object.class) {
addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
// we also need to look for interface methods -
// because the class may be abstract
//获取接口的方法
Class<?>[] interfaces = currentClass.getInterfaces();
//递归获取
for (Class<?> anInterface : interfaces) {
//添加get方法排返回值类型 + 方法名 + 参数相同的方法
addUniqueMethods(uniqueMethods, anInterface.getMethods());
}
//递归获取
currentClass = currentClass.getSuperclass();
}
- resolveGetterConflicts解析最符合的get方法
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
//循环遍历获取最符合的get方法,假设一个key->对应了两个value ,说明只有两种情况是符合的,第一种同一个类中同一个字段同时出现了get前缀和is前缀
//第二种在父类中继承过来的,因为在上一步排除了返回值类型+方法名称+参数相同的方法,所以只有可能是父类的方法返回值>子类的
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
//符号条件的get方法
Method winner = null;
String propName = entry.getKey();
//第一次赋值
for (Method candidate : entry.getValue()) {
if (winner == null) {
winner = candidate;
continue;
}
//获取返回值类型
Class<?> winnerType = winner.getReturnType();
Class<?> candidateType = candidate.getReturnType();
//返回值类型相等的情况,就必须是boolean类型,因为 根据javeBean的定义名称一样的get方法,返回值也一样,那前缀肯定不一样
//那么就只有一种可能 get 和is正对同一个字段同时存在用了is开头就必须是boolean类型否则不符合规范
if (candidateType.equals(winnerType)) {
if (!boolean.class.equals(candidateType)) {
throw new ReflectionException(
"Illegal overloaded getter method with ambiguous type for property "
+ propName + " in class " + winner.getDeclaringClass()
+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
} else if (candidate.getName().startsWith("is")) {
//get和is同时存在 将is方法默认为get方法因为是boolean类型
winner = candidate;
}
//判断candidateType 是否是 winnerType 的父类是的会默认就用winnerType 里面的get方法
} else if (candidateType.isAssignableFrom(winnerType)) {
// OK getter type is descendant
//如果相反则将candidate设为符合的get方法
} else if (winnerType.isAssignableFrom(candidateType)) {
winner = candidate;
} else {
throw new ReflectionException(
"Illegal overloaded getter method with ambiguous type for property "
+ propName + " in class " + winner.getDeclaringClass()
+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
}
}
addGetMethod(propName, winner);
}
}
- 给getMethods赋值最终完成
/**
* 添加method方法
* @param name 名称
* @param method 方法
*/
private void addGetMethod(String name, Method method) {
//判断名称是否合法
if (isValidPropertyName(name)) {
//添加getMethods
getMethods.put(name, new MethodInvoker(method));
//获取返回值类型
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
getTypes.put(name, typeToClass(returnType));
}
}
四、addSetMethods如何添加set方法
- 主方法addSetMethods 获取并解析set方法
/**
* 添加set方法
* @param cls
*/
private void addSetMethods(Class<?> cls) {
Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
//获取所有的方法
Method[] methods = getClassMethods(cls);
//获取set方法并设置conflictingSetters key-》方法名称 value 方法
for (Method method : methods) {
String name = method.getName();
if (name.startsWith("set") && name.length() > 3) {
if (method.getParameterTypes().length == 1) {
//获取方法名称
name = PropertyNamer.methodToProperty(name);
//conflictingSetters添加
addMethodConflict(conflictingSetters, name, method);
}
}
}
//解析
resolveSetterConflicts(conflictingSetters);
}
- resolveSetterConflicts方法解析set方法
private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
for (String propName : conflictingSetters.keySet()) {
//获取这个名称下的set方法
List<Method> setters = conflictingSetters.get(propName);
//获取type的返回值类型,就是set的参数类型
Class<?> getterType = getTypes.get(propName);
Method match = null;
ReflectionException exception = null;
for (Method setter : setters) {
//获取方法的参数类型
Class<?> paramType = setter.getParameterTypes()[0];
//相同
if (paramType.equals(getterType)) {
// should be the best match
match = setter;
break;
}
if (exception == null) {
try {
match = pickBetterSetter(match, setter, propName);
} catch (ReflectionException e) {
// there could still be the 'best match'
match = null;
exception = e;
}
}
}
if (match == null) {
throw exception;
} else {
//添加set方法
addSetMethod(propName, match);
}
}
}
- 添加符合添加的set方法
private void addSetMethod(String name, Method method) {
if (isValidPropertyName(name)) {
setMethods.put(name, new MethodInvoker(method));
Type[] paramTypes = TypeParameterResolver.resolveParamTypes(method, type);
setTypes.put(name, typeToClass(paramTypes[0]));
}
}
五、总结
其实添加get方法和添加set方法都是比较类似的一个操作,到现在mybatis的核心Reflector类就已经讲解完了。这个类的主要作用其实就是解析我们类的信息保存起来以便后续的使用。