MyBatis源码分析-基础支持层反射模块Reflector/ReflectorFactory

本文主要介绍MyBatis的反射模块是如何实现的。

MyBatis 反射的核心类Reflector,下面我先说明它的构造函数和成员变量。具体方法下面详解。

org.apache.ibatis.reflection.Reflector
public class Reflector {
 private final Class<?> type; //对应的Class 类型
 //可读属性的名称集合,可读属性就是存在相应getter 方法的属性,初始值为空数纽
 private final String[] readablePropertyNames;
 //可写属性的名称集合,可写属性就是存在相应setter 方法的属性,初始值为空数纽
 private final String[] writeablePropertyNames;
 //记录了属性相应的setter 方法, key 是属性名称, value 是Invoker 对象,它是对setter 方法对应
 private final Map<String, Invoker> setMethods = new HashMap<>();
 //记录了属性相应的getter 方法, key 是属性名称, value 是Invoker 对象,它是对setter 方法对应
 private final Map<String, Invoker> getMethods = new HashMap<>();
 //记录了属性相应的setter 方法的参数值类型, ke y 是属性名称, value 是setter 方法的参数类型
 private final Map<String, Class<?>> setTypes = new HashMap<>();
 //记录了属性相应的getter 方法的返回位类型, key 是属性名称, value 是getter 方法的返回位类型
 private final Map<String, Class<?>> getTypes = new HashMap<>();
 //记录了默认构造方法
 private Constructor<?> defaultConstructor;
 //记录了所有属性名称的集合
 private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
 public Reflector(Class<?> clazz) {
 type = clazz;
 //查找clazz的无参构造方法,通过反射遍历所有构造方法,找到构造参数集合长度为0的。
 addDefaultConstructor(clazz);
 //处理clazz 中的getter 方法,填充getMethods 集合和getTypes 集合
 addGetMethods(clazz);
 //处理clazz 中的set ter 方法,填充setMethods 集合和set Types 集合
 addSetMethods(clazz);
 //处理没有get/set的方法字段
 addFields(clazz);
 //初始化可读写的名称集合
 readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
 writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
 //初始化caseInsensitivePropertyMap ,记录了所有大写格式的属性名称
 for (String propName : readablePropertyNames) {
 caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
 }
 for (String propName : writeablePropertyNames) {
 caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
 }
 }
 。。。。。。。。。。。。具体代码先忽略,通过构造函数的调用慢慢渗透。
}

1:addDefaultConstructor() // 查找clazz的无参构造方法,通过反射遍历所有构造方法,找到构造参数集合长度为0的

主要实现的思想是,通过clazz.getDeclaredConstructors();获取所有构造方法集合,然后循环遍历 判断参数长度为0的,并且构造函数权限可控制的设为默认构造方法。

private void addDefaultConstructor(Class<?> clazz) {
 Constructor<?>[] consts = clazz.getDeclaredConstructors();
 for (Constructor<?> constructor : consts) {
 if (constructor.getParameterTypes().length == 0) {
 //判断反射对象的控制权限 为true是可控制
 if (canControlMemberAccessible()) {
 try {
 //设置Accessible为true后,反射可以访问私有变量。
 constructor.setAccessible(true);
 } catch (Exception e) {
 // Ignored. This is only a final precaution, nothing we can do.
 }
 }
 if (constructor.isAccessible()) {
 this.defaultConstructor = constructor;
 }
 }
 }
}

2:addGetMethods(clazz)// 处理clazz 中的getter 方法,填充getMethods 集合和getTypes 集合

private void addGetMethods(Class<?> cls) {
 Map<String, List<Method>> conflictingGetters = new HashMap<>();
 //获取当前类以及父类中定义的所有方法的唯一签名以及相应的Method对象。
 Method[] methods = getClassMethods(cls);
 for (Method method : methods) {
 if (method.getParameterTypes().length > 0) {
 continue;
 }
 String name = method.getName();
 //判断如果方法明是以get开头并且方法名长度大于3 或者 方法名是以is开头并且长度大于2
 if ((name.startsWith("get") && name.length() > 3)
 || (name.startsWith("is") && name.length() > 2)) {
 //将方法名截取,如果是is从第二位截取,如果是get或者set从第三位开始截取
 name = PropertyNamer.methodToProperty(name);

 //addMethodConflict 方法内部很简单只有两行代码:
 //1:List<Method> list=conflictingGetters.computeIfAbsent(name,K->new ArrayList<>()); 这句话的意思是,在conflictingGetters 的Map中 如果key中存在name,name什么都不做,将value返回,如果name不存在,则返回一个新的ArrayList.
 //2:list.add(method); 将方法对象添加到list对象中。
 addMethodConflict(conflictingGetters, name, method);
 }
 }
 resolveGetterConflicts(conflictingGetters);
}

