javaweb 动态获取方法参数表中需要的实体对象的全类名(主文章的补充文章)

这是一篇对javaweb BaseServlet 自动封装数据并调用service方法中提到的内容的补充说明,由于篇幅太大单独拿出来说明

1 理清整个封装的思路

首先在循环判断中,我们做的操作就是一一识别方法的参数表各个位置上需要的都是什么数据类型,Class对象指向方法的参数表上的各个位置
image

如果是请求头就给请求头,如果是响应头就给响应头,如果是实体类就给实体类对象,如果是其他的情况就躺平吧,这个servlet封装方法还做不到面对那么复杂的情况,需要使用spring的四个包进行辅助,在这里先不说了。

分析现状
所以现在我们默认处理的方法参数列表中,只有请求头、响应头、实体类。
但是一般请求头和响应头只有一个,需求的实体类却可以是很多个,比如分页查询,假如现在拿到了一个Class对象,获得了这个对象的全类名,要怎么判断当前的这个Class指向的数据类型是方法参数列表中的实体类,又是哪个实体类?
image

给出方法
有一种方法:将请求头和响应头的判断放在一开始,如果代码经过了前两轮判断,则可以确定此时Class对象指向的不是请求头也不是响应头,又基于默认是实体类,就可以直接进行json数据转对象了,又由于Class对象是与参数列表一一对应的,所以无所谓是哪个实体类,你是Heroinfo 我就转为Heroinfo 你是PageInfo 我就转Pageinfo

方法存在风险
但是这是有风险的,万一遇到一个方法就真存在第四种情况呢,那么一执行数据转换就报错了。
总不能穷举整个项目中所有存在对象的路径吧?真就穷举所有情况,然后判断此时Class对象指向的不是项目中的对象时停止方法并输出提示?那得累死啊

逆向思考
那么反过来,假如已经确定Class指向的是一个实体类了,我们要做的就是使用自定义的工具类或者第三方jar包将请求头中的数据存入该实体类的一个对象中。
由于这种实体类可能是任意一种数据类型,那么可以考虑给object,也可以考虑给泛型 T
image

如果给的是泛型 T 那么此时baseServlet可以设置为泛型类。
image

紧接着就要求各个子Servlet在继承时说明自己的泛型类型,而各个子Servlet在继承时说明的泛型类型就是各个子Servlet中各种方法的参数表中实际用到的数据类型。
image

2 通过设置泛型让问题得到转变

这样通过对 “json数据转存到对象”的操作中泛型的设置,就能要求各个子Servlet在编写时给出自己需要用的实体类,此时在对Class对象进行循环判断时,就有一个实体类的范围了——各个子servlet在继承时说明的泛型类型

于是现在问题就从 “如何判断Class对像指向的是实体类对象”转为“如何判断class对象指向的是一个确定范围内的实体类对象” 前者那除非穷举整个项目中所有实体对象,否则做不到,而后者就直接根据当前servlet使用的实体类对象进行判断,就可以做到。

由于当前servlet使用的实体类对象都因为父类为泛型类的原因在继承父类时都进行了声明,因此我们只需要想办法取到各个子servlet在继承时声明的数据类型即可。
image

3 想办法取到当前servlet类声明的泛型的数据类型

①设置父类为泛型类
②声明一个Class对象 用于储存当前servlet类声明的泛型类型
实际应该声明一个Class对象数组,因为servlet类声明的泛型类型可以是多个,现在是因为已经知道servlet类只声明了一个类型,因此偷懒了
③创建一个构造函数,在里面编写获取servlet声明的泛型的代码
也可以创建一个非静态代码块,总之要求获取servlet声明的泛型的代码要在整个servlet类执行时就执行
image

④上图代码执行完毕后就能获得servlet在声明泛型时的具体类型,此时就能对Class对的全类名进行一个判定,如果相同,说明此时Class对指向的就是一个实体类而不是别的东西,此时就能实现条件都不满足时提示“你这方法需要的参数既不是请求头又不是响应头还不是实体类,我这个servlet封装搞不定这种复杂情况,方法停止”
image

4 拆解代码

下面说明构造函数中那一长串代码的具体实现,以实现功能为顺序说明
①this.getClass()
要记住代码写在父类中,但其实是在子类执行,因此这个代码获取到的Class对象指向当前子类servlet

②this.getClass().getGenericSuperclass()
getGenericSuperclass 返回直接继承的父类(包含泛型参数)
这里使用了一个新的Class对象的方法——getGenericSuperclass()

语法 说明
Class对象 . getGenericSuperclass() 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type,返回的是一个Type
Class对象 . getGenericInterfaces() 没有使用,但因为容易和上面的方法一起谈出来,提一下,这是返回当前实体实现的所有接口的Type,返回的是一个Type[]

代码:
image

image

输出结果:直观表现就是获取到当前子类Servlet在继承父类时extends后面写的一长串信息,是父类的全类名+<声明的数据类型的全类名>
image

现在我们已经获得了一个能表明当前servlet类允许传入的数据类型的信息(也即内部所有方法需要的数据类型) 这是一个Type格式的数据
此时我们希望获取到<>里的所有信息
一种思路是直接将其以字符串格式输出,然后进行字符串截取,但是这未免太不专业
另一种思路就是将这个Type类型转为Class对象 这个Class对象想来应该和<>里的数据有关
image

我们可以尝试将Type类直接转为Class,因为Type其实是一个接口,Class是它的实现类,这步操作属于向下造型,是允许的,编译时没有报错,但是允许时报错了。
报错:sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl cannot be cast to java.lang.Class

这说明此时的Type类型其实不是java.lang包下的Type 而是Type这个接口下的一个实现类ParameterizedTypeImpl
因此此时的Type不允许直接强转为另一个包下的实现类Class,我们要先把当前这个实现类转回顶层的Type格式,再转成Class
而且怎么想也不太对,现在的Type获取到的数据是一长串数据,应该有一个方法能从中取出<>里的数据,Class对象也应该是直接对应<>里的数据,哪能将那么长一串数据直接转为Class呢

③(ParameterizedType)this.getClass().getGenericSuperclass()
这步强转操作是将sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl类强转为java.lang.reflect.ParameterizedType
image

ParameterizedType 是一个接口 底下有一个实现类为sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl 所以这属于实现类转回父类接口的类型 自然没问题
并且此时就可以使用这个接口的方法,这个接口只有三个方法
image

④((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()
这里要使用接口类的第一种方法

语法 说明
ParameterizedType对象 . getActualTypeArguments() 返回一个表示此类型的实际类型参数的Type对象数组

image

getActualTypeArguments() 这个方法的说明有点难理解
其实就是将java.lang.reflect.ParameterizedType转为java.lang.reflect.Type

下面用一个小例子说明我们拿到的是什么:


现在父类定义了三个泛型
image

子类继承父类并且声明了三个泛型
image

此时代码如下:
image

结果如下:
image


可以看到这个方法返回的Type数组中的元素其实就是一个个Class对象,分别指向子类声明的对象
这个数组中的元素就是我们需要的Class对象——分别指向<>中声明的数据类型

⑤(Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]
最后只需要从取到的数组中取出值,再强制转换为Class对象即可,将java.lang.reflect.Type转为java.lang.Class
这里的[0]是因为这个数组中只有一个元素,放在索引为0的位置,要从中取出赋值给设置好的Class对象,然后拿去做判断
image

下面附上整体代码的解析

image

posted @ 2021-08-12 01:20  夏·舍  阅读(520)  评论(0编辑  收藏  举报