今天來聊聊java中的反射機制,工作以後發現很多東西動不動就要使用反射或者動态代理,如果不能很好的了解反射,那麼對于動态代理等一些重要的設計模式就會有種不夠通透的感覺。
所謂的反射,就是在運作狀态中,對于任意一個類,都能夠擷取到這個類的所有屬性和方法,對于任意一個對象,都能夠調用它的任意一個方法和屬性(包括私有的方法和屬性),這種動态擷取的資訊以及動态調用對象的方法的功能就稱為java語言的反射機制。通俗點講,通過反射,該類對我們來說是完全透明的,想要擷取任何東西都可以-----很強大。
想要使用反射機制,就必須要先擷取到該類的位元組碼檔案對象(.class),通過位元組碼檔案對象,就能夠通過該類中的方法擷取到我們想要的所有資訊(方法,屬性,類名,父類名,實作的所有接口等等),每一個類對應着一個位元組碼檔案也就對應着一個Class類型的對象,也就是位元組碼檔案對象。
擷取位元組碼檔案對象的三種方式。
1、Class clazz1 = Class.forName("全限定類名"); //通過Class類中的靜态方法forName,直接擷取到一個類的位元組碼檔案對象,此時該類還是源檔案階段,并沒有變為位元組碼檔案。
2、Class clazz2 = Person.class; //當類被加載成.class檔案時,此時Person類變成了.class,在擷取該位元組碼檔案對象,也就是擷取自己, 該類處于位元組碼階段。
3、Person p = new Preson();
Class clazz3 = p.getClass(); //通過類的執行個體擷取該類的位元組碼檔案對象,該類處于建立對象階段
有了位元組碼檔案對象才能獲得類中所有的資訊,我們在使用反射擷取資訊時,也要考慮使用上面哪種方式擷取位元組碼對象合理,視不同情況而定。下面介紹Class類的功能
反射機制能夠獲得的資訊:
1.1通過位元組碼對象建立執行個體對象:
//如果user類還沒有加載,那麼可以在源檔案階段擷取其位元組碼檔案對象
Class clazz1 = Class.formName("Reflect.User");
//建立User執行個體,這裡通過User的無參構造方法來建立對象
User user = (User)clazz1.newInstance();
//然後通過user對象就可以擷取我們想要的資訊
1.2擷取指定構造器方法,constructor沒有無參構造方法:
//擷取位元組碼檔案
Class clazz1 = Class.forName("Reflect.User");
//先擷取有參構造器,parameterType:表示參數清單,有多少就要寫多少,如果不寫,預設調用無參構造方法
Construnctor con = clazz1.getConstructor(int.class,String.Class);
//通過構造器來執行個體化對象,将實際的參數傳進去
User user = (User)con.newInstance(12,"小明");
總結上面建立執行個體對象:該類無參的構造方法來是使用該Class類的newInstance()方法來建立對象的, 如果一個類沒有無參的構造函數, 就不能這樣建立了。
這時候可以使用getConstructor(String.class,int.class)方法擷取一個指定的構造函數然後再調用Constructor類的newInstance("張三",20)方法建立對象擷取全部構造方法。
Class clazz1 = Class.forName("Reflect.User");
//擷取所有的構造方法
Constructor[] con = clazz1.getConstructors();
//for循環周遊
for(int i=0;i<con.length;i++){
//擷取每個構造函數中的參數類型位元組碼對象
Class[] parameterTypes = con[i].getParameterTypes();
syso("第"+i+"個構造函數");
for(int j =0;j<paraparameterTypes.length;j++){
//擷取構造函數中的參數類型
syso(paraparameterTypes[j].getName()+",");
}
}
1.3擷取成員變量并使用---Filed對象
擷取指定成員變量
Class.getField(String)方法可以擷取類中的指定字段(可見的), 如果是私有的可以使用getDeclaedField("name")方法擷取,通過set(obj, "李四")方法可以設定指定對象上該字段的值, 如果是私有的需要先調用setAccessible(true)設定通路權限,用擷取的指定的字段調用get(obj)可以擷取指定對象中該字段的值。
1.4獲得方法并使用 Method
Class.getMethod(String, Class...) 和 Class.getDeclaredMethod(String, Class...)方法可以擷取類中的指定方法,如果為私有方法,則需要打開一個權限。setAccessible(true);用invoke(Object, Object...)可以調用該方法,跟上面同理,也能一次性獲得所有的方法:
1.5、獲得該類的所有接口
Class[] getInterfaces():确定此對象所表示的類或接口實作的接口
傳回值:接口的位元組碼檔案對象的數組
1.6、擷取指定資源的輸入流
InputStream getResourceAsStream(String name)
return:一個 InputStream 對象;如果找不到帶有該名稱的資源,則傳回 null
參數:所需資源的名稱,如果以"/"開始,則絕對資源名為"/"後面的一部分。
上面講了一些反射的概念和方法,但是在我們開發中,經常會出現這樣的情況,就是随着業務邏輯的不斷增多,每寫一個功能時,就需要寫一個對應的servlet,最後就會導緻Servlet異常的臃腫。下面來說說如何在servlet中使用反射對其進行優化:
每次從頁面傳過來一個參數,method="xxx"; 然後編寫一個Servlet,獲得其參數method的值,進行判斷,如果是add,則調用add方法,如果是delete,則調用delete方法,這樣就可以寫在一個servlet中實作所有的功能了。
編寫一個BaseServlet繼承HttpServlet,這是一個通用的BaseServlet,這裡需要我們明白servlet的生命周期,也就是service方法,因為是servlet,是以在通路的時候,會經過service方法,而子類MyServlet001中并沒有,是以就到父類BaseServlet中找,發現有,然後擷取參數即知道了需要調用什麼方法,因為方法的編寫都在子類中,是以通過反射,擷取到子類中對應的方法并運作,其中需要注意的是this這個參數在BaseServlet中的用法,需要了解它,才能了解我們這個程式。
編寫具體實作的方法servlet類。MySerlvet001 extends BaseServlet