在Java中,想要運(yùn)行時(shí)操作對(duì)象的方法夭问、屬性泽西,其中有效的手段曹铃,就有反射缰趋。這也是框架最常使用的手段,因?yàn)榭蚣茉诰幾g時(shí)并不知曉系統(tǒng)中有哪些類會(huì)被使用陕见。
Java中對(duì)象的類型信息在運(yùn)行時(shí)由Class對(duì)象表示秘血,Class對(duì)象是伴隨類加載而實(shí)例化產(chǎn)生的,而反射的實(shí)現(xiàn)就圍繞著Class對(duì)象评甜。
有很多種方式可以獲取類的Class對(duì)象灰粮,包括:
Class<Object> c1 = Object.class;
Class<?> c2 = Class.forName("java.lang.Object");
Class<?> c3 = new Object().getClass();
反射可以做什么?
1. 實(shí)例化對(duì)象
對(duì)象的實(shí)例化忍坷,可以通過(guò)new關(guān)鍵字直接實(shí)例化粘舟,也可以通過(guò)反射,例如:
class.newInstance()
class.getConstructor(Class<?>... parameterTypes).newInstance(Object ... initargs)
為什么需要用反射來(lái)進(jìn)行對(duì)象實(shí)例化佩研?
有不能使用new關(guān)鍵字直接實(shí)例化對(duì)象的場(chǎng)景柑肴,例如:Spring容器管理的Bean,只能通過(guò)類的全限定名加載類旬薯,然后反射實(shí)例化襟交。
在不想使用new關(guān)鍵字的場(chǎng)景析既,目的是為了簡(jiǎn)化編程结耀,使代碼美觀凤藏,可能大家經(jīng)常看到類似的用法顷编,例如:
// 解析Json
public static <T> T parseObject(String text, Class<T> clazz) {
return parseObject(text, clazz, new Feature[0]);
}
/**
* 簡(jiǎn)單的復(fù)制出新類型對(duì)象.
*/
public static <S, D> D map(S source, Class<D> destinationClass) {
return mapper.map(source, destinationClass);
}
2. 篩選合適的類
實(shí)際開(kāi)發(fā)中,經(jīng)常會(huì)有這種需要,如果類有某某特征扬跋,就進(jìn)行某某操作。
在Spring進(jìn)行掃描的時(shí)候凌节,我們會(huì)通過(guò)過(guò)濾器胁住,來(lái)精細(xì)化控制bean的生成,包括:根據(jù)isInstance(Object obj)
判斷是否實(shí)現(xiàn)某某接口或者繼承特殊的父類刊咳;根據(jù)isAnnotationPresent(Class<? extends Annotation> annotationClass)
判斷是否被注解標(biāo)記彪见。
3. 方法調(diào)用
有些場(chǎng)景不能或者不合適直接調(diào)用方法,例如:我們處理HTTP請(qǐng)求娱挨,需要從URI映射到方法調(diào)用余指,如果我們能夠窮舉所有的URL到對(duì)象方法的映射關(guān)系,那么也沒(méi)問(wèn)題跷坝,但是無(wú)數(shù)的if條件判斷酵镜,顯然不是明智的選擇。通常我們先會(huì)獲取資源對(duì)象柴钻,然后反射調(diào)用對(duì)象的方法淮韭。
Method.invoke(Object obj, Object... args)
如何獲取對(duì)象的方法對(duì)象,也就是Method對(duì)象呢贴届?Class類提供了以下實(shí)現(xiàn):
Method[] getMethods();
Method[] getDeclaredMethods();
Method getMethod(String name, Class<?>... parameterTypes);
Method getDeclaredMethod(String name, Class<?>... parameterTypes);
這些方法可以分為兩類:
一靠粪、方法簽名中帶有Declared得,會(huì)在當(dāng)前類的所有方法中查找毫蚓,但不會(huì)遍歷父類占键。
二、不帶Declared的會(huì)遍歷所有父類元潘,但只會(huì)查找public方法畔乙。
推薦使用工具類:org.apache.commons.lang3.reflect.MethodUtils
此類中包含遍歷所有父類查找方法、當(dāng)前類查找public方法或者反射執(zhí)行方法的便捷操作翩概。
4. 屬性操作
Class.getFields(), Class.getField(String),
Class.getDeclaredFields(), Class.getDeclaredField(String)
命名規(guī)則同方法牲距,推薦使用工具類:org.apache.commons.lang3.FieldUtils
,進(jìn)行讀取或者賦值操作钥庇。
反射進(jìn)行賦值有幾點(diǎn)需要注意:
- 如果不為public類型牍鞠,那么設(shè)置字段前必須通過(guò)Field.setAccessible(true)方法進(jìn)行訪問(wèn)權(quán)限設(shè)置,不然會(huì)拋出異常:IllegalAccessException 上沐。
- 如果字段為static類型皮服,那么通過(guò)set方法進(jìn)行賦值時(shí),會(huì)忽略obj對(duì)象,因?yàn)殪o態(tài)字段屬于類龄广。
- 如果字段為final類型硫眯,不管是public還是 private,那么用set方法賦值時(shí)择同,只有setAccessible進(jìn)行訪問(wèn)權(quán)限設(shè)置后两入,才能正確調(diào)用,不然會(huì)報(bào)異常:IllegalAccessException敲才。但是對(duì)final字段進(jìn)行set方法賦值時(shí)裹纳,盡管方法正常調(diào)用,但是并不會(huì)改變fianl字段的值紧武。
- 如果字段為final static 類型剃氧,那么進(jìn)行set方法賦值時(shí),總會(huì)拋出IllegalAccessException異常阻星。
最后如果對(duì)當(dāng)前對(duì)象的所有方法朋鞍,或者所有字段進(jìn)行某種操作,那么推薦工具類:org.springframework.util.ReflectionUtils
妥箕。本人在RPC中動(dòng)態(tài)代理一文中有使用滥酥。