您現在的位置是:首頁 > 農業
理解Java反射的正確姿勢
反射類為什麼慢
反射簡介
反射是Java的高階特性之一,但是在實際的開發中,使用Java反射的案例卻非常的少,但是反射確實在底層框架中被頻繁的使用。
比如:
JDBC中的載入資料庫驅動程式,Spring框架中載入bean物件,以及態代理
,這些都使用到反射,因為我們要想理解一些框架的底層原理,反射是我們必須要掌握的。
理解反射我們先從他的概念入手,那麼什麼是反射呢?
反射就是在
執行狀態能夠動態的獲取該類的屬性和方法,並且能夠任意的使用該類的屬性和方法
,這種動態獲取類資訊以及動態的呼叫物件的方法的功能就是反射。
實現上面操作的前提是能夠獲取到該類的位元組碼物件,也就是。class檔案,在反射中獲取class檔案的方式有三種:
類名.class
如:Person。class
物件.class
如:person。class
Class.forName(全類名)獲取
如:Class。forName(“ldc。org。demo。person”)
Class物件
對於反射的執行過程的原理,我這裡畫了一張圖,以供大家參考理解。
我們看過JVM的相關書籍都會詳細的瞭解到,Java檔案首先要透過編譯器編譯,編譯成Class檔案,然後透過類載入器(ClassLoader)將class檔案載入到JVM中。
在JVM中Class檔案都與一個Class物件對應,在因為Class物件中包含著該類的類資訊,只要獲取到Class物件便可以操作該類物件的屬性與方法。
在這裡深入理解反射之前先來深入的理解Class物件,它包含了類的相關資訊。
Java中我們在執行時識別物件和類的資訊,也叫做RTTI,方式主要有來兩種:
傳統的RTTI(Run-Time Type Information)
反射機制
那麼什麼是RTTI呢?RTTI稱為執行時型別識別,傳統的RTTI是在編譯時就已經知道所有型別;而反射機制則是在程式執行時才確定的型別資訊。
想要執行時使用型別資訊,就必須要獲取Class物件的引用,獲取Class物件的方式上面已經提及。
這裡有點區別的就是
使用(.class)方式獲取Class物件,並不會初始化Class物件,而使用(forName("全類名"))的方式會自動初始化Class物件
。
當一個。class檔案要被載入到JVM中的時候,會進行如下的準備工作,首先會檢查這個類是否被載入,若是沒有被載入就會根據全類名找到class檔案,接著載入Class檔案,並建立類的靜態成員引用。
但是在
程式中並非是一開始就完全載入該類的class檔案,而是在程式用的地方再載入,即為懶載入模式
。
當載入完Class檔案後,接著就會驗證Class檔案中的位元組碼,並靜態域分配儲存空間。這個過程也叫做
連結
。
最後一步就是進行
初始化
,即為了使用類而提前做的準備工作如下圖所示:
反射
反射對應到Java中的類庫就是在java。lang。reflect下,在該包下包含著Field、Method和Constructor類。
Field是表示一個類的屬性資訊,Method表示類的方法資訊,Constructor表示的是類的構造方法的資訊。
在反射中常用的方法,我這裡做了一個列舉,當然更加詳細的可以查官方的API文件進行學習。
方法名作用getConstructors()獲取公共構造器getDeclaredConstructors()獲取所有構造器newInstance()獲取該類物件getName()獲取類名包含包路徑getSimpleName()獲取類名不包含包路徑getFields()獲取類公共型別的所有屬性getDeclaredFields()獲取類的所有屬性getField(String name)獲取類公共型別的指定屬性getDeclaredField(String name)獲取類全部型別的指定屬性getMethods()獲取類公共型別的方法getDeclaredMethods()獲取類的所有方法getMethod(String name, Class[] parameterTypes)獲得類的特定公共型別方法getDeclaredClasses()獲取內部類getDeclaringClass()獲取外部類getPackage()獲取所在包
另外對於反射的使用這裡附上一段小demo,具體的實際應用,會在後面繼續說到,並且也會附上程式碼的實現:
public class User{ private String name; private Integer age; public User() { } public User(String name, Integer age) { this。name = name; this。age = age; } private void privateMethod(){ System。err。println(“privateMethod”); } public void publicMethod(String param){ System。err。println(“publicMethod”+param); } @Override public String toString() { return “User{” + “name=‘” + name + ’\‘’ + “, age=” + age + ‘}’; }}
在User的實體類中,有兩個屬性age和name,並且除了有兩個測試方法privateMethod和publicMethod用於測試私有方法和公共方法的獲取。接著執行如下程式碼:
Class clazz=User。class;//獲取有參構造Constructor constructor = clazz。getConstructor(String。class, Integer。class);//獲取該類物件並設定屬性的值Object obj = constructor。newInstance(“黎杜”, 18);//獲得類全類名,既包含包路徑String fullClassName = clazz。getName();//獲得類名String className = clazz。getSimpleName();//獲得類中公共型別(public)屬性Field[] fields = clazz。getFields();String fieldName=“”;for(Field field : fields){ // 獲取屬性名 fieldName=field。getName(); System。out。println(fieldName)}//獲得類中全部型別屬性(包括private)Field[] fieldsAll = clazz。getDeclaredFields();fieldName=“”;for(Field field : fieldsAll){ // 獲取屬性名 fieldName=field。getName(); System。out。println(fieldName)}//獲得指定公共屬性值Field age = clazz。getField(“age”);Object value = age。get(obj);System。err。println(“公共指定屬性:”+value);//獲得指定的私有屬性值Field name = clazz。getDeclaredField(“name”);//設定為true才能獲取私有屬性name。setAccessible(true);Object value2= name。get(obj);System。err。println(“私有指定屬性值:”+value2);//獲取所有公共型別方法 這裡包括 Object 類的一些方法Method[] methods = clazz。getMethods();String methodsName=“”;for(Method method : methods){ methodsName=method。getName();}//獲取該類中的所有方法(包括private)Method[] methodsAll = clazz。getDeclaredMethods();methodsName=“”;for(Method method : methodsAll){ methodsName=method。getName();}//獲取並使用指定方法Method privateMethod= clazz。getDeclaredMethod(“privateMethod”);//獲取無參私有方法privateMethod。setAccessible(true);privateMethod。invoke(obj);//呼叫方法Method publicMethod= clazz。getMethod(“publicMethod”,String。class);//獲取有引數方法publicMethod。invoke(obj,“黎杜”);//呼叫有參方法
看完上面的demo以後,有些人會說,老哥這只是一個很簡單的demo,確實是,這裡為了照顧一下一些新手,先熟悉一下反射的一些方法的用法,好戲還在後頭。
反射在jdk 1。5的時候允許對Class物件能夠支援泛型,也稱為泛化Class,具體的使用如下:
Class
泛化實現了在
獲取例項的時候直接就可以獲取到具體的物件
,因為在編譯器的時候就會做型別檢查。當然也可以使用萬用字元的方式,例如:Class<?>
反射實際應用
經過上面的反射的原理介紹,下面就要開始反射的實際場景的應用,所有的技術,你知道的該技術的應用場景永遠是最值錢。這個是越多越好,知道的場景越多思路就越多。
反射的實際場景的應用,這裡主要列舉這幾個方面:
動態代理
JDBC 的資料庫的連線
Spring 框架的使用
動態代理實際就是使用反射的技術來實現,在程式執行時建立一個代理類,用來代理給定的介面,實現動態處理對其所代理的方法的呼叫。
實現動態代理主要有以下幾個步驟:
實現InvocationHandler介面,重寫invoke方法,實現被代理物件的方法呼叫的邏輯。
Proxy。getProxyClass獲取代理類
執行方法,代理成功
動態代理的實現程式碼如下所示,首先建立自己類DynamicProxyHandler實現 InvocationHandler :
public class DynamicProxyHandler implements InvocationHandler { private Object targetObj; public DynamicProxyHandler() { super(); } public DynamicProxyHandler(Object targetObj) { super(); this。targetObj= targetObj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System。err。println(“開始執行targetObj的方法”); //執行被代理的targetObj的方法 method。invoke(targetObj, args); System。out。println(“執行方法結束”); return null; }}
然後執行Proxy。newProxyInstance方法建立代理物件,最後執行代理物件的方法,程式碼實現如下:
User user = new UserImpl();DynamicProxyHandler dynamicProxy = new DynamicProxyHandler(user);//第一個引數:類載入器;第二個引數:user。getClass()。getInterfaces():被代理物件的介面;第三個引數:代理物件User userProxy = (User ) Proxy。newProxyInstance(user。getClass()。getClassLoader(), user。getClass()。getInterfaces(), dynamicProxy);userProxy。login();userProxy。logout();
以上的實現是jdk的動態代理方式,還有一種動態代理是Cglib的動態代理方式,Cglib動態代理也是被廣泛的使用,比如Spring AOP框架中,
實現了方法的攔截功能
。
在ORM框架Hibernate框架也是使用Cglib框架來代理單端single-ended的關聯關係。
jdk的動態代理與Cglib的動態代理的區別在於
jdk動態代理必須實現介面,而Cglib的動態代理是對那些沒有實現介面的類
,實現的原理是透過繼承稱為子類,並覆蓋父類中的一些方法。
對於Cglib的動態代理這裡由於篇幅的原因不再做詳細講解,下一篇將會詳細的講解jdk的動態代理和Cglib的動態代理的實現。
下面我們來看看JDBC中反射的應用案例,在JDBC中使用Class。forName()方法來載入資料庫驅動,就是使用反射的案例。
讓我們來一波入門的時候寫的程式碼,一波回憶殺歷歷在目,具體的實現程式碼我相信也是很多人在初學者的時候也寫過,如下所示:
Class。forName(“com。mysql。jdbc。Driver”); //1、使用CLASS 類載入驅動程式 ,反射機制的體現 con = DriverManager。getConnection(“jdbc:mysql://127。0。0。1:3306/test”,“root”,“root”); //2、連線資料庫
最後一個案例實現是使用反射模擬Spring透過xml檔案初始化Bean的過程,學過ssm的專案都會依稀的記得Spring的配置檔案,比如:
上面的配置檔案非常的熟悉,在標籤裡面有屬性,屬性有屬性值,以及標籤還有子標籤,子標籤也有屬性和屬性值,那麼怎麼用他們初始化成Bean呢?
思路可以是這樣的,首先得得到配置檔案的位置,然後載入配置檔案,載入配置檔案後就可以解析具體的標籤,獲取到屬性和屬性值,透過屬性值初始化Bean。
實現的程式碼如下,首先載入配置檔案的內容,並獲取到配置檔案的根節點:
SAXReader reader = new SAXReader();ClassLoader classLoader = Thread。currentThread()。getContextClassLoader();InputStream is= classLoader。getResourceAsStream(beanXml);Document doc = reader。read(is);Element root = doc。getRootElement();
拿到根節點後,然後可以獲取bean標籤中的屬性和屬性值,當拿到屬性class屬性值後就可以透過反射初始化Bean物件。
for (Iterator i = root。elementIterator(“bean”); i。hasNext();) { Element foo = (Element) i。next(); //獲取Bean中的屬性值 Attribute idValue = foo。attribute(“id”); Attribute clazzValue = foo。attribute(“class”); //透過反射獲取Class物件 Class bean = Class。forName(clazzValue。getText()); //並例項化Bean物件 Object obj = bean。newInstance(); }
除了初始化物件你還可以為Bean物件賦予初始值,例如上面的bean標籤下還有property標籤,以及它的屬性值value:
我們就可以透過以下程式碼來初始化這些值:
BeanInfo beanInfo = Introspector。getBeanInfo(bean);// bean物件的屬性資訊PropertyDescriptor propertyDescriptor[] = beanInfo 。getPropertyDescriptors();for (Iterator ite = foo。elementIterator(“property”); ite。hasNext();) { Element property= (Element) ite。next(); Attribute name = property。attribute(“name”); Attribute value = property。attribute(“value”); for (int i= 0; k < propertyDescriptor。length; i++) { if (propertyDescriptor[i]。getName()。equalsIgnoreCase(name。getText())) { Method method= propertyDescriptor[i]。getWriteMethod(); //使用反射將值設定進去 method。invoke(obj, value。getText()); } }
以上就是簡單的三個反射的應用案例,也是比較簡單,大佬不喜勿噴哈,初學者就當是自己學多一點知識,總之一點一點進步。
反射優點和缺點
優點
:反射可以動態的獲取物件,呼叫物件的方法和屬性,並不是寫死的,比較靈活,比如你要例項化一個bean物件,你可能會使用new User()寫死在程式碼中。
但是使用反射就可以使用class。forName(user)。newInstance(),而變數名user可以寫在xml配置檔案中,這樣就不用修改原始碼,
靈活、可配置
。
缺點
:反射的效能問題一直是被吐槽的地方,反射是一種解釋操作,用於屬性欄位和方法的接入時要遠遠慢於直接使用程式碼,因此普通程式也很少使用反射。
推薦文章
- 我不是果粉,但。。。 篇一:買晚了的工具- iPhone 13 mini
再接下來就是上車後利用手機做熱點為電腦提供無線上網訊號,方便偶爾在電腦上處理一些手機上不方便完成的工作,當然這是短暫的,大部分時間還是會有手機看一部電影或是脫口秀等,由於列車高速執行的緣故,手機基本出了螢幕常亮、連線藍芽外還需要高功率進行數...
- 美參與襲擊伊朗彈藥庫,法國呼籲退出北約,俄羅斯獲得喘息之機
法國政客呼籲退出北約隨著俄烏衝突局勢的不斷擴大,北約軍事委員會主席鮑爾出面對俄喊話,稱北約已經做好與俄羅斯進行正面衝突的準備,假如俄羅斯越過紅線,北約將展開反擊...
- 9月信用債交易覆盤:城投短久期成交上升
二、城投債高於估值和短久期成交增加,貴州城投和部分“網紅”主體估值有所修復從城投債交易區域來看,9月份有成交債券超過1000只的省份共有11個,與8月份持平...