作者:Seven_Nee
來源:https://segmentfault.com/a/1190000010162647
近期在維護公司專案的時候遇到一個問題,因為物體類中的 set 方法涉及到了業務邏輯,因此在給物件賦值的程序中不能夠使用 set 方法,為了實作功能,所以采用了反射的機制給物件屬性賦值,借此機會也了解了反射的一些具體用法和使用場景,分以下兩點對反射進行分析:
- 反射的優勢和劣勢
- 反射的應用場景
反射的優勢和劣勢
個人理解,反射機制實際上就是上帝模式,如果說方法的呼叫是 Java 正確的打開方式,那反射機制就是上帝偷偷開的后門,只要存在對應的class,一切都能夠被呼叫,
那上帝為什么要打開這個后門呢?
這涉及到了靜態和動態的概念:
- 靜態編譯:在編譯時確定型別,系結物件
- 動態編譯:運行時確定型別,系結物件
兩者的區別在于,動態編譯可以最大程度地支持多型,而多型最大的意義在于降低類的耦合性,因此反射的優點就很明顯了:解耦以及提高代碼的靈活性,
因此,反射的優勢和劣勢分別在于:
優勢
運行期型別的判斷,動態類加載:提高代碼靈活度
劣勢
性能瓶頸:反射相當于一系列解釋操作,通知 JVM 要做的事情,性能比直接的java代碼要慢很多
反射的應用場景
在我們平時的專案開發程序中,基本上很少會直接使用到反射機制,但這不能說明反射機制沒有用,實際上有很多設計、開發都與反射機制有關,例如模塊化的開發,通過反射去呼叫對應的位元組碼,
動態代理設計模式也采用了反射機制,還有我們日常使用的 Spring/Hibernate 等框架,也是利用CGLIB 反射機制才得以實作,下面就舉例最常見的兩個例子,來說明反射機制的強大之處:
JDBC 的資料庫的連接
在JDBC 的操作中,如果要想進行資料庫的連接,則必須按照以上的幾步完成
- 通過Class.forName()加載資料庫的驅動程式 (通過反射加載,前提是引入相關了Jar包)
- 通過 DriverManager 類進行資料庫的連接,連接的時候要輸入資料庫的連接地址、用戶名、密碼
- 通過Connection 介面接收連接
public class ConnectionJDBC {
/**
* @param args
*/
//驅動程式就是之前在classpath中配置的JDBC的驅動程式的JAR 包中
public static final String DBDRIVER = "com.mysql.jdbc.Driver";
//連接地址是由各個資料庫生產商單獨提供的,所以需要單獨記住
public static final String DBURL = "jdbc:mysql://localhost:3306/test";
//連接資料庫的用戶名
public static final String DBUSER = "root";
//連接資料庫的密碼
public static final String DBPASS = "";
public static void main(String[] args) throws Exception {
Connection con = null; //表示資料庫的連接物件
Class.forName(DBDRIVER); //1、使用CLASS 類加載驅動程式 ,反射機制的體現
con = DriverManager.getConnection(DBURL,DBUSER,DBPASS); //2、連接資料庫
System.out.println(con);
con.close(); // 3、關閉資料庫
}
Spring 框架的使用
在 Java的反射機制在做基礎框架的時候非常有用,行內有一句這樣的老話:反射機制是Java框架的基石,一般應用層面很少用,不過這種東西,現在很多開源框架基本都已經封裝好了,自己基本用不著寫,
典型的除了hibernate之外,還有spring也用到很多反射機制,最經典的就是xml的配置模式,
Spring 通過 XML 配置模式裝載 Bean 的程序:
- 將程式內所有 XML 或 Properties 組態檔加載入記憶體中
- Java類里面決議xml或properties里面的內容,得到對應物體類的位元組碼字串以及相關的屬性資訊
- 使用反射機制,根據這個字串獲得某個類的Class實體
- 動態配置實體的屬性
Spring這樣做的好處是:
- 不用每一次都要在代碼里面去new或者做其他的事情
- 以后要改的話直接改組態檔,代碼維護起來就很方便了
- 有時為了適應某些需求,Java類里面不一定能直接呼叫另外的方法,可以通過反射機制來實作
模擬 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();
//設定值的方法
Method mSet = null;
//創建一個物件
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 = https://www.cnblogs.com/javastack/p/null;
//獲取該property的子元素value的值
for(Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();) {
Element node = (Element) ite1.next();
value = https://www.cnblogs.com/javastack/p/node.getText();
break;
}
for (int k = 0; k < pd.length; k++) {
if (pd[k].getName().equalsIgnoreCase(name.getText())) {
mSet = pd[k].getWriteMethod();
//利用Java的反射極致呼叫物件的某個set方法,并將值設定進去
mSet.invoke(obj, value);
}
}
}
//將物件放入beanMap中,其中key為id值,value為物件
beanMap.put(id.getText(), obj);
}
} catch (Exception e) {
System.out.println(e.toString());
}
}
//other codes
}
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.別在再滿屏的 if/ else 了,試試策略模式,真香!!
3.臥槽!Java 中的 xx ≠ null 是什么新語法?
4.Spring Boot 2.5 重磅發布,黑暗模式太炸了!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/338902.html
標籤:Java
上一篇:面試官:Java 有執行緒安全的 set 嗎?我竟然答不上來。。
下一篇:redis集群詳解
