MyBatis中的reflection包(一)ObjectFactory,PropertyTokenizer, Invoker, Reflector

news/2024/4/27 13:41:39/文章来源:https://blog.csdn.net/qq_43152622/article/details/127517287

内容概要

reflection是MyBatis关于反射的工具包,是实现其它功能的基石之一。这里我不准备贴上源码然而逐行解释,而是从需求分析的角度来复现。

ObjectFactory

现在有这样的需求:给你一个Class对象,要求你创建它的实例,不单单可以通过无参构造器来构造实例。
我们先实现一下无参构造版本,通过class.newInstance的方法

public class ObjectFactory {public static void main(String[] args) throws InstantiationException, IllegalAccessException {ArrayList s = create(ArrayList.class);System.out.println(s);}public static  <T> T create(Class<?> c) throws InstantiationException, IllegalAccessException {return (T) c.newInstance();}
}

还可以通过获取无参构造器来生成对象

public static  <T> T create(Class<?> c) throws Exception {Constructor<?> constructor = c.getDeclaredConstructor();T o = (T)constructor.newInstance();return o;}

上面的方法很局限,如果我想要调用有参构造器怎么办?我想给构造器传参数怎么办?
看下面的版本

c表示你要实例化的对象的Class
constructorArgsType包含了构造器中所有需要参数的Class对象,拿到这个就能指定你需要什么构造器。
constructorArgs是在实例化时给构造器传的实参

public class ObjectFactory {public static  <T> T create(Class<?> c, List<Class<?>> constructorArgsType, List<Object> constructorArgs)throws Exception {// 如果没传这两个参数就调用无参构造实例化if(constructorArgsType == null || constructorArgs == null){Constructor<?> constructor = c.getDeclaredConstructor();return (T) constructor.newInstance();}// List的toArray方法可以将一个List转化成Array,往里传一个指定的Array类型,它可以做类型推断//这里就是告诉toArray方法,帮我转换成一个Class类型的ArrayConstructor<?> constructor = c.getConstructor(constructorArgsType.toArray(new Class[0]));//传入参数,同样需要toArrayreturn (T) constructor.newInstance(constructorArgs.toArray(new Object[0]));}
}

通过上面的方法你给我任意一个Class类型还有相关参数我都可以构造出一个实例来。以下面String类型为例

public static void main(String[] args) throws Exception{ArrayList<Class<?>> constructorArsgsType = new ArrayList<>();ArrayList<Object> constructorArsgs = new ArrayList<>();constructorArsgsType.add(String.class);constructorArsgs.add("666");String s = (String)create(String.class, constructorArsgsType, constructorArsgs);System.out.println(s);}

有了上面的基础我们看Mybatis的ObjectFactory源码就一目了然了

public class DefaultObjectFactory implements ObjectFactory, Serializable {private static final long serialVersionUID = -8855120656740914948L;@Overridepublic <T> T create(Class<T> type) {return create(type, null, null);}@SuppressWarnings("unchecked")@Overridepublic <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {// 它这里先做了一个类型转换,防止你传入的是抽象类,比如传了一个List进来肯定无法实例化//所以就先做一层转换,比如将List,Collection这种统一用ArrayList,Map都用HashMap//不得不说写框架就要考虑的很周全Class<?> classToCreate = resolveInterface(type);// we know types are assignablereturn (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);}private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {try {Constructor<T> constructor;if (constructorArgTypes == null || constructorArgs == null) {constructor = type.getDeclaredConstructor();try {return constructor.newInstance();} catch (IllegalAccessException e) {if (Reflector.canControlMemberAccessible()) {constructor.setAccessible(true);return constructor.newInstance();} else {throw e;}}}constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[0]));try {return constructor.newInstance(constructorArgs.toArray(new Object[0]));} catch (IllegalAccessException e) {if (Reflector.canControlMemberAccessible()) {constructor.setAccessible(true);return constructor.newInstance(constructorArgs.toArray(new Object[0]));} else {throw e;}}} catch (Exception e) {String argTypes = Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList).stream().map(Class::getSimpleName).collect(Collectors.joining(","));String argValues = Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList).stream().map(String::valueOf).collect(Collectors.joining(","));throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);}}protected Class<?> resolveInterface(Class<?> type) {Class<?> classToCreate;if (type == List.class || type == Collection.class || type == Iterable.class) {classToCreate = ArrayList.class;} else if (type == Map.class) {classToCreate = HashMap.class;} else if (type == SortedSet.class) { // issue #510 Collections SupportclassToCreate = TreeSet.class;} else if (type == Set.class) {classToCreate = HashSet.class;} else {classToCreate = type;}return classToCreate;}@Override//判断当前class是不是一个Collection,通过isAssianableFrom实现public <T> boolean isCollection(Class<T> type) {return Collection.class.isAssignableFrom(type);}}

仔细消化一下上面的内容,我们继续。

PropertyTokenizer

我们知道通过Object.attribute的方法可以获得对象的属性,并且如果,attribute如果是个对象,这个语法还可以继续。如果数组的每一项是个对象,我们也可以通过array[index].attribute拿到其对象。
现在,如果只给你字符串形式的数据,你怎么拿到解析对象的名称和属性的名称呢?
给个例子,我想解析下面的字符串怎么办呢?

String s = "a[0].b.c[0]"

先从简单的开始,靠 . 来分割变量名称

class ProperyTokenizer{// 代表对象private String name;// 代表属性private String children;public ProperyTokenizer(String fullname) {// 先确定.所在的下标int delim = fullname.indexOf('.');// 如果查找到了if(delim > -1){// 截取前面的部分name = fullname.substring(0, delim);//剩下的就是它的属性children = fullname.substring(delim + 1);}else{name = fullname;children = null;}}public String getName() {return name;}public String getChildren() {return children;}
}

上面的实现适用的情况非常的局限

String s1 = a.b  //适用
String s2 = a.b.c // 不适用

为了实现这种嵌套的结构可以考虑实现Iterator接口,每迭代一次就解析出来一部分。Iterator必须实现next和hasNext方法。

class ProperyTokenizer implements Iterator<ProperyTokenizer> {// 省略重复的代码@Overridepublic boolean hasNext() {return children != null;}@Overridepublic ProperyTokenizer next() {return new ProperyTokenizer(children);}
}

我们测试一下

