Java反射

Java3y

共 4091字,需浏览 9分钟

 ·

2021-01-04 16:54


本文公众号来源:安琪拉的博客

作者:安琪拉的博客

本文已收录至我的GitHub


刚开始学Java 一般不太会关注到反射,但是如果看很多框架的源码,发现反射无处不在。最近一个业务需求中用了反射,感觉非常丝滑。

前文回顾(推荐点击下方蓝色链接阅读):

Java 程序员都需要懂的 反射!

前言

鲁班:  什么是反射?

安琪拉:  反射是Java 中提供的运行期获取对象信息的能力。先记住二个关键词:运行期、对象信息。

鲁班:  那为什么Java 需要反射呢?需要在运行期获取对象信息呢?

安琪拉:  比如你希望调用某个对象的方法,例如下面这段代码:

angela 对象你如果运行期不知道它是否有dance 方法, 可以调用 getClass().getMethod("dance") 判断一下。

鲁班:  那岂不是很弱鸡,我可以直接调 angela.dance() 方法啊!

安琪拉:  你要用 angela.dance() 方法,包里是不是需要 import Angela 类,一定要有确定的Angela 对象,很多框架场景,是不知道目标对象的Class类型的,要动态获取对象的类类型。

鲁班:  我知道了,反射就是运行的时候知道这个对象能不能调某个方法。

安琪拉:  不止如此,反射就是对于任意一个对象,我们能够运行时访问它的方法和属性。

鲁班:  为什么强调运行时?

安琪拉:   因为是编译期,类型是确定的,很多时候在拿不到确定的对象的属性和值的时候,需要运行时动态调用方法或获取属性。后面会介绍一个通用框架能力通过反射实现的sample。

先说 Java 反射API相关的类有下面几个:

这里可以引出一个很有意思的话题,Java 中一切皆对象,那Class 也是对象,另外所有对象都有对应的Class(类),Class(类)就像饼干模板,Object(对象)是根据Class(类) 做出的饼干,那JDK 加载时先有Class 还是先有Object 呢?如何加载 ?这个可以留个思考题。

真实业务场景

鲁班:  那知道反射有什么用?对我平常写 curd 有帮助吗?

安琪拉:  有几点原因要知道反射,一个是一些框架代码里面会有很多反射,例如, 我们经常接触的动态代理, Spring的自定义注解。另外我们如果希望把从业务层代码抽象出一些平台能力,就可以用反射。

鲁班:  你这么说没有体感,能不能举个例子?

安琪拉:  那你继续说说上次你的需求。

鲁班:你说我最近接到了一个需求啊,要在下路把对方每一波过来的小兵做标注,只有遇到特定的小兵,我才开火。

安琪拉: 那这些小兵有什么特点呢?你打算怎么精准定位要开火的小兵?

鲁班:如果小兵身上的符文是红色符文(除此以外,还有蓝色符文和紫色符文),法术防御是魔法防御(除此以外,还有物理防御),我只对这些小兵开火,当然咯,可能以后还需要对带各种属性组合的小兵进行开火打击。

安琪拉: 需求我大概清楚了,你有思路了吗?我们先把模型建立起来,如下图所示,是小兵的模型

鲁班:你上次说了,写业务代码的时候要考虑通用性和可扩展性,但是这个功能也能用反射吗?

安琪拉:  我们拆解一下需求,希望对于指定对象,这个对象上具有指定属性值或某些属性值时,我们做一些后置业务处理。这个是我们做的业务逻辑抽象,这个就是设计能力。

安琪拉:  我们列一下有几个变量:  对象不确定、提取的属性不确定、 提取属性的个数不确定、属性值不确定,最后是要做的后续业务处理逻辑不确定。怎么把模型做的足够通用呢?我们来设计一下。

鲁班:但是产品给我这个需求就是判断小兵对象的符文和防御属性值啊?

安琪拉:  如果只是按照产品的需求搞,以后有的改,所以索性一次把模型设计的通用。我们可以这么搞:

安琪拉:  我们抽象后可以把这个服务叫做定位服务,如上所示,我们希望无论是什么对象,可以判断对象的指定属性值和预期值是否一致。这里用反射获取到属性的get 方法,然后调用get 方法获取属性值,和预期值做比较,这里 getReadMethod 方法为了方便说明做了简化,很多情况没写进入,比如属性是boolean 类型,get方法前缀是is,比如是父类或接口的方法等等。

鲁班:这样写有什么好处呢?

安琪拉:  这样就把原来的只对Batman 对象的属性做判断做了一层抽象,这样以后类似的需求都可以满足了。我们来做一下对比:

鲁班:这二个方案都是判断 batman(小兵)身上带的 rune(符文)是不是红色,如果是红色,就开火。但是新方案用了反射,有什么优势吗?

