【Mybatis源码分析】Mybatis中的反射(MetaObject)详细讲解

news2024/11/15 8:44:38

Mybatis中的反射

  • 一、引入MetaObject
  • 二、MetaObject 源码分析
    • 1. 使用MetaObject
  • 三、BeanWrapper源码分析
    • 1. MetaClass
    • 2. ReflectorFactory
    • 3. Reflector
  • 四、总结

一、引入MetaObject

在使用Mybatis,编写DQL语句时,查询结果可能会是多个,多变量指定肯定是不现实的。而Mybatis可以进行映射,将JDBC返回的结果映射到实例类或者Map对象中,方便开发者直接使用返回对象,就可以得到从数据库取出来的结果。

映射原理大伙都知道是利用了反射(因为咱就只是通过resulttype或者resultmap给了返回值类型的全限定类名而已啊,底层不用反射,返回的对象哪来的啊🤞)。在Mybatis中,提供了一个很强大的工具类===》MetaObject,翻译为元对象,它可以使用pojo类中本有的getter,setter方法,即使是嵌套的对象也可以。当然也可以获取一些getter用到的属性名啊,setter用到的属性名,类型啊等等。

当然当我们使用Mybatis框架时,如果需要也可以去用它。

注意:上面说的工具类是因为我觉得它的构造函数是私有的,而且被开发者拿来使用,但是它可以通过其内部的静态方法forObject去得到其对象的。

二、MetaObject 源码分析

在具体分析之前,看看MetaObject中的构造函数和其内部属性(都是final类型的),MetaObject只提供了一个有参构造(私有的),如下:

private final Object originalObject;// 实体对象
private final ObjectWrapper objectWrapper;// 对象的封装,内部是一些属性名什么getset方法的封装
private final ObjectFactory objectFactory;// 对象工厂
private final ObjectWrapperFactory objectWrapperFactory;// 对象封装工厂,可拓展
private final ReflectorFactory reflectorFactory;// 用来构造Reflect对象的反射工厂

private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory,
      ReflectorFactory reflectorFactory) {
      // 属性赋值
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;

    if (object instanceof ObjectWrapper) {
      // 判断实例对象是否已经实现过了ObjectWrapper
      this.objectWrapper = (ObjectWrapper) object;
    }
    else if (objectWrapperFactory.hasWrapperFor(object)) {
      // 判断这个对象是不是mybatis内置的CustomCollection,或者是不是继承了Author实体类(这个最后还是用BeanWrapper处理的,感觉没啥用)
      // 当然这些判断是基于传过来的工厂对象的
      this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    }
    else if (object instanceof Map) {
      // 如果是map及其实现的话,其使用封装为mapwrapper,但map只能是<String,Object>
      this.objectWrapper = new MapWrapper(this, (Map) object);
    }
    else if (object instanceof Collection) {
      // 这封装里面全是抛异常
      this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    }
    else {
      // 其他实力类封装
      // 封装了MetaClass对象
      this.objectWrapper = new BeanWrapper(this, object);
    }
  }

可以看见是通过if-elseif-else去初始化objectWrapper对象的。

ObjectFactory继承结构

在这里插入图片描述

Mybatis在核心配置中可以对ObjectFactory进行配置,在setting标签的子标签objectFactory进行配置,如果你觉得可以比源码写的好的话,当然也可以自己写一个对象工厂,去继承ObjectFactory,然后配置上去。(感觉意义不打,如果有用的话记得告诉小编😬)

下面是Mybatis官方文档的案例