2-1:getClassMethods(cls);//获取当前类以及父类中定义的所有方法的唯一签名以及相应的Method对象。

private Method[] getClassMethods(Class<?> cls) {
 Map<String, Method> uniqueMethods = new HashMap<>();
 Class<?> currentClass = cls;
 while (currentClass != null && currentClass != Object.class) {
 //currentClass.getDeclaredMethods(),获取当前类的所有方法
 //addUniqueMethods 为每个方法生成唯一签名,并记录到uniqueMethods集合中
 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) {
 addUniqueMethods(uniqueMethods, anInterface.getMethods());
 }
 currentClass = currentClass.getSuperclass();
 }
 Collection<Method> methods = uniqueMethods.values();
 return methods.toArray(new Method[methods.size()]);
}

2-1-1: addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods()); 为每个方法生成唯一签名,并记录到uniqueMethods集合中

private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
 for (Method currentMethod : methods) {
 //判断是不是桥接方法, 桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法
 if (!currentMethod.isBridge()) {
 //获取签名
 // 签名格式为:方法返回参数#方法名:参数名 ps:多个参数用,分割 签名样例:String#getName:User
 String signature = getSignature(currentMethod);
 // check to see if the method is already known
 // if it is known, then an extended class must have
 // overridden a method
 //如果签名存在,则不做处理,表示子类已经覆盖了该方法。
 //如果签名不存在,则将签名作为Key,Method作为value 添加到uniqueMethods中
 if (!uniqueMethods.containsKey(signature)) {
 if (canControlMemberAccessible()) {
 try {
 currentMethod.setAccessible(true);
 } catch (Exception e) {
 // Ignored. This is only a final precaution, nothing we can do.
 }
 }
 uniqueMethods.put(signature, currentMethod);
 }
 }
 }
}

2-2: resolveGetterConflicts(conflictingGetters);;//在2-1中返回的方法可能存在,两个相同的方法名称,因为当子类实现父类方法时且参数不同,此时2-1生成的签名是不同的生成签名的规则是 方法返回值#方法名#参数名,那么就会返回两个相同的方法名。 resolveGetterConflicts方法会对这种覆写的情况进行处理,同时将处理后的getter方法记录到getMethods集合中,将其返回值类型填充到getTypes集合中。 内部实现主要是两个for循环,循环比较方法名称相同的情况下,返回值不同的情况下,拿第二个当最终想要的Method。

private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
 for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
 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();
 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")) {
 winner = candidate;
 }
 } else if (candidateType.isAssignableFrom(winnerType)) {
 // OK getter type is descendant
 } 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);
 }
}

总结一下addGetMethods(clazz)方法和addSetMethods(clazz)大致相同:

首先创建:

Map<String, List<Method>> conflictingGetters = new HashMap<>();

1:获取子类和父类的所有方法。 获取方法是:先生成唯一签名,唯一签名规则是方法返回值#方法名:方法参数1,方法参数2 。 根据签名作为key,method对象作为value生成Map,通过签名进行过滤,将此Map转换为List返回。

2:循环遍历Map,找到符合条件的方法名,is开头或者get开头的,将方法名截取,截取后的方法名作为key,List<Method>作为value,放入到conflictingGetters中。

3:由于子类存在实现父类方法,且返回值不同的情况,导致用一方法名可能有不同的Method ,第三步 resolveGetterConflicts方法会对这种覆写的情况进行处理,同时将处理后的getter方法记录到getMethods集合中,将其返回值类型填充到getTypes集合中。

Reflector Factory 接口主要实现了对Reflector对象的创建和缓存,有三个方法:该接口定义如下:

public interface ReflectorFactory {
 boolean isClassCacheEnabled(); //检测该ReflectorFactory对象是否会缓存Reflector对象
 void setClassCacheEnabled(boolean classCacheEnabled);//设置是否缓存Reflector对象
 Reflector findForClass(Class<?> type); //创建指定class对应的Reflector对象
}

Reflector Factory的实现是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;
 }
 @Override
 public Reflector findForClass(Class<?> type) {
 //如果开启缓存,Reflector对象从ConcurrentMap<Class<?>, Reflector> 取出。
 if (classCacheEnabled) {
 // synchronized (type) removed see issue #461
 return reflectorMap.computeIfAbsent(type, Reflector::new);
 } else {//没开启缓存,重新创建。
 return new Reflector(type);
 }
 }
}

