java 为什么使用反射?(附反射实例)

以前我总是想要理解反射,文章也写了一些,结果用得不多,只留下了“反射给静态语言提供了动态可能”这个初步印象。今天我想结合几个例子,把反射的概念梳理清楚,免得一知半解。

参考:https://www.zhihu.com/question/66525147

 

一、尝试理解反射

个人认为,要理解反射,不要先从概念入手,用一用就知道是怎么回事了。

要理解java反射,首先要把这几个鬼东西记住:

Java反射机制的实现就借助于这4个类:Class,Constructor,Field,Method。实际上,我们已经能够隐隐约约地看出,这四个对象就是一个类的各个组成部分

现在看看如下代码:

Test.java

(为了美观,我的catch都写到一起了,现实编码时千万别这样干)

记住这几个关键字后,你会发现反射就那么回事:只要拿到某个对象的类,就可以通过反射去操作某个实例对象。

就像上面那段程序一样,我们拿到了Class之后,先后获取了Class中的Method方法和Field属性,然后实例化了该Class的对象object,再对它的具体某个Method和Field做了些什么。最后,我们的注意力直接转向了“做了些什么”,只要打开代码提示看一看invoke和set,就知道这段代码的意思是“调用了setName方法”、“注入了name属性”。

了解了反射的使用方法后,再尝试理解反射的意义:

在Java运行时环境中,对于任意一个类,可以知道这个类有哪些属性和方法。对于任意一个对象,可以调用它的任意一个方法。

这种动态获取类的信息、动态调用对象的方法的功能,来自于Java 语言的反射机制。

有没有感觉,反射没有那么抽象了?

现在我们再理解一下这个概念:

反射为静态语言提供了动态的功能。

如何理解这段话呢?

首先我们要注意一点:这段代码中forName、getDeclaredField、get等等方法的参数,都是我们自己填入的。

如果把这些参数通过配置文件进行注入,就可以起到“不需要重新编译程序,就可以改变程序的功能”的效果(虽然不需要重新编译,但是需要重启应用。因为启动应用时,所有的配置已经在内存中加载完毕了,这时候修改配置文件毫无意义)。

个人认为,这就是“反射为静态程序加入动态功能”的含义。

二、反射的使用实例

 (1)Spring中的反射

Spring中大量使用反射,和配置文件结合在一起使用。

记得Spring中的类是怎么自动装配的吗?

一般存在两种情况:注解装配、配置文件装配(包括spring mvc项目中常见的xml,以及spring boot项目中常见的properties)。

如果是注解方式:当服务端启动时,Spring会去扫描指定目录下的类,通过反射看看类是否被@Service注解注释。如果类上有@Service注解,会提前初始化(new)这个类。初始化好所有类以后,Spring再去查找类中的所有属性,看属性有没有@Autowired注解。如果有,Spring会给这个属性注入值(反射赋值)。

如果是xml方式:原理上也差不多,只不过是先解析xml,拿到xml里的配置信息,再去初始化(new)或给属性反射赋值。

有了以上两种方式,我们写代码的时候就不用一个一个地手动new实现类,再给所有参数赋上值了。这部分工作都交给Spring完成。

我们尝试模拟一下注解自动装配的流程,先自定义一个Service注解(没有参数的注解):

Service.java

再实现自动初始化流程:

TestAutowired.java

这里的className是我们自己填的一个String。在Spring中,className可以来自xml、properties配置文件,也可以直接由Spring扫描路径得到。

(2)Junit中的反射

使用Junit时,需要写专门的测试类。需要注意的是,测试类中不能含有main方法,所有的测试方法(被@Test注释的方法被称为测试方法)都不能注入任何参数。我们随便找一个测试文件来看看:

PersInfoTest.java

你会发现,这个测试类中找不到main方法,测试方法testUpdatePers_info也没有参数。那么问题来了,如果测试类中没有main方法,测试方法又是怎样运行起来的呢?为什么testUpdatePers_info方法不能有参数,必须提前在方法中写好?

个人推测,Junit进行测试的原理是这样的(以maven打包时进行的测试为例):

使用maven打包时,不知在何处的main方法(我们不用管)自动扫描某个默认路径下的类(这也是为什么测试类要放在某个固定路径下的原因),使用反射加载所有类。初始化之后,使用反射运行所有带有@Test的测试方法(为什么测试方法不能带参数?就是因为测试时我们无法注入参数啊)。如果有方法测试不通过,输出错误信息就完事了。

个人估计,如果不使用反射,“maven打包时顺便把测试跑一遍”这种功能应该是无法实现的。我们可能要另外写一个main方法,自己进行测试,这该多麻烦啊。

(3)Json的转换

我用的Json包基本上就两个,最开始用的是Google出的Gson,后来写Spring写得多,转用Jackson。这些Json包都具备一个常见的功能,JavaBean自动转换为Json。

实际上,构造Json的原理就是字符串的拼接,重点就是获取JavaBean内部的属性。如何获取属性?当然还是用反射啦。

拼接Json写起来比较麻烦,我写了个JavaBean自动转Map,一个意思,就是转Map的操作变成拼接字符串而已(而已你个头)。

TestTurnToMap.java

运行结果为:

同样的,如果要实现Json转JavaBean的功能,在读取json的key后,也要通过反射注入到JavaBean相应的属性中(如果有set方法,直接set就可以。如果没有,就反射设置field)。

三、总结

什么时候使用反射?我看到了这样一个回答:

当你发现离开了反射就写不了代码的那天。

很多场景离开了反射,就不好(甚至无法)实现。如果某流程存在特定规律,正确使用反射往往可以起到“偷懒”的效果。

发表评论

电子邮件地址不会被公开。 必填项已用*标注