在这里插入图片描述下面再看看MetaObject提供的getValue和setValue,供外界使用的,用了PropertyTokenizer对象,用了迭代器设计模式。(简单看看不细说,具体实现比较简单,用了MetaClass中的Reflector对象…后面小编会去解析Reflector这个核心类)

  public Object getValue(String name) {
    // 这个属性分词器,就解析你字符串用的,解析成属性名
    // 得到属性名好执行反射对象中封装好的get方法
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (!prop.hasNext()) {
      return objectWrapper.get(prop);
    }
    // 获取对象内置属性
    MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
    if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
      return null;
    } else {
      return metaValue.getValue(prop.getChildren());
    }
  }

  public void setValue(String name, Object value) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
      if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
        if (value == null) {
          // don't instantiate child path if value is null
          return;
        }
        metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
      }
      metaValue.setValue(prop.getChildren(), value);
    } else {
      objectWrapper.set(prop, value);
    }
  }

1. 使用MetaObject

可以看看具体怎么个使用(不出意外,也用不上)。但感觉如果自己写什么小框架方便开发的,小编觉得它是好工具。

    @Test
    public void testMetaObject(){
        /**
         * user中的属性
         *     private Integer id;
         *     private String username;
         *     private String password;
         *     private List<Integer> list;
         *     private Student stu;
         */
        User user = new User();
        MetaObject metaObject = MetaObject.forObject(user,
                new DefaultObjectFactory(),
                new DefaultObjectWrapperFactory(),
                new DefaultReflectorFactory());
        metaObject.setValue("id",1);
        metaObject.setValue("username","林奕");
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        metaObject.setValue("list",list);
        // collection集合、map集合、数组都是用[索引]的形式
        System.out.println(metaObject.getValue("list[0]"));
        Student stu = new Student();
        stu.setName("美琪");
        metaObject.setValue("stu",stu);
        // 如果内部还有其他实例对象,会创建其MetaObject,再次调用getValue
        // 可以假设理解为递归调用了getValue,但实际不是同一个对象调用的
        System.out.println(metaObject.getValue("stu.name"));
        System.out.println(user);
    }

三、BeanWrapper源码分析

在初始化objectwrapper对象封装对象的时候,要么是实体类封装,要么是map封装,map封装前面做过了判断,用得更多的还是映射到实体类的属性上,而其所做的封装都得归功于BeanWrapper。所以这里对BeanWrapper类进行源码分析。

下面是BeanWrapper继承结构

在这里插入图片描述下面是BeanWrapper类中的属性和构造函数(其他方法都是对ObjectWrapper方法的实现)

  private final Object object;// 传过来的实例对象
  private final MetaClass metaClass;// 元字节码对象(内部封装了reflector对象,用于获取所需getter方法,setter方法,或属性等等)

  public BeanWrapper(MetaObject metaObject, Object object) {
    super(metaObject);
    this.object = object;
    // 通过MetaClass中的forClass获取MetaClass对象,和MetaObject对象获取方式一样
    // MetaClass构造方法私有
    this.metaClass = MetaClass.forClass(object.getClass(), metaObject.getReflectorFactory());
  }

下面看看metaClass对象是干啥的,可以看见获取MetaClass对象需要俩参数:

  1. 通过实例对象获取的Class对象
  2. 通过metaObject获取的ReflectorFactory反射工厂对象

1. MetaClass

下面是其属性和构造方法及forClass方法

// 方法很简单,就是去new MetaClass对象
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
    return new MetaClass(type, reflectorFactory);
  }

  private final ReflectorFactory reflectorFactory;// 反射工厂,用于得到reflector对象的。
  private final Reflector reflector;// 反射对象

  private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
    this.reflectorFactory = reflectorFactory;
    // 通过反射工厂去获取reflector对象
    this.reflector = reflectorFactory.findForClass(type);
  }

2. ReflectorFactory

reflectorFactory反射工厂还提供缓存,开始支持高并发的,然后去除了。
在这里插入图片描述可以看看其默认实现的反射工厂代码,它是利用ConcurrentHashMap对象,保障了在多线程环境下的安全。当然也提供了缓存的作用,如果对应的type存在就直接给出反射对象,不存在会存入reflectMap,会得到重新new的反射对象。