DefaultReflectorFactory 的缓存是通过ConcurrentMap来实现的,如果开启了缓存,那么就从ConcurrentMap取Reflector,如果没有开启,就新建Reflector.

除了使用MyBatis提供的DefaultReflectorFactory实现,我们还可以在mybatis-config.xml中配置自定义的ReflectorFactory 实现类,从而实现功能上的扩展。

本文转自:https://www.toutiao.com/a6613870863701770759/?tt_from=mobile_qq&utm_campaign=client_share&timestamp=1539993909&app=news_article&utm_source=mobile_qq&iid=46395736178&utm_medium=toutiao_android&group_id=6613870863701770759

原文地址:https://www.cnblogs.com/nizuimeiabc1/p/9844247.html

时间: 2024-07-30 15:03:12

MyBatis源码分析-基础支持层反射模块Reflector/ReflectorFactory的相关文章

MyBatis源码分析-SQL语句执行的完整流程

MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录.如何新建MyBatis源码工程请点击MyBatis源码分析-IDEA新建MyBatis源码工程. MyBatis框架主要完成的是以下2件事情: 根据JD

【MyBatis源码分析】环境准备

前言 之前一段时间写了[Spring源码分析]系列的文章,感觉对Spring的原理及使用各方面都掌握了不少,趁热打铁,开始下一个系列的文章[MyBatis源码分析],在[MyBatis源码分析]文章的基础之上,可以继续分析数据库连接池.Spring整合MyBatis源码.Spring事物管理tx等等. [MyBatis源码分析]整个文章结构相较[Spring源码分析]稍微改一改,后者会在每一部分源码分析的开头列出要分析的源码的实例,比如: 分析Bean流程加载,就会先写Bean的代码示例及xml

MyBatis 源码分析 - 配置文件解析过程

* 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAliases和typeHandlers等,本文的篇幅也主要在对这三个配置解析过程的分析上.下面,我们来一起看一下本篇文章的目录结构. 从目录上可以看出,2.3节.2.5节和2.8节的内容比较多.其中2.3节是关于settings配置解析过程的分析,除了对常规的 XML 解析过程分析,本节额外的分析了元

Mybatis源码分析

MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录.如何新建MyBatis源码工程请点击MyBatis源码分析-IDEA新建MyBatis源码工程. MyBatis框架主要完成的是以下2件事情: 根据JD

MyBatis源码分析-MyBatis初始化流程

MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录.如何新建MyBatis源码工程请点击MyBatis源码分析-IDEA新建MyBatis源码工程. MyBatis初始化的过程也就是创建Configura

【MyBatis源码分析】select源码分析及小结

示例代码 之前的文章说过,对于MyBatis来说insert.update.delete是一组的,因为对于MyBatis来说它们都是update:select是一组的,因为对于MyBatis来说它就是select. 本文研究一下select的实现流程,示例代码为: 1 public void testSelectOne() { 2 System.out.println(mailDao.selectMailById(8)); 3 } selectMailById方法的实现为: 1 public M

MyBatis 源码分析 - 插件机制

1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 MyBatis 为例,我们可基于 MyBatis 插件机制实现分页.分表,监控等功能.由于插件和业务无关,业务也无法感知插件的存在.因此可以无感植入插件,在无形中增强功能. 开发 MyBatis 插件需要对 MyBatis 比较深了解才行,一般来说最好能够掌握 MyBatis 的源码,门槛相对较高.本篇

MyBatis 源码分析系列文章合集

1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章中,有4篇文章字数超过了1万,最长的一篇文章约有2.7万字(含代码).考虑到超长文章对读者不太友好,以及拆分文章工作量也不小等问题.遂决定将博文整理成电子书,方便大家阅读. 经过两周紧张的排版,<一本小小的MyBatis源码分析书>诞生了.本书共7章,约300页.本书以电子书的形式发布,大家可自由

mybatis源码分析(四) mybatis与spring事务管理分析

mybatis源码分析(四) mybatis与spring事务管理分析 一丶从jdbc的角度理解什么是事务 从mysql获取一个连接之后, 默认是自动提交, 即执行完sql之后, 就会提交事务. 这种事务的范围是一条sql语句. 将该连接设置非自动提交, 可以执行多条sql语句, 然后由程序决定是提交事务, 还是回滚事务. 这也是我们常说的事务. Connection connection = dataSource.getConnection(); // connection.setTransa