 public static void main(String[] args){String s = "a.b.c";ProperyTokenizer properyTokenizer = new ProperyTokenizer(s);while (properyTokenizer.hasNext()){System.out.println(properyTokenizer.getName());properyTokenizer = properyTokenizer.next();}System.out.println(properyTokenizer.getName());}

在这里插入图片描述
对于Object.attribute我们已经能够逐步解析出来了,但是还没有解决数组下标的问题。
我们加入数组下标的逻辑,并用indexedName保存可能带数组下标的名称,index表示数组索引

 public ProperyTokenizer(String fullname){int delim = fullname.indexOf('.');if(delim > -1){name = fullname.substring(0, delim);children = fullname.substring(delim + 1);}else{name = fullname;children = null;}// 表示可能带下标的名称indexedName = name;//找是否有数组下标delim = name.indexOf("[");if(delim > -1){//有的话先把索引取出来index = name.substring(delim + 1, name.length() - 1);//然后取数组名name = name.substring(0, delim);}}

我们测试一下

public static void main(String[] args){String s = "a[0].b.c";ProperyTokenizer properyTokenizer = new ProperyTokenizer(s);while (properyTokenizer.hasNext()){System.out.println(properyTokenizer.getName());if(!properyTokenizer.getName().equals(properyTokenizer.getIndexedName())){System.out.print(properyTokenizer.getIndexedName());System.out.print(" index: ");System.out.println(properyTokenizer.getIndex());}properyTokenizer = properyTokenizer.next();}System.out.println(properyTokenizer.getName());}

在这里插入图片描述

Invoker

下面先看一个接口

public interface Invoker{Object invoke(Object target, Object [] args);Class<?> getType();
}

这里的Invoker是个调用的管家,它有一个invoke方法,target表示待调用的对象,args是要传入的参数。

下面我们实现一个功能,提供一个对象的方法的名称就能够调用这个方法。实现字符串到具体方法实现的映射。

先给一个测试类吧,一个类中有两个public方法

class Test{void test1(){System.out.println("test1");}String test2(String s){System.out.println(s);return "ok";}
}

现在的一种做法就是,你给我方法的名称,我遍历Test.class的method,找到以后调用它。你有100个方法,我就要遍历100次。这个显然不太好。那么先用一个HashMap将方法保存下来可不可以呢?

public class TestReflect {public static void main(String[] args) throws Exception{HashMap<String, Method> methodHashMap = new HashMap<String, Method>();Method[] methods = Test.class.getDeclaredMethods();for(Method method : methods){methodHashMap.put(method.getName(), method);}Method test1Method = methodHashMap.get("test1");test1Method.invoke(new Test());Method test2Method = methodHashMap.get("test2");Object result = test2Method.invoke(new Test(), "666");}}

这样做确实可以能够解决一些问题,但还有不少缺陷。首先,丢失了方法返回值的类型,只能当作Object类型来处理。当然,你可以增加一个HashMap来保存名称和返回类型的映射。其次,这里的invoke是有问题的,getDecaledMethods获得的是所有方法,对于非public方法,直接调用可能报错。综合以上,我们单独编写一个类来实现相关功能。

class MethodInvoker{// 保存方法的返回值类型private final Class<?> returnType;// 保存方法对象private final Method method;public MethodInvoker( Method method) {this.returnType = method.getReturnType();this.method = method;}// 为method做了一层包装,处理了访问修饰符不是public时的问题public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {try {return method.invoke(target, args);}catch (IllegalAccessException e){method.setAccessible(true);return method.invoke(target, args);}}// 获取返回值类型public Class<?> getType(){return returnType;}
}

基于这个类我们只需建立字符串和MethodInvoker对象的映射,而不是字符串和Method对象的映射。
下面看用法

public class TestReflect {public static void main(String[] args) throws Exception{HashMap<String, MethodInvoker> methodHashMap = new HashMap();Method[] methods = Test.class.getDeclaredMethods();for(Method method : methods){methodHashMap.put(method.getName(), new MethodInvoker(method));}MethodInvoker test1 = methodHashMap.get("test1");Object result1 = test1.invoke(new Test());System.out.println(test1.getType().cast(result1));MethodInvoker test2 = methodHashMap.get("test2");Object result2 = test2.invoke(new Test(), "666");System.out.println(test2.getType().cast(result2));}}

细心的读者可能已经发现了,MethodInvoker不正好实现了Invoker的相关接口吗。通过这样的小例子让大家明白Invoker这个接口的意义。

好了,收拾一下行囊我们前往下一站,继续沿着Invoker的思路继续探究。

现在,我们又来了一个新需求,就是通过字符串来访问一个类的属性。
我们知道,一般的对象都是满足javaBean的要求的,就是属性私有化,然后提供getter和setter方法,但是你不能要求用户在所有细节都满足你的要求,作为框架的设计者,需要去兼容不容类型的用户。

有了MethodHandler的经验,下面的代码应该很好理解

class FieldInvoker implements Invoker{Field field;Class<?> type;public FieldInvoker(Field field) {this.field = field;type = field.getType();}@Overridepublic Object invoke(Object target, Object... args) throws IllegalAccessException {try {return field.get(target);} catch (IllegalAccessException e) {field.setAccessible(true);return field.get(target);}}@Overridepublic Class<?> getType() {return type;}
}

只不过是将Method换成了Field

Rflector

前面关于Invoker的内容铺垫了这么多,终于可以开启Mybatis中关于反射的一个及其重要的类Rflector。
它实现的功能是,可以拿到一个对象的所有字段以及getter和setter方法,并将它们与字符串之间建立起了映射。下面是Reflector类的一个用法

public void testReflector() throws InvocationTargetException, InstantiationException, IllegalAccessException {Reflector reflector = new Reflector(Person.class);// 获取reflector的所有可以获取的属性名称String[] getablePropertyNames = reflector.getGetablePropertyNames();//实例化一个person,它有三个属性Person person = new Person(1, "zs", 12);for (String property: getablePropertyNames){// 根据属性名称逐个获取属性的值System.out.println(reflector.getGetInvoker(property).invoke(person, null));}}```java
package flect;import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;public class Reflector {private Class<?> type;private Set<String> properties = new HashSet<>();private Map<String, Invoker> getMethods = new HashMap<>();private Map<String, Invoker> setMethods = new HashMap<>();public Reflector(Class<?> type) {this.type = type;// 获取所有的方法Method[] methods = type.getDeclaredMethods();// 添加get方法到HashMap中addGetMethod(methods);// 添加set方法到HashMap中addSetMethod(methods);Field[] fields = type.getDeclaredFields();// 添加没有setter,getter方法的字段addFileds(fields);}public String[] getProperyNames(){return properties.toArray(new String[0]);}public Invoker getGetInvoker(String propery){return getMethods.get(propery);}public Invoker getSetInvoker(String propery){return setMethods.get(propery);}private void addFileds(Field[] fields) {for(Field field:fields){String name = field.getName();if(properties.contains(name))continue;properties.add(name);getMethods.put(name, new FieldGetInvoker(field));setMethods.put(name, new FieldSetInvoker(field));}}private void addSetMethod(Method[] methods) {for(Method method:methods){String name = method.getName();if(name.startsWith("get") && name.length() > 3){String property = name.substring(3);String sb = property.substring(0, 1).toLowerCase() +property.substring(1);getMethods.put(sb, new MethodInvoker(method));properties.add(sb);}}}private void addGetMethod(Method[] methods){for(Method method:methods){String name = method.getName();if(name.startsWith("set") && name.length() > 3){String property = name.substring(3);String sb = property.substring(0, 1).toLowerCase() +property.substring(1);setMethods.put(sb, new MethodInvoker(method));}}}
}
上面的并不是Mybatis中Relfector的实现,而是我实现的简化版本,去掉了处理一些特殊情况的代码。但是大体功能依然的能实现。

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kong自动注册kong-spring-boot-stater

前言 kong-spring-boot-stater框架是为了解决SpringBoot项目和kong网关的自动注册&#xff0c;虽然Kong网关有提供可视化管理后台的操作界面&#xff0c;但是在多服务、多环境的时候在管理后台挨个配置每个服务节点是比较麻烦的&#xff0c;所以这也是kong-spring-boot-stater…

图形写稿基础,含teaser figure的特殊排版方法

写在前面&#xff1a;这是第一次投稿后针对论文写作部分的总结。需要注意的是&#xff1a;老师提了意见&#xff0c;一定要快速改&#xff0c;否则会很恼人。 1. 图片展示 构图要美观&#xff0c;保证横平竖直&#xff1b;图片中文字保证和文章正文中文字一样大小&#xff1b;…

VUE |“ 登录页面”的权限以及接口问题

目录 一、功能需求 二、前提准备 三、具体实现 一、功能需求 今天写到项目的登录页面&#xff0c;我这边是没有后台数据接口的&#xff0c;所以我们用了Mock模拟了一个假的数据&#xff0c;那么我们应该怎么实现呢&#xff1f;我们先来看一下功能需要。 当我们退出登录…

系分 - 系统可靠性分析与设计

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 系分 - 系统可靠性分析与设计 考点摘要 可靠性相关基本概念&#xff08;★&#xff09;系统可靠性分析&#xff08;★★&#xff09;软件可靠性设计&#xff08;★★&#xff09; 系统故障类型 系统故障是指由…

09代码

实例1 def division():print(\n==========分苹果了===========\n)apple=int(input(请输入苹果的个数))children=int(input(请输入来了多少个小朋友))result=apple//childrenremain=apple-result*childrenif remain>0:print(apple,个苹果,平均分给,children,个小朋友,每人分…

网络爬虫及openyxl模块

网络爬虫及openyxl模块 一、第三方模块简介 1.第三方模块的用处python之所以在这么多的编程语言中脱颖而出的优点是有众多的第三方库函数,可以更高效率的实现开发2.第三方模块的使用 1.第三方模块必须下载才能使用格式:pip install 模块名 -i 源地址清华大学 :https://pypi.…

【cuda编程】CUDA的运行方式以及grid、block结构关系

文章目录1. CUDA基础知识1.1 程序基本运行顺序1.2 grid与block1.3 dim类型定义2. CUDA的第一个程序3. CUDA线程的组织结构——grid与block关系1. CUDA基础知识 1.1 程序基本运行顺序 一般来说&#xff0c;一个cpugpu的程序运行如下所示&#xff1a; 1.2 grid与block 从GPU至…

网络原理——No.4 传输层_TCP协议中的延迟应答, 捎带应答, 面向字节流与TCP的异常处理

JavaEE传送门JavaEE 网络原理——No.2 传输层_TCP的连接管理 网络原理——No.3 传输层_TCP的滑动窗口, 流量控制与拥塞控制 目录延迟应答捎带应答面向字节流粘包问题TCP 中的异常处理(连接异常)TCP 和 UDP 的应用场景延迟应答 一种提高传输效率的机制, 又是基于流量控制, 来引…

调度线程池ScheduledThreadPoolExecutor源码解析

实现机制分析 我们先思考下&#xff0c;如果让大家去实现ScheduledThreadPoolExecutor可以周期性执行任务的功能&#xff0c;需要考虑哪些方面呢&#xff1f; ScheduledThreadPoolExecutor的整体实现思路是什么呢&#xff1f; 答&#xff1a; 我们是不是可以继承线程池类&am…

docker快速安装redis

一.背景 开发环境中&#xff0c;经常需要redis本地环境&#xff0c;方便开发。准备在本机的虚拟机里面准备一个redis环境。 二.版本信息 操作系统&#xff1a;Windows 10 家庭版 Oracle VM VirtualBox&#xff1a;版本 6.0.10 r132072 (Qt5.6.2) Ubuntu:16.04.6-desktop-a…

STM32CubeMX学习笔记(44)——USB接口使用(HID按键)

一、USB简介 USB&#xff08;Universal Serial BUS&#xff09;通用串行总线&#xff0c;是一个外部总线标准&#xff0c;用于规范电脑与外部设备的连接和通讯。是应用在 PC 领域的接口技术。USB 接口支持设备的即插即用和热插拔功能。USB 是在 1994 年底由英特尔、康柏、IBM、…

淘宝十年技术思考与总结,让人惊叹的进化脱变,最终确认版已发布

看了淘宝在将近10年时间里技术的革新&#xff0c;我对技术与业务有了更近一步的认识。 任何技术都是从小做起&#xff0c;一步步做起来的。如果你让04年的淘宝去做一个能承受10亿次访问的网站&#xff0c;马云那时候肯定会伤透脑筋&#xff0c;即使做半年都做不出来。但现在&a…

Java实现邮件发送

这里我们以QQ邮箱为例。 一、导入依赖:<dependencies><!-- https://mvnrepository.com/artifact/javax.activation/activation --><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><versio…

联邦学习:联邦异构知识图谱划分

在联邦场景下,C个知识图谱位于不同的客户端上。知识图谱拥的实体集合之间可能会存在重叠,而其关系集合和元组集合之间则不会重叠。我们联系一下现实场景看这是合理的,比如在不同客户端对应不同银行的情况下,由于不同银行都有着自己的业务流程,所以关系集合不重叠。本文我们…

如何给PDF文件添加水印?PDF免费添加水印教程来了

有时候&#xff0c;为了不让别人盗用我们PDF文件里面的内容或图片&#xff0c;或者是出于宣传产品的目的&#xff0c;我们经常会需要给自己的PDF文件添加各种类型的水印&#xff0c;那你们知道如何给PDF文件添加水印吗&#xff1f;下面我们就来看看如何给PDF文件添加水印&#…

瞄准五金行业采购痛难点,智慧采购管理系统实现业务流程数据化,提高采购效率

五金行业采购一直是传统企业采购的软肋和头痛环节&#xff0c;无论从人力成本&#xff0c;物料成本&#xff0c;财务监管成本&#xff0c;物流成本等都存在一个整合服务需求&#xff0c;同时&#xff0c;传统五金行业采购难的问题&#xff0c;也一直制约着行业发展&#xff0c;…

股指期货高手陈(股指期货第一人)

​ 什么是股指期货&#xff0c;怎么玩&#xff1f;请教高手&#xff01; 股指期货&#xff08;Stock Index Futures&#xff0c;即股票价格指数期货&#xff0c;也可称为股价指数期货&#xff09;&#xff0c;是指以股价指数为标的资产的标准化期货合约。双方约定在未来某个特…

RK3399应用开发 | 移植libdrm到rk3399开发板(2.4.113)

一、下载源码 下载地址:https://dri.freedesktop.org/libdrm/。 这里我下载最新的2.4.113版本: wget https://dri.freedesktop.org/libdrm/libdrm-2.4.113.tar.xz解压: xz -d libdrm-2.4.113.tar.xz tar -xf libdrm-2.4.113.tar二、编译环境安装 1. 更新python ubuntu安…

【安信可NB-IoT模组EC系列应用笔记⑧】用NB-IoT模组EC系列了解LwM2M协议并接入云平台

文章目录前言一、测试准备1、硬件准备2、云平台准备二、云平台连接1.注册入网2.读取IMSI及IMEI3.利用IMSI及IMEI创建设备4.LwM2M连接云平台设备三、 数据互交1.ATMIPLNOTIFY 通知属性变化2.ATMIPLREADRSP 返回读取结果3.ATMIPLWRITERSP 发送写入结果4.ATMIPLEXECUTERSP 发送执行…

半乳糖-人血清白蛋白 Gal-HSA,Gal-PEG-HSA 半乳糖修饰人血清白蛋白

产品名称&#xff1a;半乳糖修饰人血清白蛋白 Gal-HSA 用途&#xff1a;科研 状态&#xff1a;固体/粉末/溶液 产品规格&#xff1a;1g/5g/10g 保存&#xff1a;冷藏 储藏条件&#xff1a;-20℃ 储存时间&#xff1a;1年 温馨提醒&#xff1a;仅供科研&#xff0c;不能用于人体…