在这里插入图片描述
这个反射工厂也可供外界使用,而且可拓展。

3. Reflector

通过MetaObject setvalue,getvalue方法去操作实体对象是依赖于objectwrapper对象的,objectwrapper利用了多态,调用其中的方法实际是调用BeanWrapper对象中的(拿BeanWrapper举例),而BeanWrapper对象依赖于MetaClass对象,而MetaClass对象依赖于Reflector对象。核心反射实现最后还是到了Reflector类,其余是一层一层的调用。下面对Reflector进行源码分析。

下面是其构造函数及其属性

  private static final MethodHandle isRecordMethodHandle = getIsRecordMethodHandle();
  private final Class<?> type;
  // get方法中用到的属性
  private final String[] readablePropertyNames;
  // set方法中用到的属性
  private final String[] writablePropertyNames;
  // 与get类似的操作
  private final Map<String, Invoker> setMethods = new HashMap<>();
  // 封装get方法,key是get后的名(会将第一个字符转小写),value是方法
  private final Map<String, Invoker> getMethods = new HashMap<>();
  // 与get一样的操作
  private final Map<String, Class<?>> setTypes = new HashMap<>();
  // 封装get方法的返回值,key是get后的名(会将第一个字符转小写),value是返回值类型字节码
  private final Map<String, Class<?>> getTypes = new HashMap<>();
  private Constructor<?> defaultConstructor;

  // 这个是把getter、setter 方法中用到的属性名进行大写转换然后封装起来
  // key是大写转换后的,value是属性名
  private final Map<String, String> caseInsensitivePropertyMap = new HashMap<>();

  public Reflector(Class<?> clazz) {
    type = clazz;
    // 这里是获取无参构造
    addDefaultConstructor(clazz);
    Method[] classMethods = getClassMethods(clazz);
    if (isRecord(type)) {
      addRecordGetMethods(classMethods);
    } else {
      addGetMethods(classMethods);// get方法封装
      addSetMethods(classMethods);// set方法的封装
      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);
    }
  }

下面拿get方法封装举例。(再往下就是反射操作了)

  private void addGetMethods(Method[] methods) {
    // 因为存在getXX,isXX吧,所以用List作为value
    Map<String, List<Method>> conflictingGetters = new HashMap<>();
    // 这里filter需要有返回值的,需要满足getter条件的,就是以get或is开头的方法
    Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
      // 获取属性名(get后面的,is后面的,首字符转小写)
      // 还得不能冲突,及首字符不让用$,还有属性名不能是class......
        .forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
    // 解析这个conflictingGetters
    // 将满足条件的存入到getMethods,getTypes中
    resolveGetterConflicts(conflictingGetters);
  }

四、总结

  • 可以通过MetaObject对象对实例对象属性赋值和获取;

  • ObjectFactory工厂对象可拓展;

  • PropertyTokenizer类用了迭代器设计模式,对属性名进行操作迭代;

  • BeanWrapper依赖MetaClass,MetaClass依赖反射工厂对象和反射对象;

  • Reflector对象是对实例对象构造器、getter、setter方法、属性名等的封装;

  • 编写Bean对象的时候,应该遵循Bean规范,使得get后和set后可以和属性名对应上,从而好进行操作。本质是不管你属性名对不对应的上,反正get后和set后的名得和数据库列名对应上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/431139.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java实现图片验证码功能

文章目录一、背景二、实现步骤1、maven中加入依赖2、CaptchaController.java3、生成验证码配置4、CaptchaService.java接口5、CaptchaServiceImpl.java实现类6、增加验证码校验涉及文件一、背景 在实现登录功能时&#xff0c;为了防止特定的程序暴力破解&#xff0c;一般为了安…

使用DevExpress22.X(Patch)控件库在VisualStudio2022使用C#进行Winform、WPF应用的开发,看这一篇就够了!

