
文档教程知识库【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址https://gitcode.com/doocs/source-code-hunter点击查看免费下载本文基于 doocs/source-code-hunter 仓库中的 Mybatis-ObjectWrapper.md 展开深入剖析org.apache.ibatis.reflection.wrapper包下的核心抽象ObjectWrapper接口及其三个实现类BaseWrapper、BeanWrapper、MapWrapper、CollectionWrapper。读完本文你将理解 MyBatis 如何用一套统一的 API 屏蔽 JavaBean、Map、集合List/Set/数组在属性读写上的差异掌握嵌套属性如user.address.city与集合索引访问如list[0].name的底层解析机制以及ObjectWrapper在参数绑定、结果集映射中的实际调用位置。一、为什么需要 ObjectWrapperMyBatis 的反射工具箱需要解决一个核心问题属性读写目标对象的形态千差万别。可能是标准的 JavaBean通过 getter/setter 访问属性可能是MapString, Object通过map.get(key)/map.put(key, value)访问可能是List、Set、数组等集合类型通过下标或add/addAll操作还可能是嵌套对象例如user.address.city、list[0].name这种带索引的复合属性路径。如果在参数映射、结果集映射的每一处都写if (object instanceof Map) ... else if ...代码将极度冗余且难以扩展。于是 MyBatis 在MetaObject之上抽象出了ObjectWrapper接口把所有目标对象的读写统一收敛为一组语义明确的方法。MetaObject的构造逻辑清晰地展示了这种按类型选择包装器的策略见 Mybatis-MetaObject.mdif (object instanceof ObjectWrapper) { this.objectWrapper (ObjectWrapper) object; } else if (objectWrapperFactory.hasWrapperFor(object)) { this.objectWrapper objectWrapperFactory.getWrapperFor(this, object); } else if (object instanceof Map) { this.objectWrapper new MapWrapper(this, (Map) object); } else if (object instanceof Collection) { this.objectWrapper new CollectionWrapper(this, (Collection) object); } else { this.objectWrapper new BeanWrapper(this, object); }即对象是 Map 就包装成 MapWrapper是 Collection 就包装成 CollectionWrapper其余一律当作 JavaBean 包装成 BeanWrapper。同时 MyBatis 也开放了ObjectWrapperFactory扩展点允许自定义包装器如支持第三方集合类型。完整的类继承关系如下二、ObjectWrapper 接口一组语义统一的方法契约ObjectWrapper接口位于org.apache.ibatis.reflection.wrapper.ObjectWrapper全部方法如下public interface ObjectWrapper { /** * 根据 prop 获取属性值 */ Object get(PropertyTokenizer prop); /** * 设置属性 */ void set(PropertyTokenizer prop, Object value); /** * 获取属性 */ String findProperty(String name, boolean useCamelCaseMapping); /** * get 方法名可读方法名 */ String[] getGetterNames(); /** * set 方法名可写方法名 */ String[] getSetterNames(); /** * set 数据类型获取可写的数据类型 */ Class? getSetterType(String name); /** * get 数据类型获取可读的数据类型 */ Class? getGetterType(String name); /** * 判断是否包含 set 方法 */ boolean hasSetter(String name); /** * 判断是否包含 get 方法 */ boolean hasGetter(String name); /** * 初始化数据处理嵌套属性的中间层实例化 */ MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory); /** * 判断是不是 list */ boolean isCollection(); /** * list add */ void add(Object element); /** * list addAll */ E void addAll(ListE element); }按职责可以分成四组理解方法组方法语义属性读写get/set按PropertyTokenizer解析出的属性路径读写值支持索引访问属性元数据findProperty/getGetterNames/getSetterNames/getSetterType/getGetterType/hasSetter/hasGetter暴露对象的可读/可写属性名、类型及存在性判断嵌套实例化instantiatePropertyValue当嵌套属性路径中间对象为 null 时用ObjectFactory创建实例并回写集合能力isCollection/add/addAll标识并操作集合类型的包装器注意get/set接收的参数不是简单的字符串而是PropertyTokenizer。PropertyTokenizer负责把user.address[0].city这类表达式切分成name当前段、index索引可为 null、children剩余子路径这是后续嵌套解析的基石。三、BaseWrapper集合与索引访问的公共底座BaseWrapper是BeanWrapper与MapWrapper的公共抽象父类持有MetaObject metaObject引用并沉淀了三个与集合/索引相关的通用方法。public abstract class BaseWrapper implements ObjectWrapper { protected static final Object[] NO_ARGUMENTS new Object[0]; protected final MetaObject metaObject; protected BaseWrapper(MetaObject metaObject) { this.metaObject metaObject; } /** * 处理集合对象 */ protected Object resolveCollection(PropertyTokenizer prop, Object object) { if (.equals(prop.getName())) { return object; } else { return metaObject.getValue(prop.getName()); } } /** * 对象获取根据 index * map.get(index)、数组 array[index]、list list.get(index) */ protected Object getCollectionValue(PropertyTokenizer prop, Object collection) { if (collection instanceof Map) { // 如果是 Map 类型则 index 为 key return ((Map) collection).get(prop.getIndex()); } else { // index 作为下标直接获取 int i Integer.parseInt(prop.getIndex()); if (collection instanceof List) { return ((List) collection).get(i); } else if (collection instanceof Object[]) { return ((Object[]) collection)[i]; } else if (collection instanceof char[]) { return ((char[]) collection)[i]; } else if (collection instanceof boolean[]) { return ((boolean[]) collection)[i]; } else if (collection instanceof byte[]) { return ((byte[]) collection)[i]; } else if (collection instanceof double[]) { return ((double[]) collection)[i]; } else if (collection instanceof float[]) { return ((float[]) collection)[i]; } else if (collection instanceof int[]) { return ((int[]) collection)[i]; } else if (collection instanceof long[]) { return ((long[]) collection)[i]; } else if (collection instanceof short[]) { return ((short[]) collection)[i]; } else { throw new ReflectionException(The prop.getName() property of collection is not a List or Array.); } } } /** * 设置属性值List、object[]、char[]、boolean[]、byte[]、double[]、float[]、int[]、long[]、short[] * map - put(index,value)、list - list.set(index,value)、array - array[index] value */ protected void setCollectionValue(PropertyTokenizer prop, Object collection, Object value) { if (collection instanceof Map) { ((Map) collection).put(prop.getIndex(), value); } else { int i Integer.parseInt(prop.getIndex()); if (collection instanceof List) { ((List) collection).set(i, value); } else if (collection instanceof Object[]) { ((Object[]) collection)[i] value; } else if (collection instanceof char[]) { ((char[]) collection)[i] (Character) value; } else if (collection instanceof boolean[]) { ((boolean[]) collection)[i] (Boolean) value; } else if (collection instanceof byte[]) { ((byte[]) collection)[i] (Byte) value; } else if (collection instanceof double[]) { ((double[]) collection)[i] (Double) value; } else if (collection instanceof float[]) { ((float[]) collection)[i] (Float) value; } else if (collection instanceof int[]) { ((int[]) collection)[i] (Integer) value; } else if (collection instanceof long[]) { ((long[]) collection)[i] (Long) value; } else if (collection instanceof short[]) { ((short[]) collection)[i] (Short) value; } else { throw new ReflectionException(The prop.getName() property of collection is not a List or Array.); } } } }三个方法的分工resolveCollection从属性路径中定位出集合本体。当prop.getName()为空串时例如表达式本身就是[0]这种形式直接返回当前对象否则继续借助metaObject.getValue(prop.getName())向下解析。getCollectionValue按索引取值。Map场景下prop.getIndex()被当作 key 使用这正是map[key]写法的来源其余场景下prop.getIndex()被解析为整数下标。值得注意这里显式覆盖了 8 种基本类型数组char[]、boolean[]、byte[]、double[]、float[]、int[]、long[]、short[]与Object[]、List说明 MyBatis 对基本类型数组的反射访问做了完整的兜底处理。setCollectionValue与getCollectionValue对称的写操作数组赋值时还会做一次类型转换如(Character) value保证基本类型数组写入的类型安全。四、BeanWrapperJavaBean 的标准包装器BeanWrapper是使用频率最高的实现负责对普通 POJO 的属性读写。它在构造时借助MetaClass缓存目标类的反射元数据public BeanWrapper(MetaObject metaObject, Object object) { super(metaObject); this.object object; this.metaClass MetaClass.forClass(object.getClass(), metaObject.getReflectorFactory()); }MetaClass内部通过Reflector见 Mybatis-Reflector.md解析出可读属性列表、可写属性列表以及每个属性对应的 setter/getter 的InvokerReflectorFactoryDefaultReflectorFactory使用ConcurrentHashMap做缓存保证了同一 Class 只解析一次。4.1 get / set索引优先非索引走 bean 属性Override public Object get(PropertyTokenizer prop) { // 索引不为空 if (prop.getIndex() ! null) { // 实例化集合对象 Object collection resolveCollection(prop, object); return getCollectionValue(prop, collection); } else { // 没有索引 return getBeanProperty(prop, object); } } Override public void set(PropertyTokenizer prop, Object value) { // 是否存在索引 if (prop.getIndex() ! null) { Object collection resolveCollection(prop, object); // 向上层调用 BaseWrapper setCollectionValue(prop, collection, value); } else { // 本类方法 setBeanProperty(prop, object, value); } }逻辑非常清晰属性路径带索引如items[0]时走 BaseWrapper 的集合读写不带索引如userName时走 JavaBean 的 getter/setter。4.2 getBeanProperty / setBeanProperty经由 Invoker 的反射调用private Object getBeanProperty(PropertyTokenizer prop, Object object) { try { // 获取 get 方法 Invoker method metaClass.getGetInvoker(prop.getName()); try { // 获取属性值 return method.invoke(object, NO_ARGUMENTS); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } catch (RuntimeException e) { throw e; } catch (Throwable t) { throw new ReflectionException(Could not get property prop.getName() from object.getClass() . Cause: t.toString(), t); } } private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) { try { // 获取 set 方法 Invoker method metaClass.getSetInvoker(prop.getName()); Object[] params {value}; try { method.invoke(object, params); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } catch (Throwable t) { throw new ReflectionException(Could not set property prop.getName() of object.getClass() with value value Cause: t.toString(), t); } }两个方法都遵循取 Invoker → invoke → 异常包装的模式getter 使用无参调用NO_ARGUMENTSsetter 把值包装成单元素数组底层异常先用ExceptionUtil.unwrapThrowable解开最终统一包装为ReflectionException便于上层定位是哪个属性、哪个对象、哪个值出了问题。4.3 嵌套属性的递归处理a.b.c 被逐层解析BeanWrapper的getSetterType、getGetterType、hasSetter、hasGetter都处理了属性路径嵌套的情况Override public boolean hasGetter(String name) { PropertyTokenizer prop new PropertyTokenizer(name); if (prop.hasNext()) { if (metaClass.hasGetter(prop.getIndexedName())) { MetaObject metaValue metaObject.metaObjectForProperty(prop.getIndexedName()); if (metaValue SystemMetaObject.NULL_META_OBJECT) { return metaClass.hasGetter(name); } else { return metaValue.hasGetter(prop.getChildren()); } } else { return false; } } else { return metaClass.hasGetter(name); } }以user.address.city为例PropertyTokenizer会解析出nameuser、childrenaddress.city。上述代码先用metaClass.hasGetter(user)判断第一层是否存在 getter再通过metaObject.metaObjectForProperty(user)得到下一层的MetaObject递归调用metaValue.hasGetter(address.city)。若中间某层为 nullmetaObjectForProperty会返回SystemMetaObject.NULL_META_OBJECT这个单例此时回退到metaClass.hasGetter(name)兜底判断避免空指针。getSetterType/getGetterType的处理方式与此一致先定位prop.getIndexedName()含索引的完整段名对应的下层MetaObject再委托给下层的getSetterType(prop.getChildren())从而拿到嵌套路径最深处的属性类型。4.4 instantiatePropertyValue嵌套中间层自动实例化当嵌套属性如user.address.city的address为 null 时MyBatis 需要先创建address对象才能继续赋值这正是instantiatePropertyValue的职责Override public MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory) { MetaObject metaValue; Class? type getSetterType(prop.getName()); try { Object newObject objectFactory.create(type); // 出现嵌套处理 instantiatePropertyValue - set metaValue MetaObject.forObject(newObject, metaObject.getObjectFactory(), metaObject.getObjectWrapperFactory(), metaObject.getReflectorFactory()); set(prop, newObject); } catch (Exception e) { throw new ReflectionException(Cannot set value of property name because name is null and cannot be instantiated on instance of type.getName() . Cause: e.toString(), e); } return metaValue; }流程是查 setter 类型 →ObjectFactory.create(type)反射创建实例 → 把新实例包成新的 MetaObject →set(prop, newObject)回写到父对象。返回的新MetaObject会被上层MetaObject.setValue继续用于处理children剩余路径。ObjectFactory的默认实现是DefaultObjectFactory优先走无参构造无权限时借助setAccessible(true)强开参见 反射工具箱和TypeHandler系列。BeanWrapper的isCollection()固定返回falseadd/addAll直接抛UnsupportedOperationException——因为 JavaBean 本身不是集合这两个操作对它没有意义。五、MapWrapperMap 场景的包装器当映射目标是MapString, Object时MyBatis 使用MapWrapper其读写逻辑与BeanWrapper对称但访问方式换成map.get/map.putpublic class MapWrapper extends BaseWrapper { private final MapString, Object map; public MapWrapper(MetaObject metaObject, MapString, Object map) { super(metaObject); this.map map; } Override public Object get(PropertyTokenizer prop) { if (prop.getIndex() ! null) { Object collection resolveCollection(prop, map); return getCollectionValue(prop, collection); } else { return map.get(prop.getName()); } } Override public void set(PropertyTokenizer prop, Object value) { if (prop.getIndex() ! null) { Object collection resolveCollection(prop, map); setCollectionValue(prop, collection, value); } else { map.put(prop.getName(), value); } } Override public String findProperty(String name, boolean useCamelCaseMapping) { return name; } Override public String[] getGetterNames() { return map.keySet().toArray(new String[map.keySet().size()]); } Override public String[] getSetterNames() { return map.keySet().toArray(new String[map.keySet().size()]); } }MapWrapper的几个设计要点get/set无索引时直接map.get(prop.getName())/map.put(prop.getName(), value)带索引时如map[key]或list[0]复用 BaseWrapper 的集合读写逻辑findProperty直接原样返回name不做驼峰映射因为 Map 的 key 天然就是属性名getGetterNames/getSetterNames返回map.keySet()可读/可写属性即 Map 的 key 集合getSetterType/getGetterType优先返回map.get(name).getClass()推断实际类型key 不存在或值为 null 时回退为Object.class嵌套路径场景同样委托给下层MetaObjecthasSetter恒为true——对 Map 而言任何 key 都可以随时 put 进去hasGetter则用map.containsKey判断instantiatePropertyValue直接创建一个HashMap作为中间层实例set回写后包装成新的MetaObject返回。这一特性也是 MyBatis 支持查询结果直接映射为MapString, Object的底层基础。六、CollectionWrapper集合的包装器当目标是List、Set等Collection时使用CollectionWrapper。它的设计最极端除了isCollection、add、addAll之外其余方法全部抛UnsupportedOperationExceptionpublic class CollectionWrapper implements ObjectWrapper { private final CollectionObject object; public CollectionWrapper(MetaObject metaObject, CollectionObject object) { this.object object; } Override public Object get(PropertyTokenizer prop) { throw new UnsupportedOperationException(); } Override public void set(PropertyTokenizer prop, Object value) { throw new UnsupportedOperationException(); } // findProperty / getGetterNames / getSetterNames / getSetterType / // getGetterType / hasSetter / hasGetter / instantiatePropertyValue // 全部 throw new UnsupportedOperationException(); Override public boolean isCollection() { return true; } Override public void add(Object element) { object.add(element); } Override public E void addAll(ListE element) { object.addAll(element); } }原因很直观集合对象没有命名属性可言get/set/hasGetter等按名字访问的语义对它不成立集合的合法操作只有add/addAll/isCollection。isCollection()返回true是上层判断当前对象是否是集合的关键开关。七、ObjectWrapper 在 MyBatis 中的实际应用ObjectWrapper不是孤立存在的类它是 MyBatis 反射工具箱的最底层出口MetaObject的所有属性操作最终都委托给它。在仓库源码文档中可以找到多处实际调用执行器与语句处理器中的参数取值在 5、Executor组件.md 与 4、StatementHandler.md 中都存在metaObject.getValue(propertyName)的调用——例如解析Param注解指定的属性、处理动态 SQL 绑定的参数对象时都依赖MetaObject → ObjectWrapper这条链路完成属性读取。Mapper 方法参数为集合的场景在 Mybatis-MapperMethod.md 中可以看到metaObject.objectWrapper CollectionWrapper的注释与metaObject.addAll(list)的调用——当 Mapper 方法接收List/Set/ 数组参数并需要把多个返回值累加进集合时正是通过CollectionWrapper.addAll完成3、binding模块.md 中也有metaObject.addAll(list)的同类用法。结果集映射ResultSetHandler在处理resultMap时对目标 POJO 的字段赋值最终也会落到MetaObject.setValue → BeanWrapper.set → setBeanProperty的链路上嵌套association/collection中间对象为 null 时触发instantiatePropertyValue自动创建。级联属性与动态 SQL动态 SQL 中形如#{user.address.city}、#{items[0].name}的参数解析以及OGNL之外的内置参数取值底层都要经过PropertyTokenizer切分 ObjectWrapper逐层读写。八、小结反射工具箱的四层协作把本文内容放回 MyBatis 反射工具箱的整体脉络详见 反射工具箱和TypeHandler系列可以总结出清晰的分工层次组件职责元数据层Reflector/MetaClass缓存 JavaBean 的可读/可写属性、setter/getter 的Invoker、属性类型统一入口层MetaObject对外提供getValue/setValue/addAll等门面方法按对象类型选择包装器策略实现层ObjectWrapper体系BeanWrapperJavaBean、MapWrapperMap、CollectionWrapper集合、BaseWrapper公共集合读写实例化层ObjectFactory为嵌套属性的中间层对象提供反射创建能力这套设计的核心价值在于通过策略 门面模式把对象形态的差异封装在包装器内部上层参数映射、结果映射、动态 SQL、缓存只需要面向MetaObject这一套 API 编程新增对象形态如第三方集合时也只需实现ObjectWrapper并注册到ObjectWrapperFactory无需改动任何调用方代码。理解了ObjectWrapper就等于拿到了阅读 MyBatis 参数绑定与结果映射源码的钥匙。赞分享文档教程知识库【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址https://gitcode.com/doocs/source-code-hunter点击查看免费下载相关推荐MyBatis 源码解析ObjectWrapper 对象包装器 —— 统一 Bean、Map 与集合的属性访问模型MyBatis 源码解析ObjectWrapper 对象包装器 —— 统一 Bean、Map 与集合的属性访问模型 导读 ObjectWrapper 是 My文档教程技术博客知识库hadoop-cluster-docker部署最佳实践生产环境配置与安全指南hadoop cluster docker部署最佳实践生产环境配置与安全指南 在当今大数据时代快速搭建和部署Hadoop集群已成为数据工程师的必备技能。ha文档教程知识库Symfony属性访问对象属性操作的统一接口Symfony属性访问对象属性操作的统一接口 你是否还在为PHP对象属性的复杂操作而困扰在日常开发中我们经常需要处理各种对象的属性访问比如获取私有属性、后端Web框架上一篇终极Mousetrap.js性能优化指南从100ms到10ms的事件处理加速技巧下一篇Ludwig模型部署最佳实践从本地服务到云端API的无缝迁移创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考