反射簡(jiǎn)介
反射是Java的高級(jí)特性之一,但是在實(shí)際的開(kāi)發(fā)中,使用Java反射的案例卻非常的少酵幕,但是反射確實(shí)在底層框架中被頻繁的使用。
比如:JDBC中的加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序缓苛,Spring框架中加載bean對(duì)象芳撒,動(dòng)以及態(tài)代理,這些都使用到反射未桥,因?yàn)槲覀円肜斫庖恍┛蚣艿牡讓釉肀噬玻瓷涫俏覀儽仨氁莆盏摹?/p>
理解反射我們先從他的概念入手,那么什么是反射呢冬耿?
反射就是在運(yùn)行狀態(tài)能夠動(dòng)態(tài)的獲取該類(lèi)的屬性和方法舌菜,并且能夠任意的使用該類(lèi)的屬性和方法,這種動(dòng)態(tài)獲取類(lèi)信息以及動(dòng)態(tài)的調(diào)用對(duì)象的方法的功能就是反射亦镶。
實(shí)現(xiàn)上面操作的前提是能夠獲取到該類(lèi)的字節(jié)碼對(duì)象日月,也就是.class文件袱瓮,在反射中獲取class文件的方式有三種:
類(lèi)名.class如:Person.class
對(duì)象.getClass如:person.getClass
Class.forName(全類(lèi)名)獲取如:Class.forName("ldc.org.demo.person")
Class對(duì)象
對(duì)于反射的執(zhí)行過(guò)程的原理,我這里畫(huà)了一張圖爱咬,以供大家參考理解尺借。
我們看過(guò)JVM的相關(guān)書(shū)籍都會(huì)詳細(xì)的了解到,Java文件首先要通過(guò)編譯器編譯精拟,編譯成Class文件燎斩,然后通過(guò)類(lèi)加載器(ClassLoader)將class文件加載到JVM中。
在JVM中Class文件都與一個(gè)Class對(duì)象對(duì)應(yīng)蜂绎,在因?yàn)镃lass對(duì)象中包含著該類(lèi)的類(lèi)信息瘫里,只要獲取到Class對(duì)象便可以操作該類(lèi)對(duì)象的屬性與方法。
在這里深入理解反射之前先來(lái)深入的理解Class對(duì)象荡碾,它包含了類(lèi)的相關(guān)信息谨读。
Java中我們?cè)谶\(yùn)行時(shí)識(shí)別對(duì)象和類(lèi)的信息,也叫做RTTI坛吁,方式主要有來(lái)兩種:
傳統(tǒng)的RTTI(Run-Time Type Information)
反射機(jī)制
那么什么是RTTI呢劳殖?RTTI稱(chēng)為運(yùn)行時(shí)類(lèi)型識(shí)別,傳統(tǒng)的RTTI是在編譯時(shí)就已經(jīng)知道所有類(lèi)型拨脉;而反射機(jī)制則是在程序運(yùn)行時(shí)才確定的類(lèi)型信息哆姻。
想要運(yùn)行時(shí)使用類(lèi)型信息,就必須要獲取Class對(duì)象的引用玫膀,獲取Class對(duì)象的方式上面已經(jīng)提及矛缨。
這里有點(diǎn)區(qū)別的就是使用(.class)方式獲取Class對(duì)象,并不會(huì)初始化Class對(duì)象帖旨,而使用(forName("全類(lèi)名"))的方式會(huì)自動(dòng)初始化Class對(duì)象箕昭。
當(dāng)一個(gè).class文件要被加載到JVM中的時(shí)候,會(huì)進(jìn)行如下的準(zhǔn)備工作解阅,首先會(huì)檢查這個(gè)類(lèi)是否被加載落竹,若是沒(méi)有被加載就會(huì)根據(jù)全類(lèi)名找到class文件,接著加載Class文件货抄,并創(chuàng)建類(lèi)的靜態(tài)成員引用述召。
但是在程序中并非是一開(kāi)始就完全加載該類(lèi)的class文件,而是在程序用的地方再加載蟹地,即為懶加載模式积暖。
當(dāng)加載完Class文件后,接著就會(huì)驗(yàn)證Class文件中的字節(jié)碼怪与,并靜態(tài)域分配存儲(chǔ)空間夺刑。這個(gè)過(guò)程也叫做鏈接。
最后一步就是進(jìn)行初始化琼梆,即為了使用類(lèi)而提前做的準(zhǔn)備工作如下圖所示:
反射
反射對(duì)應(yīng)到Java中的類(lèi)庫(kù)就是在java.lang.reflect下性誉,? ? 在該包下包含著Field窿吩、Method和Constructor類(lèi)。
Field是表示一個(gè)類(lèi)的屬性信息错览,Method表示類(lèi)的方法信息纫雁,Constructor表示的是類(lèi)的構(gòu)造方法的信息。
在反射中常用的方法倾哺,我這里做了一個(gè)列舉轧邪,當(dāng)然更加詳細(xì)的可以查官方的API文檔進(jìn)行學(xué)習(xí)。
方法名作用
getConstructors()獲取公共構(gòu)造器
getDeclaredConstructors()獲取所有構(gòu)造器
newInstance()獲取該類(lèi)對(duì)象
getName()獲取類(lèi)名包含包路徑
getSimpleName()獲取類(lèi)名不包含包路徑
getFields()獲取類(lèi)公共類(lèi)型的所有屬性
getDeclaredFields()獲取類(lèi)的所有屬性
getField(String name)獲取類(lèi)公共類(lèi)型的指定屬性
getDeclaredField(String name)獲取類(lèi)全部類(lèi)型的指定屬性
getMethods()獲取類(lèi)公共類(lèi)型的方法
getDeclaredMethods()獲取類(lèi)的所有方法
getMethod(String name, Class[] parameterTypes)獲得類(lèi)的特定公共類(lèi)型方法
getDeclaredClasses()獲取內(nèi)部類(lèi)
getDeclaringClass()獲取外部類(lèi)
getPackage()獲取所在包
另外對(duì)于反射的使用這里附上一段小demo羞海,具體的實(shí)際應(yīng)用忌愚,會(huì)在后面繼續(xù)說(shuō)到,并且也會(huì)附上代碼的實(shí)現(xiàn):
publicclassUser{
privateString?name;
privateInteger?age;
publicUser(){
}
publicUser(String?name,?Integer?age){
this.name?=?name;
this.age?=?age;
}
privatevoidprivateMethod(){
System.err.println("privateMethod");
}
publicvoidpublicMethod(String?param){
System.err.println("publicMethod"+param);
}
@Override
publicStringtoString(){
return"User{"+
"name='"+?name?+'\''+
",?age="+?age?+
'}';
}
}
在User的實(shí)體類(lèi)中却邓,有兩個(gè)屬性age和name硕糊,并且除了有兩個(gè)測(cè)試方法privateMethod和publicMethod用于測(cè)試私有方法和公共方法的獲取。接著執(zhí)行如下代碼:
Class?clazz=User.class;
//獲取有參構(gòu)造
Constructor?constructor?=?clazz.getConstructor(String.class,?Integer.class);
//獲取該類(lèi)對(duì)象并設(shè)置屬性的值
Objectobj?=?constructor.newInstance("黎杜",18);
//獲得類(lèi)全類(lèi)名腊徙,既包含包路徑
StringfullClassName?=?clazz.getName();
//獲得類(lèi)名
StringclassName?=?clazz.getSimpleName();
//獲得類(lèi)中公共類(lèi)型(public)屬性
Field[]?fields?=?clazz.getFields();
StringfieldName="";
for(Field?field?:?fields){
//?獲取屬性名
fieldName=field.getName();
System.out.println(fieldName)
}
//獲得類(lèi)中全部類(lèi)型屬性(包括private)
Field[]?fieldsAll?=?clazz.getDeclaredFields();
fieldName="";
for(Field?field?:?fieldsAll){
//?獲取屬性名
fieldName=field.getName();
System.out.println(fieldName)
}
//獲得指定公共屬性值
Field?age?=?clazz.getField("age");
Objectvalue?=?age.get(obj);
System.err.println("公共指定屬性:?"+value);
//獲得指定的私有屬性值
Field?name?=?clazz.getDeclaredField("name");
//設(shè)置為true才能獲取私有屬性
name.setAccessible(true);
Objectvalue2=?name.get(obj);
System.err.println("私有指定屬性值:?"+value2);
//獲取所有公共類(lèi)型方法???這里包括?Object?類(lèi)的一些方法
Method[]?methods?=?clazz.getMethods();
StringmethodsName="";
for(Method?method?:?methods){
methodsName=method.getName();
}
//獲取該類(lèi)中的所有方法(包括private)
Method[]?methodsAll?=?clazz.getDeclaredMethods();
methodsName="";
for(Method?method?:?methodsAll){
methodsName=method.getName();
}
//獲取并使用指定方法
Method?privateMethod=?clazz.getDeclaredMethod("privateMethod");//獲取無(wú)參私有方法
privateMethod.setAccessible(true);
privateMethod.invoke(obj);//調(diào)用方法
Method?publicMethod=?clazz.getMethod("publicMethod",String.class);//獲取有參數(shù)方法
publicMethod.invoke(obj,"黎杜");//調(diào)用有參方法
看完上面的demo以后简十,有些人會(huì)說(shuō),老哥這只是一個(gè)很簡(jiǎn)單的demo撬腾,確實(shí)是螟蝙,這里為了照顧一下一些新手,先熟悉一下反射的一些方法的用法民傻,好戲還在后頭胰默。
反射在jdk 1.5的時(shí)候允許對(duì)Class對(duì)象能夠支持泛型,也稱(chēng)為泛化Class漓踢,具體的使用如下:
Class?user=?User.class;
//泛化class可以直接得到具體的對(duì)象牵署,而不再是Object
Useruser=?user.newInstance();
泛化實(shí)現(xiàn)了在獲取實(shí)例的時(shí)候直接就可以獲取到具體的對(duì)象,因?yàn)樵诰幾g器的時(shí)候就會(huì)做類(lèi)型檢查彭雾。當(dāng)然也可以使用通配符的方式碟刺,例如:Class<?>
反射實(shí)際應(yīng)用
經(jīng)過(guò)上面的反射的原理介紹锁保,下面就要開(kāi)始反射的實(shí)際場(chǎng)景的應(yīng)用薯酝,所有的技術(shù),你知道的該技術(shù)的應(yīng)用場(chǎng)景永遠(yuǎn)是最值錢(qián)爽柒。這個(gè)是越多越好吴菠,知道的場(chǎng)景越多思路就越多。
反射的實(shí)際場(chǎng)景的應(yīng)用浩村,這里主要列舉這幾個(gè)方面:
動(dòng)態(tài)代理
JDBC 的數(shù)據(jù)庫(kù)的連接
Spring 框架的使用
動(dòng)態(tài)代理實(shí)際就是使用反射的技術(shù)來(lái)實(shí)現(xiàn)做葵,在程序運(yùn)行時(shí)創(chuàng)建一個(gè)代理類(lèi),用來(lái)代理給定的接口心墅,實(shí)現(xiàn)動(dòng)態(tài)處理對(duì)其所代理的方法的調(diào)用酿矢。
實(shí)現(xiàn)動(dòng)態(tài)代理主要有以下幾個(gè)步驟:
實(shí)現(xiàn)InvocationHandler接口榨乎,重寫(xiě)invoke方法,實(shí)現(xiàn)被代理對(duì)象的方法調(diào)用的邏輯瘫筐。
Proxy.getProxyClass獲取代理類(lèi)
執(zhí)行方法蜜暑,代理成功
動(dòng)態(tài)代理的實(shí)現(xiàn)代碼如下所示,首先創(chuàng)建自己類(lèi)DynamicProxyHandler實(shí)現(xiàn)InvocationHandler:
publicclassDynamicProxyHandlerimplementsInvocationHandler{
privateObject?targetObj;
publicDynamicProxyHandler(){
super();
}
publicDynamicProxyHandler(Object?targetObj){
super();
this.targetObj=?targetObj;
}
@Override
publicObjectinvoke(Object?proxy,?Method?method,?Object[]?args)throwsThrowable{
System.err.println("開(kāi)始執(zhí)行targetObj的方法");
//執(zhí)行被代理的targetObj的方法
method.invoke(targetObj,?args);
System.out.println("執(zhí)行方法結(jié)束");
returnnull;
}
}
然后執(zhí)行Proxy.newProxyInstance方法創(chuàng)建代理對(duì)象策肝,最后執(zhí)行代理對(duì)象的方法肛捍,代碼實(shí)現(xiàn)如下:
User?user?=newUserImpl();
DynamicProxyHandler?dynamicProxy?=newDynamicProxyHandler(user);
//第一個(gè)參數(shù):類(lèi)加載器;第二個(gè)參數(shù):user.getClass().getInterfaces():被代理對(duì)象的接口之众;第三個(gè)參數(shù):代理對(duì)象
User?userProxy?=?(User?)Proxy.newProxyInstance(user.getClass().getClassLoader(),?user.getClass().getInterfaces(),?dynamicProxy);
userProxy.login();
userProxy.logout();
以上的實(shí)現(xiàn)是jdk的動(dòng)態(tài)代理方式拙毫,還有一種動(dòng)態(tài)代理是Cglib的動(dòng)態(tài)代理方式,Cglib動(dòng)態(tài)代理也是被廣泛的使用棺禾,比如Spring AOP框架中缀蹄,實(shí)現(xiàn)了方法的攔截功能。
在ORM框架Hibernate框架也是使用Cglib框架來(lái)代理單端single-ended的關(guān)聯(lián)關(guān)系膘婶。
jdk的動(dòng)態(tài)代理與Cglib的動(dòng)態(tài)代理的區(qū)別在于jdk動(dòng)態(tài)代理必須實(shí)現(xiàn)接口袍患,而Cglib的動(dòng)態(tài)代理是對(duì)那些沒(méi)有實(shí)現(xiàn)接口的類(lèi),實(shí)習(xí)那的原理通通過(guò)繼承稱(chēng)為子類(lèi)竣付,并覆蓋父類(lèi)中的一些方法诡延。
對(duì)于Cglib的動(dòng)態(tài)代理這里由于篇幅的原因不再做詳細(xì)講解,下一篇將會(huì)詳細(xì)的講解jdk的動(dòng)態(tài)代理和Cglib的動(dòng)態(tài)代理的實(shí)現(xiàn)古胆。
下面我們來(lái)看看JDBC中反射的應(yīng)用案例肆良,在JDBC中使用Class.forName()方法來(lái)加載數(shù)據(jù)庫(kù)驅(qū)動(dòng),就是使用反射的案例逸绎。
讓我們來(lái)一波入門(mén)的時(shí)候?qū)懙拇a惹恃,一波回憶殺歷歷在目,具體的實(shí)現(xiàn)代碼我相信也是很多人在初學(xué)者的時(shí)候也寫(xiě)過(guò)棺牧,如下所示:
Class.forName("com.mysql.jdbc.Driver");//1巫糙、使用CLASS?類(lèi)加載驅(qū)動(dòng)程序?,反射機(jī)制的體現(xiàn)?
con?=?DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test","root","root");//2、連接數(shù)據(jù)庫(kù)??
最后一個(gè)案例實(shí)現(xiàn)是使用反射模擬Spring通過(guò)xml文件初始化Bean的過(guò)程颊乘,學(xué)過(guò)ssm的項(xiàng)目都會(huì)依稀的記得Spring的配置文件参淹,比如:
上面的配置文件非常的熟悉,在標(biāo)簽里面有屬性乏悄,屬性有屬性值浙值,以及標(biāo)簽還有子標(biāo)簽,子標(biāo)簽也有屬性和屬性值檩小,那么怎么用他們初始化成Bean呢开呐?
思路可以是這樣的,首先得得到配置文件的位置,然后加載配置文件筐付,加載配置文件后就可以解析具體的標(biāo)簽卵惦,獲取到屬性和屬性值,通過(guò)屬性值初始化Bean瓦戚。
實(shí)現(xiàn)的代碼如下鸵荠,首先加載配置文件的內(nèi)容,并獲取到配置文件的根節(jié)點(diǎn):
SAXReaderreader?=?new?SAXReader();
ClassLoaderclassLoader?=?Thread.currentThread().getContextClassLoader();
InputStreamis=?classLoader.getResourceAsStream(beanXml);
Documentdoc?=?reader.read(is);
Elementroot?=?doc.getRootElement();
拿到根節(jié)點(diǎn)后伤极,然后可以獲取bean標(biāo)簽中的屬性和屬性值蛹找,當(dāng)拿到屬性class屬性值后就可以通過(guò)反射初始化Bean對(duì)象。
for(Iteratori?=?root.elementIterator("bean");?i.hasNext();)?{
Elementfoo?=?(Element)?i.next();
//獲取Bean中的屬性值
Attribute?idValue?=?foo.attribute("id");
Attribute?clazzValue?=?foo.attribute("class");
//通過(guò)反射獲取Class對(duì)象
Class?bean?=?Class.forName(clazzValue.getText());
//并實(shí)例化Bean對(duì)象
Objectobj?=?bean.newInstance();
}
除了初始化對(duì)象你還可以為Bean對(duì)象賦予初始值哨坪,例如上面的bean標(biāo)簽下還有property標(biāo)簽庸疾,以及它的屬性值value:
我們就可以通過(guò)一下代碼來(lái)初始化這些值:
BeanInfo?beanInfo?=?Introspector.getBeanInfo(bean);
//?bean對(duì)象的屬性信息
PropertyDescriptor?propertyDescriptor[]?=?beanInfo?.getPropertyDescriptors();
for(Iteratorite?=?foo.elementIterator("property");?ite.hasNext();)?{
Elementproperty=?(Element)?ite.next();
Attribute?name?=?property.attribute("name");
Attribute?value?=?property.attribute("value");
for(inti=0;?k?<?propertyDescriptor.length;?i++)?{
if(propertyDescriptor[i].getName().equalsIgnoreCase(name.getText()))?{
Method?method=?propertyDescriptor[i].getWriteMethod();
//使用反射將值設(shè)置進(jìn)去
method.invoke(obj,?value.getText());
}
}
以上就是簡(jiǎn)單的三個(gè)反射的應(yīng)用案例,也是比較簡(jiǎn)單当编,大佬不喜勿噴哈届慈,初學(xué)者就當(dāng)是自己學(xué)多一點(diǎn)知識(shí),總之一點(diǎn)一點(diǎn)進(jìn)步忿偷。
反射優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):反射可以動(dòng)態(tài)的獲取對(duì)象金顿,調(diào)用對(duì)象的方法和屬性,并不是寫(xiě)死的鲤桥,比較靈活揍拆,比如你要實(shí)例化一個(gè)bean對(duì)象,你可能會(huì)使用new User()寫(xiě)死在代碼中茶凳。
但是使用反射就可以使用class.forName(user).newInstance()嫂拴,而變量名user可以寫(xiě)在xml配置文件中,這樣就不用修改源代碼贮喧,靈活筒狠、可配置。
缺點(diǎn):反射的性能問(wèn)題一直是被吐槽的地方箱沦,反射是一種解釋操作辩恼,用于屬性字段和方法的接入時(shí)要遠(yuǎn)遠(yuǎn)慢于直接使用代碼,因此普通程序也很少使用反射谓形。