什么是Java的反射機制
JAVA反射機制是在運行狀態(tài)中, 對于任意一個類, 都能夠知道這個類的所有屬性和方法; 對于任意一個對象, 都能夠調(diào)用它的任意一個方法和屬性; 這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機制.
主要作用有三:
運行時取得類的方法和字段的相關(guān)信息。
創(chuàng)建某個類的新實例(.newInstance())
取得字段引用直接獲取和設(shè)置對象字段,無論訪問修飾符是什么。
用處如下:
- 觀察或操作應用程序的運行時行為。
- 調(diào)試或測試程序,因為可以直接訪問方法、構(gòu)造函數(shù)和成員字段。
- 通過名字調(diào)用不知道的方法并使用該信息來創(chuàng)建對象和調(diào)用方法
反射的應用場景
我在車管項目中遇到了一個應用場景,請先看界面:

在這個界面中,我要通過輸入關(guān)鍵字來檢索已經(jīng)通過網(wǎng)絡請求加載好的列表數(shù)據(jù)中,有哪些數(shù)據(jù)項是符合我的過濾條件的。
這個需求感覺很簡單,只要遍歷存放數(shù)據(jù)的List<T>,然后每項都對比關(guān)鍵字即可,然后再將過濾后的數(shù)據(jù)存入一個新的List<T>中,返回給RecyclerView的Adapter進行數(shù)據(jù)刷新即可。
但是在做的過程中我發(fā)現(xiàn)了兩個問題:
1.第一個問題是我們的List<T>獲取的數(shù)據(jù)由于業(yè)務的不同,其泛型也是不同的,且泛型之間沒有關(guān)聯(lián)性;
2.每個泛型都是一個Bean,我們要過濾的是Bean中的屬性,這個也是不確定的;
但是項目中大量的用到了這個功能,幾乎每個列表項都會有本地過濾的功能,如果不將功能進行封裝,每次都寫硬編碼,項目的復用性太低了,且整體項目不易維護。
一開始我想通過泛型進行處理,但是鑒于上邊兩個問題,泛型是沒辦法靈活處理的,尤其是第二個問題,泛型沒有辦法處理,于是想來想去,想到了反射機制。
閑話少說,先看代碼:
public class ListFilter<T> {
/**
* 過濾ArrayList中的關(guān)鍵字數(shù)據(jù)
* @param models 網(wǎng)絡獲取到的數(shù)據(jù)列表
* @param query 過濾關(guān)鍵字
* @param propertyName 泛型的數(shù)據(jù)過濾項
* @return 返回過濾后的數(shù)據(jù)
*/
public ArrayList<T> filter(ArrayList<T> models, String query, String propertyName) {
ArrayList<T> filteredModelList = null;
//實例化這個類賦給o
try {
query = query.toLowerCase();
filteredModelList = new ArrayList<>();
for (T model : models) {
final String text = getClassInfo(model,model.getClass().getName(),propertyName).toLowerCase();
if (text.contains(query)) {
filteredModelList.add(model);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return filteredModelList;
}
/**
* 獲取類的屬性值
* @param obj 類實例
* @param classNameString 類名
* @param propertyNameString 獲取的屬性名
* @return 屬性值
*/
private String getClassInfo(Object obj,String classNameString,String propertyNameString) {
String returnString="";
try{
Class classInfo = Class.forName(classNameString);
if(!(classInfo.isInstance(obj))){
L.e("傳入的java實例與配置的java對象類型不符!");
return returnString;
}
Field field = classInfo.getDeclaredField(propertyNameString);
field.setAccessible(true);
returnString=field.get(obj).toString();
}catch(Exception e){
e.printStackTrace();
}
return returnString;
}
}
這段代碼其實就是getClassInfo方法為核心的獲取某個類的某個字段的字段值,這里涉及幾個知識點大家可以自行百度學習一下:
1.getCanonicalName(), getName(), getSimpleName()三個方法的不同?
簡答:
1、getCanonicalName() 是獲取所傳類從java語言規(guī)范定義的格式輸出。
2、getName() 是返回實體類型名稱
3、getSimpleName() 返回從源代碼中返回實例的名稱。
2.反射中獲取類中的私有屬性該如何操作?
簡答:
field.setAccessible(true);
public void setAccessible(boolean flag) throws SecurityException
將此對象的 accessible 標志設(shè)置為指示的布爾值。值為 true 則指示反射的對象在使用時應該取消 Java 語言訪問檢查。值為 false 則指示反射的對象應該實施 Java 語言訪問檢查。
實際上setAccessible是啟用和禁用訪問安全檢查的開關(guān),并不是為true就能訪問為false就不能訪問;
反射經(jīng)典應用場景
以上就是我的項目中,使用反射的一個具體的實例了,其實在Java中還有很多地方都用到了反射,這里舉兩個比較常見的例子:
- JDBC 的數(shù)據(jù)庫的連接
在JDBC 的操作中,如果要想進行數(shù)據(jù)庫的連接,則必須按照以上的幾步完成
- 通過Class.forName()加載數(shù)據(jù)庫的驅(qū)動程序 (通過反射加載,前提是引入相關(guān)了Jar包)
- 通過 DriverManager 類進行數(shù)據(jù)庫的連接,連接的時候要輸入數(shù)據(jù)庫的連接地址、用戶名、密碼
- 通過Connection 接口接收連接
public class ConnectionJDBC {
/**
* @param args
*/
//驅(qū)動程序就是之前在classpath中配置的JDBC的驅(qū)動程序的JAR 包中
public static final String DBDRIVER = "com.mysql.jdbc.Driver";
//連接地址是由各個數(shù)據(jù)庫生產(chǎn)商單獨提供的,所以需要單獨記住
public static final String DBURL = "jdbc:mysql://localhost:3306/test";
//連接數(shù)據(jù)庫的用戶名
public static final String DBUSER = "root";
//連接數(shù)據(jù)庫的密碼
public static final String DBPASS = "";
public static void main(String[] args) throws Exception {
Connection con = null; //表示數(shù)據(jù)庫的連接對象
Class.forName(DBDRIVER); //1、使用CLASS 類加載驅(qū)動程序 ,反射機制的體現(xiàn)
con = DriverManager.getConnection(DBURL,DBUSER,DBPASS); //2、連接數(shù)據(jù)庫
System.out.println(con);
con.close(); // 3、關(guān)閉數(shù)據(jù)庫
}
- Spring 框架的使用
在 Java的反射機制在做基礎(chǔ)框架的時候非常有用,行內(nèi)有一句這樣的老話:反射機制是Java框架的基石。一般應用層面很少用,不過這種東西,現(xiàn)在很多開源框架基本都已經(jīng)封裝好了,自己基本用不著寫。典型的除了hibernate之外,還有spring也用到很多反射機制。最經(jīng)典的就是xml的配置模式。
Spring 通過 XML 配置模式裝載 Bean 的過程:
- 將程序內(nèi)所有 XML 或 Properties 配置文件加載入內(nèi)存中
- Java類里面解析xml或properties里面的內(nèi)容,得到對應實體類的字節(jié)碼字符串以及相關(guān)的屬性信息
- 使用反射機制,根據(jù)這個字符串獲得某個類的Class實例
- 動態(tài)配置實例的屬性
Spring這樣做的好處是:
- 不用每一次都要在代碼里面去new或者做其他的事情
- 以后要改的話直接改配置文件,代碼維護起來就很方便了
- 有時為了適應某些需求,Java類里面不一定能直接調(diào)用另外的方法,可以通過反射機制來實現(xiàn)
模擬 Spring 加載 XML 配置文件:
public class BeanFactory {
private Map<String, Object> beanMap = new HashMap<String, Object>();
/**
* bean工廠的初始化.
* @param xml xml配置文件
*/
public void init(String xml) {
try {
//讀取指定的配置文件
SAXReader reader = new SAXReader();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//從class目錄下獲取指定的xml文件
InputStream ins = classLoader.getResourceAsStream(xml);
Document doc = reader.read(ins);
Element root = doc.getRootElement();
Element foo;
//遍歷bean
for (Iterator i = root.elementIterator("bean"); i.hasNext();) {
foo = (Element) i.next();
//獲取bean的屬性id和class
Attribute id = foo.attribute("id");
Attribute cls = foo.attribute("class");
//利用Java反射機制,通過class的名稱獲取Class對象
Class bean = Class.forName(cls.getText());
//獲取對應class的信息
java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean);
//獲取其屬性描述
java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors();
//設(shè)置值的方法
Method mSet = null;
//創(chuàng)建一個對象
Object obj = bean.newInstance();
//遍歷該bean的property屬性
for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) {
Element foo2 = (Element) ite.next();
//獲取該property的name屬性
Attribute name = foo2.attribute("name");
String value = null;
//獲取該property的子元素value的值
for(Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();) {
Element node = (Element) ite1.next();
value = node.getText();
break;
}
for (int k = 0; k < pd.length; k++) {
if (pd[k].getName().equalsIgnoreCase(name.getText())) {
mSet = pd[k].getWriteMethod();
//利用Java的反射極致調(diào)用對象的某個set方法,并將值設(shè)置進去
mSet.invoke(obj, value);
}
}
}
//將對象放入beanMap中,其中key為id值,value為對象
beanMap.put(id.getText(), obj);
}
} catch (Exception e) {
System.out.println(e.toString());
}
}
//other codes
}