Reflector反射器,每个Reflector对象对应一个类,该对象会缓存反射操作所需要的类元信息,便于后续反射操作。
Reflector的类注释如下(难得作者心情好,写了两行注释):
This class represents a cached set of class definition information that allows for easy mapping between property names and getter/setter methods.
此类表示一组缓存的类定义信息,可轻松在属性名称和getter / setter方法之间进行映射
Reflector的构造函数会对所有字段进行初始化
因为mybatis是一个orm框架,故其对反射使用最多的场景就是将封装了请求参数的pojo拼接到对应的sql语句中,或是将sql查询出的结果集映射到对应的pojo对象中,所以调用pojo的getter和setter方法是最频繁的。观察Reflector类中定义的字段可以发现,Reflector类就是为了更好的服务于ORM映射的使用场景而设计。
private final Class<?> type;//Reflector对应的类
private final String[] readablePropertyNames;//可读属性的名称集合
private final String[] writablePropertyNames;//可写属性的名称集合
private final Map<String, Invoker> setMethods = new HashMap<>();//所有属性的set方法
private final Map<String, Invoker> getMethods = new HashMap<>();//所有属性的get方法
private final Map<String, Class<?>> setTypes = new HashMap<>();//所有set方法的类型
private final Map<String, Class<?>> getTypes = new HashMap<>();//所有get方法的类型
private Constructor<?> defaultConstructor;//默认的构造函数(空参构造)
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();//不区分大小的属性集合
构造函数为Reflector类的核心方法,Reflector的构造函数会对上述所有字段进行初始化,便于后续调用,所以本文重点分析构造函数中的逻辑。
构造函数的源码如下:
public Reflector(Class<?> clazz) {
type = clazz;
addDefaultConstructor(clazz);
addGetMethods(clazz);
addSetMethods(clazz);
addFields(clazz);
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
因为构造函数中的逻辑较多,所以简单梳理了如下思维导图,可以对代码的逻辑有一个大致的印象,便于后续阅读。
接下来开始详细分析Reflector类的构造函数源码
在Reflector简介的章节中说过,Reflector对象的作用是缓存反射操作所需要的类元信息,便于后续反射操作调用。所以类和Reflector对象是一一对应的关系,故构造函数的第一行代码就是初始化type字段。
addDefaultConstructor(clazz)方法的作用是找到该类的默认空参构造器。
源码如下:
private void addDefaultConstructor(Class<?> clazz) {
//获取该类声明的所有构造函数
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
//找到空参的构造器,并将其赋给defaultConstructor字段
Arrays.stream(constructors)
.filter(constructor -> constructor.getParameterTypes().length == 0)
.findAny()
.ifPresent(constructor -> this.defaultConstructor = constructor);
}
对应非lamda代码为(未测试,仅供参考):
private void addDefaultConstructor(Class<?> clazz) {
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors)
if(constructor.getParameterTypes().length == 0){
this.defaultConstructor = constructor;
}
}
代码逻辑简单,不多BB直接过。
addGetMethods(clazz)方法的作用找到clazz对应的类中的所有
private void addGetMethods(Class<?> clazz) {
//因为子类可能会重写父类相同的方法,所以数据结构设计为name->List
Map<String, List<Method>> conflictingGetters = new HashMap<>();
Method[] methods = getClassMethods(clazz);
//这段lamda的意思是从数组中找到所有参数长度为0,且以get开头的方法,并将其添加到addMethodConflict中
Arrays
.stream(methods)
.filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
.forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
//解析冲突的get方法
resolveGetterConflicts(conflictingetters);
}
该方法的作用是从当前类开始,递归向上查找所有父类和接口中定义的方法
private Method[] getClassMethods(Class<?> clazz) {
Map<String, Method> uniqueMethods = new HashMap<>();
Class<?> currentClass = clazz;
//递归退出条件
while (currentClass != null && currentClass != Object.class) {
//将当前类中声明的方法添加到Map集合uniqueMethods中
addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
//获取当前类的实现的所有接口
Class<?>[] interfaces = currentClass.getInterfaces();
//遍历将当前类所实现的接口中声明的方法添加到Map集合uniqueMethods中
for (Class<?> anInterface : interfaces) {
addUniqueMethods(uniqueMethods, anInterface.getMethods());
}
//获取当前类的父类,继续向上递归
currentClass = currentClass.getSuperclass();
}
Collection<Method> methods = uniqueMethods.values();
return methods.toArray(new Method[0]);
}
添加methods数组中的所有方法到map中,其中key是当前方法的签名,mybatis自定义了方法签名的格式,用于区分重写方法和重载方法,自定义方法签名的格式为:
signature = 返回值类型#方法名:参数类型1,参数类型2,参数类型3…
addUniqueMethods方法的逻辑较为简单,不做分析。不过需要注意的是方法并没有将桥接方法添加到map中,关于什么是桥接方法,可以参考文章
[]: https://www.zhihu.com/question/54895701/answer/141623158 “Java反射中method.isBridge()由来,含义和使用场景?”
private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
for (Method currentMethod : methods) {
if (!currentMethod.isBridge()) {
String signature = getSignature(currentMethod);
if (!uniqueMethods.containsKey(signature)) {
uniqueMethods.put(signature, currentMethod);
}
}
}
}
1、调用isValidPropertyName方法判断当前属性名称是否是合法的属性名称,比如不能是以$开头,属性名不等于serialVersionUID等等
2、将当前方法添加到conflictingMethods中
private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
if (isValidPropertyName(name)) {
List<Method> list = conflictingMethods.computeIfAbsent(name, k -> new ArrayList<>());
list.add(method);
}
}
遍历每个属性,查找其最匹配的方法。因为子类可以覆写父类的方法,所以一个属性,可能对应多个 getting 方法。
winner表示胜者,candidate表示候选人,代码逻辑不复杂,直接看注释即可。
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
Method winner = null;
String propName = entry.getKey();
boolean isAmbiguous = false;//是否有二义性
for (Method candidate : entry.getValue()) {
//初始情况,直接赋值
if (winner == null) {
winner = candidate;
continue;
}
Class<?> winnerType = winner.getReturnType();
Class<?> candidateType = candidate.getReturnType();
if (candidateType.equals(winnerType)) {
//如果候选者的返回值类型和胜者的返回值类型相同,且不是布尔类型,则表示出现了二义性。
//也就是说在前面的addUniqueMethods()方法中并没有对这两个方法进行合并,这种情况是不允许的。
//所以将isAmbiguous标志位置为True,在稍后的代码逻辑中会抛出对应的异常(ReflectionException) //终止程序运行
if (!boolean.class.equals(candidateType)) {
isAmbiguous = true;
break;
} else if (candidate.getName().startsWith("is")) {
//如果是boolean类型的话,属性名为is开头的优先级更高
winner = candidate;
}
//下面这段不理解的话可以看下面的例子
} else if (candidateType.isAssignableFrom(winnerType)) {
//如果此时candidateType是winnerType的父类,则此时不作操作
} else if (winnerType.isAssignableFrom(candidateType)) {
//如果此时winnerType是candidateType的父类,则candidate更适合
winner = candidate;
} else {
isAmbiguous = true;
break;
}
}
addGetMethod(propName, winner, isAmbiguous);
}
}
举例
public class Father<T> {
private T nickname;
public T getNickname() {
return nickname;
}
public void setNickname(T nickname) {
this.nickname = nickname;
}
}
public class Son extends Father<String>{
private String nickname;
@Override
public String getNickname() {
return nickname;
}
@Override
public void setNickname(String nickname) {
this.nickname = nickname;
}
}
以上述两个类为例,Father类的nickname字段的类型为Object(泛型擦除后),而Son类的nickname字段的类型为String。在出现这个情况时,上述代码会选择子类的该方法。
读者可debug下面的测试类进行调试
@Test
void testCreateReflactor(){
ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
Reflector reflector = reflectorFactory.findForClass(Son.class);
Assertions.assertEquals(String.class, reflector.getGetterType("nickname"));
}
最后会调用addGetMethod(String name, Method method, boolean isAmbiguous)方法将选择出的get方法添加到当前Reflector对象的getMethods和getTypes属性中。
代码中的MethodInvoker和TypeParameterResolver.resolveReturnType(method, type)方法会在后续文章中进行详解
private void addGetMethod(String name, Method method, boolean isAmbiguous) {
//当isAmbiguous为true,即出现二义性时,后续代码会抛出ReflectionException异常,终止程序运行
MethodInvoker invoker = isAmbiguous
? new AmbiguousMethodInvoker(method, MessageFormat.format(
"Illegal overloaded getter method with ambiguous type for property ''{0}'' in class ''{1}''. This breaks the JavaBeans specification and can cause unpredictable results.",
name, method.getDeclaringClass().getName()))
: new MethodInvoker(method);
//添加到getMethods属性中
getMethods.put(name, invoker);
//解析get方法的返回值类型
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
//添加到getTypes属性中
getTypes.put(name, typeToClass(returnType));
}
addSetMethods方法的逻辑与addGetMethods方法的逻辑相似,差异点在于resolveSetterConflicts方法,即如何选择出合适的set方法,所以本节直接分析resolveSetterConflicts方法即可,其余部分略略略。。。
private void addSetMethods(Class<?> clazz) {
Map<String, List<Method>> conflictingSetters = new HashMap<>();
Method[] methods = getClassMethods(clazz);
Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 1 && PropertyNamer.isSetter(m.getName()))
.forEach(m -> addMethodConflict(conflictingSetters, PropertyNamer.methodToProperty(m.getName()), m));
resolveSetterConflicts(conflictingSetters);
}
该方法借助了getTypes和getMethods进行判断,逻辑简单,直接看注释就好
private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
for (Entry<String, List<Method>> entry : conflictingSetters.entrySet()) {
String propName = entry.getKey();
List<Method> setters = entry.getValue();
Class<?> getterType = getTypes.get(propName);
boolean isGetterAmbiguous = getMethods.get(propName) instanceof AmbiguousMethodInvoker;
boolean isSetterAmbiguous = false;
Method match = null;
for (Method setter : setters) {
//getter方法返回的类型和当前setter方法的入参类型相同,即找到最合适的set方法
if (!isGetterAmbiguous && setter.getParameterTypes()[0].equals(getterType)) {
match = setter;
break;
}
//没有找到对应的get方法,且set方法不存在二义性,处理只有set方法没有get方法的情况
if (!isSetterAmbiguous) {
match = pickBetterSetter(match, setter, propName);
isSetterAmbiguous = match == null;
}
}
//如果找到合适的set方法,则将其添加到setMethods和setTypes属性中
if (match != null) {
addSetMethod(propName, match);
}
}
}
与resolveGetterConflicts方法相关的逻辑大同小异
private Method pickBetterSetter(Method setter1, Method setter2, String property) {
if (setter1 == null) {
return setter2;
}
Class<?> paramType1 = setter1.getParameterTypes()[0];
Class<?> paramType2 = setter2.getParameterTypes()[0];
//两个类型进行比较取子类
if (paramType1.isAssignableFrom(paramType2)) {
return setter2;
} else if (paramType2.isAssignableFrom(paramType1)) {
return setter1;
}
MethodInvoker invoker = new AmbiguousMethodInvoker(setter1,
MessageFormat.format(
"Ambiguous setters defined for property ''{0}'' in class ''{1}'' with types ''{2}'' and ''{3}''.",
property, setter2.getDeclaringClass().getName(), paramType1.getName(), paramType2.getName()));
setMethods.put(property, invoker);
Type[] paramTypes = TypeParameterResolver.resolveParamTypes(setter1, type);
setTypes.put(property, typeToClass(paramTypes[0]));
return null;
}
递归添加类及其父类中定义的字段,关于字段和属性的区别可以参考这篇文章
private void addFields(Class<?> clazz) {
//获取clazz声明的所有字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (!setMethods.containsKey(field.getName())) {
int modifiers = field.getModifiers();//获取字段的修饰符
//不是final,也不是static的将会被添加到setMethods和setTypes字段中
if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
addSetField(field);
}
}
//添加到getMethods和getTypes字段中
if (!getMethods.containsKey(field.getName())) {
addGetField(field);
}
}
if (clazz.getSuperclass() != null) {
addFields(clazz.getSuperclass());
}
}
readablePropertyNames数组中保存着所有可读属性和字段的名称,即定义了get方法的字段名
writablePropertyNames数组中保存着所有可写属性和字段的名称,即定义了set方法的字段名
通过上面的介绍可以知道,在mybatis中,一个类就会对应一个Reflector对象。假设有100个类,就需要有100个对应的Reflector对象,如何管理这些Reflector对象是需要考虑的问题,于是mybatis的作者设计了ReflectorFactory接口并对其进行了默认实现,便于上层代码使用。
ReflectorFactory为Reflector的工厂接口,用于创建和缓存 Reflector 对象,其接口定义如下:
public interface ReflectorFactory {
//判断是否允许对reflector进行缓存
boolean isClassCacheEnabled();
//设置是否允许对reflector进行缓存
void setClassCacheEnabled(boolean classCacheEnabled);
//获取type对应的Reflector对象
Reflector findForClass(Class<?> type);
}
ReflectorFactory接口只有一个实现类,即DefaultReflectorFactory类
public class DefaultReflectorFactory implements ReflectorFactory {
//默认开启缓存
private boolean classCacheEnabled = true;
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
public DefaultReflectorFactory() {
}
@Override
public boolean isClassCacheEnabled() {
return classCacheEnabled;
}
@Override
public void setClassCacheEnabled(boolean classCacheEnabled) {
this.classCacheEnabled = classCacheEnabled;
}
//如果允许缓存则直接在类中定义的ConcurrentHashMap中获取
//如果不允许缓存则每次都会创建新的Reflector对象
@Override
public Reflector findForClass(Class<?> type) {
if (classCacheEnabled) {
//如果reflectorMap中没有对应的Reflector对象,则会创建一个对象并将其放在reflectorMap中
return reflectorMap.computeIfAbsent(type, Reflector::new);
} else {
return new Reflector(type);
}
}
}