文章目录
本篇文章将继续分析 reflection 包,本篇文章的重点就是 reflection 包中 BeanWrapper 操作属性的底层原理。
通过查看 BeanWrapper 的源码,可以发现 BeanWrapper 利用了委派者模式,即自身没有实现 ObjectWrapper 的接口,而是交由其他对象来实现。
ObjectWrapper 使用了委派者模式,将具体的实现交给了 MetaClass 和 MetaObject,MetaObject 将具体实现交给了 ObjectWrapper,MetaClass 将具体实现交给了 Reflector 和 ReflectorFactory。所以最终我们可以推断出,在这之中最重要的两个类就是 Reflector 和 ReflectorFactory。
可能描述的不太清晰,所以可以辅助 UML 图,帮助理解,如下图:
Reflector
Reflector 是 reflection 包中实现对象属性操作的底层实现类。
Reflector 属性
我们首先查看其中的属性。从类中的属性,我们就可以大概的推断出代码是如何写的,就是通过反射和上篇文章提到的一些工具类来实现的。
/**
* 当前 Reflector 操作的 Class 类型
*/
private final Class<?> type;
/**
* 类所有可读属性的名称
*/
private final String[] readablePropertyNames;
/**
* 类所有可写属性的名称
*/
private final String[] writablePropertyNames;
/**
* 类中的 set 方法映射。key -> 属性名,value -> 方法的 Invoker
*/
private final Map<String, Invoker> setMethods = new HashMap<>();
/**
* 类中的 get 方法映射。key -> 属性名,value -> 方法的 Invoker
*/
private final Map<String, Invoker> getMethods = new HashMap<>();
/**
* 类中的 set 方法的请求参数类型
*/
private final Map<String, Class<?>> setTypes = new HashMap<>();
/**
* 类中 get 方法的返回类型
*/
private final Map<String, Class<?>> getTypes = new HashMap<>();
/**
* 类的默认的构造方法
*/
private Constructor<?> defaultConstructor;
/**
* 全大写的属性名称 -> 标准属性名称。防止属性名称不规范例如 username 和 userName 这种情况。
*/
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
Reflector 构造方法
查看 Reflector 的构造方法,其实和其属性基本吻合,写者并不会将每个方法都去进行解析,只会解析其中一些在我学习过程中认为比较难或者比较绕的部分。如果没有你不太理解的地方可以在评论区单独交流,我就不在文章中提出了,因为文章代码太多,篇幅过长不利于大家的学习。
public Reflector(Class<?> clazz) {
// 设置 Reflector 的类型
type = clazz;
// 设置默认的构造器
addDefaultConstructor(clazz);
// 设置 get 方法和它的返回值类型
addGetMethods(clazz);
// 设置 set 方法和它的请求参数类型
addSetMethods(clazz);
// 对没有 set 或 get 方法的属性做补充
// 即如果有 set 或 get 方法则调用对应的方法,如果没有的话则通过反射直接对 field 进行赋值
addFields(clazz);
// getMethods 中的所有 key 即属性都是可读的
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
// setMethods 中所有的 key 即属性名称都是可写的
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);
}
}
重点分析 addGetMethods,调用栈如下。
/**
* 解析类中属性的 get 方法及其返回值类型
*
* @param clazz 要解析的类
*/
private void addGetMethods(Class<?> clazz) {
// 可能存在冲突的 get 方法。key -> 属性名称,value -> 对应的get方法列表
Map<String, List<Method>> conflictingGetters = new HashMap<>();
// 获取一个类的所有方法
Method[] methods = getClassMethods(clazz);
Arrays.stream(methods)
// 只留下符合要求的 get 方法
.filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
// 将方法添加到 conflictingGetters 中
.forEach(
m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
// 解决 conflictingGetters 中的冲突
resolveGetterConflicts(conflictingGetters);
}
/**
* 解析 conflictingGetters 中有冲突的列表,即一个属性名称可能对应了多个 getXXX 方法,要从中选择最合适的 get 方法
*
* @param conflictingGetters 可能有冲突的 getXXX 方法映射
*/
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
// 当前有冲突列表中最合适的 get 方法
Method winner = null;
String propName = entry.getKey();
// 最后的结果是否是 '唯一解' 的,即可能有多个答案都一样合适
boolean isAmbiguous = false;
for (Method candidate : entry.getValue()) {
// 经过层层筛选找到符合要求的方法,如果有多个方法符合要求,说明要调用的方法是含糊不清的
// 就需要设置 isAmbiguous = true
}
// 将 winner 放入到 getMethods 属性中
addGetMethod(propName, winner, isAmbiguous);
}
}
/**
* 添加 get 方法到 getMethods 和 getTypes 中
*
* @param name getXXX 方法名称解析后得到的名称为 XXX
* @param method getXXX 方法
* @param isAmbiguous 是否是 '唯一解'
*/
private void addGetMethod(String name, Method method, boolean isAmbiguous) {
// 如果是 '唯一解' 那么就直接生成 MethodInvoker,如果有 '多个解',则封装为 AmbiguousMethodInvoker,在执行时会报错
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 中。key -> '属性'名称,value -> '属性'的get方法
getMethods.put(name, invoker);
// 获取到方法真实的返回类型,并添加到 getTypes 中
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
getTypes.put(name, typeToClass(returnType));
}
reflection 包中的工具类
在 reflection 包中除了前面提到的反射相关的类,还有一些工具类。
ArrayUtil
提供数组对象的相关工具,有 hashCode、equals、toString
ExceptionUtil
用于拆开异常包装类 InvocationTargetException 和 UndeclaredThrowableException。当使用 Java 反射调用方法的时候,由于每个方法抛出的异常是未知的,所以就会用 InvocationTargetException 将原异常包裹,而不是直接抛出 Throwable。当使用 Java 对一个接口进行动态代理的时候,如果接口上没有声明某受检异常的时候,但是代理方法却会抛出该受检异常的时候,就会使用 UndeclaredThrowableException 将这个受检异常包装为一个非受检异常。
ParamNameResolver
用于解析映射接口文件中方法的请求参数的名称并且根据一定规则添加别名。就可以在 XML 映射文件中写 SQL 时使用这些参数名称。我们只分析该类中的两个关键方法,其他的方法如果感兴趣的读者可以自己去看源码。
构造方法
public ParamNameResolver(Configuration config, Method method) {
// 是否使用真实的参数名称(在没有声明 @Param 注解时)
this.useActualParamName = config.isUseActualParamName();
// 获取参数的类型
final Class<?>[] paramTypes = method.getParameterTypes();
// 获取方法请求参数上的所有注解
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
// key -> 请求参数的下标,从0开始,value -> 参数名称
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// 跳过特殊的参数类型
continue;
}
String name = null;
// 遍历当前请求参数所有声明的注解,找到 @Param 注解中的 value
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// 如果当前请求参数上没有声明 @Param 注解
if (useActualParamName) {
// 如果允许使用真实的参数名称的话,则取源码中声明的参数名称
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// 如果不允许使用真实的参数名称,则使用下标作为名称 ("0", "1", ...)
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
// 转换为不可变的 Map
names = Collections.unmodifiableSortedMap(map);
}
getNamedParams
/**
* 根据参数命名规则给参数命名
*
* @param args 参数列表
* @return 命名后的请求参数
*/
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
// 如果没有请求参数
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
// 如果没有声明 @Param 注解,并且只有一个请求参数,所以可以添加一些易理解的别名,在写 sql 时更加灵活
// 为 Collection 的子类,添加 collection 别名
// 为 List 的子类,添加 list 别名
// 为 Array 的子类,添加 array 别名
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
// key -> @Param的value、下标、源码中声明的请求参数名称,value -> 请求参数
param.put(entry.getValue(), args[entry.getKey()]);
// 通用名称前缀 + 下标(从1开始)。例如:(param1,param2,...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// 由于是自动生成的别名,所以在添加到 param 前要防止覆盖
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
ParamNameUtil
用来获取方法真实的请求参数名称,即在源码中声明的请求参数名称。
TypeParameterResolver
用于获取真实类型的解析器。由于该工具类对于涉及了比较深入的反射知识,如果在这里书写的话,就会让篇幅过长,我在网上找了一篇写的不错的文章,大家如果有兴趣的话就跳转过去吧。
TypeParameterResolver详解
如果大家对这个工具类很感兴趣的话,可以在评论区提出,我可以单独写一篇文章来解析它。
参考文献
- 《Mybatis技术内幕》