写在开头&#xff0c;Dev Express是个十分强大的控件库&#xff08;下文简称Dev&#xff09;&#xff0c;但碍于其高昂的使用费用&#xff0c;“出于学习目的”&#xff0c;我们一般使用的都是Patch版本&#xff08;在版权意识日趋加强的当下&#xff0c;不要提那两个字&#x…

面试题React

1.React Fiber是什么&#xff1f; 在 React V16 将调度算法进行了重构&#xff0c; 将之前的 stack reconciler 重构成新版的 fiber reconciler&#xff0c;变成了具有链表和指针的 单链表树遍历算法。通过指针映射&#xff0c;每个单元都记录着遍历当下的上一步与下一步&…

接口测试用例编写和接口测试模板

一、简介 接口测试区别于传统意义上的系统测试&#xff0c;下面介绍接口测试用例和接口测试报告。 二、接口测试用例模板 功能测试用例最重要的两个因素是测试步骤和预期结果&#xff0c;接口测试属于功能测试&#xff0c;所以同理。接口测试的步骤中&#xff0c;最重要的是将…

149.网络安全渗透测试—[Cobalt Strike系列]—[HTTP Beacon重定器/代理服务器/流量走向分析]

我认为&#xff0c;无论是学习安全还是从事安全的人多多少少都会有些许的情怀和使命感&#xff01;&#xff01;&#xff01; 文章目录一、Cobalt Strike 重定器1、Cobalt Strike 重定器简介2、重定器用到的端口转发工具二、cobalt strike重定器实验1、实验背景2、实验过程3、流…

Springboot项目Aop、拦截器、过滤器横向对比

前言 伟人曾经说过&#xff0c;没有调查就没有发言权(好像是伟人说的&#xff0c;不管谁说的&#xff0c;这句话是正确的)&#xff0c;有些东西看着简单&#xff0c;张口就来&#xff0c;但很有可能是错的。我个人的经验是&#xff0c;aop、过滤器、拦截器的实现方式很简单&…

冯诺依曼体系结构+操作系统

目录 冯诺依曼体系结构 基本概念 基本原理 操作系统 基本概念 设计OS的目的 管理的本质 管理的方法 系统调用和库函数 冯诺依曼体系结构 基本概念 冯诺依曼结构也称普林斯顿结构&#xff0c;是一种将程序指令存储器和数据存储器合并在一起的存储器结构。 ... 数学…

GDOUCTD NSSCTF2023广东海洋大学比赛WP RE(上) Tea Check_Your_Luck

Check_Your_Luck 下载文件是cpp 是个解方程的题&#xff0c;用python的z3 from z3 import * v,w,x,y,zBitVecs(v w x y z,16)lSolver() l.add(v * 23 w * -32 x * 98 y * 55 z * 90 333322) l.add(v * 123 w * -322 x * 68 y * 67 z * 32 707724) l.add(v * 266 …

openEuler RISC-V 23.03 创新版本亮相:全面提升硬件兼容性和桌面体验

近日&#xff0c;openEuler RISC-V 23.03 创新版本正式发布。openEuler RISC-V SIG 作为 openEuler 系统在 RISC-V 架构上的维护组织&#xff0c;主要致力于 openEuler 在 RISC-V 软硬件方面的适配&#xff0c;一直跟随 openEuler 版本节奏提供 openEuler 的 RISC-V 镜像版本。…

Redis源码之SDS简单动态字符串

Redis 是内存数据库&#xff0c;高效使用内存对 Redis 的实现来说非常重要。 看一下&#xff0c;Redis 中针对字符串结构针对内存使用效率做的设计优化。 一、SDS的结构 c语言没有string类型&#xff0c;本质是char[]数组&#xff1b;而且c语言数组创建时必须初始化大小&#…

图片转PDF怎么转换?快学习这三种免费转换方法!