安琪拉:  实际业务场景里面,规则往往比这个复杂很多,而且还会一直变化,怎么把方案做的通用性和可扩展性更新,同时性能损耗减少到最少使我们要考虑的问题。例如:产品经理跟你说,这次除了对batman(小兵)身上带的 rune(符文)一定是红色开火,条件还要加一条必须盔甲是防法术伤害的才开火,或者是二者满足其中一条就开火,除了batman(小兵)做判断,也要对野怪、对方英雄做属性值判断。

鲁班:你的意思是业务需求这么变,我用反射做了通用性功能,可以不需要重复写代码吗?

安琪拉:  对呀。到时候你可以抽更多时间来研究?的技能。

鲁班:用反射可以实现对不同对象做业务逻辑处理,我可以理解,但是你刚才说的那些条件之前的业务规则,比如同时满足,二者满足其一就可以怎么能做到复用呢?

安琪拉:   你可以建一个规则表,一个条件表,规则表中有规则的场景、规则关联的条件(可以多个),条件之前的关系。条件的关系你可以设计的灵活一些,支持四类:

  • simple  简单条件,满足一个属性值就符合

  • and   多个条件都要满足

  • or   多个条件满足其中一个

  • expression  表达式,如果上面都满足不了,你还可以支持自定义表达式,例如: (A & B) | (C &D)

以上面的需求举例,条件表存二条记录,分别是

  1. propertyKey="rune" ,  propertyValue= "red", conditionName=\"**\", conditionId=\"\"

  2. propertyKey="armor" ,  propertyValue= "magic-defend", conditionName=\"**\", conditionId=\"\"

规则定为:

  1. 条件组: 条件1,条件2  

  2. 条件关系: and (代表条件都要满足)

然后你的产品经理需求变更了,你只需要新增规则和条件,或者修改规则表的记录就可以了。

鲁班:既然都说到这个份上了,能给写段代码吗?干说不练假把式

安琪拉:  好的,如下图代码所示,这套带有规则的反射可以应付来自产品各种花样需求了。

鲁班:那规则表和条件表我都建在数据库吗?

安琪拉:  有配置中心,可以把表建在配置中心,本地做份缓存, 没有放在数据库也可以,做好一致性。

鲁班:并发量非常高的时候,反射不会影响程序的性能吗?听说反射很耗性能。

安琪拉:  反射的确对性能有损耗,但你知道反射为什么影响性能吗?性能主要损耗在哪里?

鲁班:不知道。

反射性能问题

安琪拉:  反射影响性能是因为运行时,程序需要动态解析的类型,例如Class.getDeclaredMethod 的时候方法方法的类型都是运行时检查,Java虚拟机也没办法优化,每次Method 执行都要从Class 类信息中加载,我们知道类的方法信息是放在单独的方法区的,对象在堆区,但是相比于反射带来的便利,如果不是高并发需要十分频繁的调用,反射的性能损耗可以忽略,并且反射性能损耗也有方法优化降低。

鲁班:怎么优化反射的性能损耗?

安琪拉:  例如,我们前面每次调 locateObject 时都需要查找Method,我们可以把第一次查找的 Method 缓存起来,下次就不需要再调Class.getDeclaredMethod了。我们可以看下Class.getDeclaredMethod 内部处理逻辑,是比较耗性能的。

下图是截取的一段源码:

鲁班:那如果我要用反射,这个性能问题需要我自己做缓存吗?

安琪拉:  其实已经有现成的框架想到了反射的性能问题,因此可以直接用就好了。

鲁班:啊。。。在哪里?

安琪拉:  就是大名鼎鼎的 springframework 的 BeanUtils,我们上面的那段自己实现的获取Method的代码可以改写成如下这样:

在BeanUtils中实现了Method 的缓存。

我们对反射做一个简单的性能测试, 对反射代码执行100万次,打印耗时, 同时看Cpu、堆内存和非堆内存占用情况:

性能指标截图如下:

CPU、内存反射和常规内存占用基本差别不到,100万次耗时多290ms左右,

100万次反射调用: 

299ms,多次执行,在上下浮动。因此使用Spring framework提供的BeanUtils 包,反射性能影响很少。

在阿里巴巴开发规约有一条

【强制】避免用Apache Beanutils进行属性的copy。
说明:Apache BeanUtils性能较差,可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier,注意均是浅拷贝。
反例:[性能提升300%:Apache的BeanUtils的坑]

Apache BeanUtils 在类似copyProperties 方法实现机制上和Spring BeanUtils 略有不同,Apache BeanUtils 拷贝机制做了各种转换和解析逻辑, 导致性能变差,大家使用的时候注意区分。

欢迎关注我的微信公众号【面试造火箭】来聊聊Java面试

添加我的微信进一步交流和学习

如果显示频繁,微信手动搜索sanwaiyihao添加即可

点亮在看转发是我持续更新的动力,对我真的很重要!

浏览 20
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报