图像转PDF功能是指将图像文件转换为PDF文件的过程。PDF&#xff08;PortableDocumentFormat&#xff09;它是一种文件类型&#xff0c;可以存储许多元素&#xff0c;如文本、图像和报告。PDF文档具有跨平台、可打印、可搜索等优点&#xff0c;因此广泛应用于文档共享、文档存储…

Qt扫盲-QAbstractSeries理论总结

QAbstractSeries理论总结 一、概述二、常用函数1. 属性2. 设置功能3. 显示隐藏4. 与 绘图的交互 三、信号 一、概述 QAbstractSeries类是所有Qt图表线的基类。通常&#xff0c;特定于序列类型的继承类会被使用&#xff0c;而不是这个基类。这个基类只是提供了一些管理和控制这…

多功能科学计算器:Magic Number 2 Mac中文

Magic Number Mac - 让数学更简单。当你能正确地看待数学&#xff0c;能够输入你的想法&#xff0c;并凭直觉做每件事时&#xff0c;数学就会变得轻而易举。从日常数学到高级科学&#xff0c;Magic Number 让您事半功倍——无论您的水平如何。欢迎需要的朋友下载使用&#xff0…

IDEA中使用Git提交代码

在IDEA中使用git提交代码到远程仓库&#xff0c;整体可分为如下几个步骤&#xff1a; 前提&#xff1a;注册有GitHub或者gitee账号&#xff1b;本地安装有git。 1.创建远程仓库&#xff08;github或者gitee&#xff09;&#xff1b; 2.创建本地仓库并提交代码到本地仓库&#x…

2023年如何成为一名优秀的大前端Leader?

目录 一、0-1开发vs低代码 二、优点与缺点 先以JNPF为例&#xff0c;展开说说优点&#xff1a; 1、开发周期短&#xff08;这点我愿称之为神&#xff09;&#xff1a; 2、开发成本低 3、助力企业适用市场 再来说说缺点&#xff1a; 1、平台越成熟&#xff0c;费用越高 …

【动态规划】经典问题第三组---背包问题基础

前言 小亭子正在努力的学习编程&#xff0c;接下来将开启算法的学习~~ 分享的文章都是学习的笔记和感悟&#xff0c;如有不妥之处希望大佬们批评指正~~ 同时如果本文对你有帮助的话&#xff0c;烦请收藏点赞关注支持一波, 感激不尽~~ 刷题专栏在这里~~ 简单介绍一下什么是背包问…

再学C语言50:C库中的字符串函数(2)

一、strcmp()函数 功能&#xff1a;对字符串内容进行比较&#xff0c;如果两个字符串参数相同&#xff0c;函数返回0 示例代码&#xff1a; /* test of strcmp() function */ #include <stdio.h> #include <string.h>#define NAME "Forster"int main(…

rem实现移动端自适应

rem实现自适应的原理&#xff1a;就是屏幕的宽度/任意数&#xff08;推荐设计稿除下来是整数&#xff0c;方便计算&#xff09;&#xff0c;接着设置根html的font-size为这个数&#xff0c;比如设计师给我们的设计稿宽度为750px&#xff0c;我们可以用750/7.5得到100再赋值给ht…

rnn、lstm、cnn、transformer

rnn不能并行的原因&#xff1a;不同时间步的隐藏层之间有关联。 rnn中batch的含义 如何理解RNN中的Batch_size&#xff1f;_batch rnn_Forizon的博客-CSDN博客 rnn解决的问题 不定长输入带有顺序的序列输入1 rnn前向传播 2 rnn中的反向传播 还有loss对其他参数的求导&#…

Flutter渲染原理

一 Widget Element RenderObject 之间的关系 1 Widget 在Flutter 中&#xff0c;万物皆是Widget,无论是可见的还是功能型的。一切都是Widget. 官方文档中说的Widget 使用配置和状态来描述View 界面应该长什么样子。 它不仅可以表示UI元素&#xff0c;也可以表示一些功能性的…