前些天更新的《陸敏技說Spring》一文姐浮,小伙伴們的反映非常好窖壕,有很多找我們的老師索要配套視頻的钠糊,時隔好幾天,小編今天接著上次的未完待續(xù)粘昨,給大家送干貨了哦~~ 結(jié)尾處有我們老師的聯(lián)系方式垢啼,可以索要配套視頻哦。
此文為原創(chuàng)张肾,禁止非法轉(zhuǎn)載膊夹。
正文:
1.3.反射的原理
我們講完了IOC和DI了,但是其實對反射還是接觸到了一點皮毛“坪疲現(xiàn)在放刨,讓我們深入的了解下反射。不過尸饺,要了解反射的原理进统,還得首先了解一些關(guān)于類型加載的基礎(chǔ)知識。
我們知道運行java代碼的是虛擬機jvm浪听。那么java代碼本身是怎么進(jìn)入到j(luò)vm并且被jvm所識別的呢螟碎。 代碼本身本身編譯之后變成class文件,class文件進(jìn)入jvm會被一種叫做類加載器的組件先處理一遍迹栓。被類加載器處理過后的類型掉分,會變成一些被jvm認(rèn)識的元數(shù)據(jù),它們包括:構(gòu)造函數(shù)克伊、屬性和方法等酥郭。 負(fù)責(zé)反射的那些類型也認(rèn)識這些元數(shù)據(jù),并可以動態(tài)修改或者操作這些元數(shù)據(jù)愿吹。即不从,對于java中的任意一個類,反射類都能知道和調(diào)用這個類的所有屬性和方法犁跪,包括私有屬性和方法(這就厲害了椿息,說好的封裝呢,說好的private不能被訪問坷衍,在反射類這里統(tǒng)統(tǒng)無效G抻拧)
除此之外,反射甚至允許我們動態(tài)生成類型枫耳,也即我們壓根在原來的代碼中沒有一個叫做User的類型乏矾,但是利用反射基礎(chǔ),卻能動態(tài)生成一個User類型,再通過類加載器加載到j(luò)vm中妻熊。
1.3.1. 類加載器
既然說到類加載器,那我們就先來看看類加載器仑最。類裝載器具體來說就是解析類的節(jié)碼文件并構(gòu)造出類在JVM內(nèi)部表現(xiàn)形式(元數(shù)據(jù))的組件扔役。類裝載器加載一個類型,經(jīng)歷了如下步驟:
1:裝載:查找和導(dǎo)入Class文件警医;
2:鏈接:執(zhí)行校驗亿胸、準(zhǔn)備和解析步驟,其中解析步驟是可以選擇的:
校驗:檢查載入Class文件數(shù)據(jù)的正確性预皇;
準(zhǔn)備:給類的靜態(tài)變量分配存儲空間侈玄;
解析:將符號引用轉(zhuǎn)成直接引用;
3:初始化:對類的靜態(tài)變量吟温、靜態(tài)代碼塊執(zhí)行初始化工作序仙。
類加載器默認(rèn)有三個,它們分別負(fù)責(zé)不同類型的java類的加載:
(1)Bootstrap ClassLoader 根類加載器
也被稱為引導(dǎo)類加載器鲁豪,負(fù)責(zé)Java核心類的加載比如System,String等在JDK中JRE的lib目錄下rt.jar文件中的那些類型潘悼。
(2)Extension ClassLoader 擴(kuò)展類加載器
負(fù)責(zé)JRE的擴(kuò)展目錄中jar包的加載。這些類型在JDK中JRE的lib目錄下ext目錄下爬橡。
(3)System ClassLoader 系統(tǒng)類加載器
負(fù)責(zé)在JVM啟動時加載來自java命令的class文件治唤,以及classpath環(huán)境變量所指定jar包和類路徑下那些類型。
這三個類裝載器之間存在父子層級關(guān)系糙申,即根裝載器是ExtClassLoader的父裝載器宾添,ExtClassLoader是AppClassLoader的父裝載器。
我們可以通過代碼得到類加載器的原型柜裸,如下:
public class ClassLoaderTest {?
public static void main(String[] args) {?
? ? ClassLoader loader = ClassLoaderTest.class.getClassLoader();?
? ? System.out.println("current loader:"+loader);?
? ? System.out.println("parent loader:"+loader.getParent());?
? ? System.out.println("top loader:"+loader.getParent(). getParent());? ? ? }?
}
結(jié)果:
current loader:sun.misc.Launcher$AppClassLoader@4e0e2f2a
parent loader:sun.misc.Launcher$ExtClassLoader@2a139a55
top loader:null
注意缕陕,根加載器在java中無法訪問,所以是null疙挺。
1.3.2. 反射能做什么
利用反射榄檬,我們主要可以做這些事情,
在運行時構(gòu)造任意一個類的對象衔统;
在運行時獲得任意一個類的信息鹿榜,包括所具有的成員變量和方法;
在運行時調(diào)用任意一個對象的方法锦爵;
生成動態(tài)代理舱殿。
構(gòu)造任意一個對象:
? ? Class class1 = null;
? ? Class class2 = null;
? ? Class class3 = null;
? ? // 獲取類型的三種方式,有了類型后就可以newInstance()了
? ? class1 = Class.forName("com.zuikc.dao.mysql.UserDaoImpl");
? ? class2 = new UserDaoImpl().getClass();
? ? class3 = UserDaoImpl.class;
? ? System.out.println("類名稱? " + class1.getName() + "類對象:" + class1.newInstance());
? ? System.out.println("類名稱? " + class2.getName() + "類對象:" + class2.newInstance());
? ? System.out.println("類名稱? " + class3.getName() + "類對象:" + class3.newInstance());
注意险掀,如果類有構(gòu)造方法沪袭,則可以這樣調(diào)用(這里,我們使用User類):
? ? Class class1 = Class.forName("com.zuikc.bean.User");
? ? // 第一種方法樟氢,不調(diào)用有參構(gòu)造器冈绊,所以直接newInstance
? ? User user = (User) class1.newInstance();
? ? user.setAge(20);
? ? user.setName("baobao");
? ? System.out.println(user);
? ? // 第二種方法侠鳄,先獲取全部的構(gòu)造器,看看它們有什么參數(shù)
? ? Constructor cons[] = class1.getConstructors();
? ? // 查看每個構(gòu)造方法需要的參數(shù)
? ? for (int i = 0; i < cons.length; i++) {
? ? ? ? Class clazzs[] = cons[i].getParameterTypes();
? ? ? ? System.out.print("cons[" + i + "] (");
? ? ? ? for (int j = 0; j < clazzs.length; j++) {
? ? ? ? ? ? if (j == clazzs.length - 1)
? ? ? ? ? ? ? ? System.out.print(clazzs[j].getName());
? ? ? ? ? ? else
? ? ? ? ? ? ? ? System.out.print(clazzs[j].getName() + ",");
? ? ? ? }
? ? ? ? System.out.println(")");
? ? }
? ? // 調(diào)用有參構(gòu)造器來創(chuàng)造對象
? ? user = (User) cons[0].newInstance(20, "Rollen");
? ? System.out.println(user);
? ? user = (User) cons[1].newInstance("Rollen");
? ? System.out.println(user);
為保證代碼的完整性死宣,同時給出Bean伟恶,
package com.zuikc.bean;
public class User {
public User() {
? ? super();
}
public User(String name) {
? ? super();
? ? this.name = name;
}
public User(int age, String name) {
? ? super();
? ? this.age = age;
? ? this.name = name;
}
private String name;
private int age;
public String getName() {
? ? return name;
}
public void setName(String name) {
? ? this.name = name;
}
public int getAge() {
? ? return age;
}
public void setAge(int age) {
? ? this.age = age;
}
@Override
public String toString() {
? ? return "User [name=" + name + ", age=" + age + "]";
}
}
獲取類的信息,包括所具有的成員變量和方法:
? ? Class clazz = Class.forName("com.zuikc.dao.mysql.UserDaoImpl");
? ? System.out.println("===============本類的field===============");
? ? Field[] field = clazz.getDeclaredFields();
? ? for (int i = 0; i < field.length; i++) {
? ? ? ? int mo = field[i].getModifiers();
? ? ? ? String priv = Modifier.toString(mo);
? ? ? ? Class type = field[i].getType();
? ? ? ? System.out.println(priv + " " + type.getName() + " " + field[i].getName() + ";");
? ? }
? ? System.out.println("==========實現(xiàn)的接口或者父類的public field==========");
? ? Field[] filed1 = clazz.getFields();
? ? for (int j = 0; j < filed1.length; j++) {
? ? ? ? int mo = filed1[j].getModifiers();
? ? ? ? String priv = Modifier.toString(mo);
? ? ? ? Class type = filed1[j].getType();
? ? ? ? System.out.println(priv + " " + type.getName() + " " + filed1[j].getName() + ";");
? ? }
? ? System.out.println("==========實現(xiàn)的接口或者父類的方法==========");
? ? Method method[] = clazz.getMethods();
? ? for (int i = 0; i < method.length; ++i) {
? ? ? ? Class returnType = method[i].getReturnType();
? ? ? ? Class para[] = method[i].getParameterTypes();
? ? ? ? int temp = method[i].getModifiers();
? ? ? ? System.out.print(Modifier.toString(temp) + " ");
? ? ? ? System.out.print(returnType.getName() + "? ");
? ? ? ? System.out.print(method[i].getName() + " ");
? ? ? ? System.out.print("(");
? ? ? ? for (int j = 0; j < para.length; ++j) {
? ? ? ? ? ? System.out.print(para[j].getName() + " " + "arg" + j);
? ? ? ? ? ? if (j < para.length - 1) {
? ? ? ? ? ? ? ? System.out.print(",");
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? Class exce[] = method[i].getExceptionTypes();
? ? ? ? if (exce.length > 0) {
? ? ? ? ? ? System.out.print(") throws ");
? ? ? ? ? ? for (int k = 0; k < exce.length; ++k) {
? ? ? ? ? ? ? ? System.out.print(exce[k].getName() + " ");
? ? ? ? ? ? ? ? if (k < exce.length - 1) {
? ? ? ? ? ? ? ? ? ? System.out.print(",");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? } else {
? ? ? ? ? ? System.out.print(")");
? ? ? ? }
? ? ? ? System.out.println();
? ? }
}
調(diào)用任意方法毅该,
? ? Class clazz = Class.forName("com.zuikc.dao.mysql.UserDaoImpl");
? ? // 調(diào)用方法博秫,第二個參數(shù)指參數(shù)的類型
? ? Method method = method = clazz.getMethod("getUserByName", String.class);
? ? Object? o = method.invoke(clazz.newInstance(), "baobao");
? ? User user = (User)o;
? ? System.out.println(user.getName());
調(diào)用任意屬性:
? ? Class clazz = Class.forName("com.zuikc.dao.mysql.UserDaoImpl");
? ? Object obj = clazz.newInstance();
? ? // 可以直接對 private 的屬性賦值
? ? Field field = clazz.getDeclaredField("propretyname");
? ? field.setAccessible(true);
? ? field.set(obj, "some value");
? ? System.out.println(field.get(obj));
實現(xiàn)動態(tài)代理。注意眶掌,關(guān)于動態(tài)代理挡育,是下一小節(jié)我們將要重點介紹的內(nèi)容。在此處先略過朴爬。
1.4.動態(tài)代理的現(xiàn)實意義
我們說反射可以被用于動態(tài)代理即寒,現(xiàn)在我們先不管動態(tài)代理是什么,按照我們最課程(zuikc.com)授課模式召噩,我們先來看該技術(shù)產(chǎn)生的現(xiàn)實意義蒿叠。
1.4.1. 為什么需要動態(tài)代理
首先,我們的程序已經(jīng)撰寫完畢了◎汲#現(xiàn)在市咽,我們的產(chǎn)品經(jīng)理跑過來說,最近發(fā)生了一些安全上的事故抵蚊,所以我們要加入一個功能施绎,記錄某些關(guān)鍵的操作是誰在什么時候操作的。
簡單來說贞绳,比如谷醉,任何人訪問getUserByName,我們都需要記錄是誰冈闭,在什么時候訪問俱尼,甚至如果是在一個web程序的話,我們還要記錄訪問者的遠(yuǎn)程ip地址萎攒。
一種做法是遇八,我們進(jìn)入getUserByName中去修改代碼,把記錄日志這個事情完成一下耍休。但是技術(shù)總監(jiān)說刃永,不行,既有的服務(wù)層以下的代碼都已經(jīng)經(jīng)過嚴(yán)格測試羊精,不能再侵入代碼斯够,我們必須在既有代碼的外層來加入這些新功能。
于是,思路來了:我們還是按照原有流程執(zhí)行代碼读规,但是執(zhí)行到UserDao的getUserByName時候抓督,我們通過技術(shù)手段替換掉UserDao這個對象,轉(zhuǎn)而調(diào)用UserDaoProxy對象束亏,在這個proxy中铃在,也有一個getUserByName方法,在這個方法里面我們添加新代碼枪汪,同時還調(diào)用老方法,這樣就完美解決了即不修改老方法怔昨,也增加了新功能雀久。
當(dāng)然,這里面的關(guān)鍵點趁舀,就是“通過技術(shù)手段替換掉UserDao這個對象赖捌,轉(zhuǎn)而調(diào)用UserDaoProxy對象”。這個技術(shù)手段矮烹,就叫做:動態(tài)代理。
1.4.2. 代理的最簡單實現(xiàn)
那怎么來生成代理對象,它的形式可以如:
? ? UserDao proxy= 根據(jù)UserDaoImpl來生成代理對象;
? ? proxy.getUserByName("baobao");
好求泰,現(xiàn)在的關(guān)鍵就是讓我們來解決“根據(jù)UserDaoImpl來生成代理對象”备畦。
其實在JDK中的反射包中,正好有一個類java.lang.reflect.Proxy有一個newProxyInstance方法是用來專門生成代理對象的:
static object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
第一個參數(shù)仁期,是要被代理的那個類的ClassLoader桑驱,那怎么得到這個ClassLoader?好辦跛蛋,直接調(diào)用 被代理的類型.getClass().getClassLoader()熬的;
第二個參數(shù),接受的是一個類型的參數(shù)赊级,即我們的UserDaolIml這個類型一共實現(xiàn)了幾個接口押框。那怎么得到這個接口數(shù)組呢,也好辦理逊,Class類型有一個方法getInterfaces()就是用來得到類型所實現(xiàn)的接口數(shù)組的橡伞。
第三個參數(shù),是要傳入一個InvocationHandler的類型晋被。我們發(fā)現(xiàn)InvocationHandler是一個接口骑歹,所以我們得首先實現(xiàn)一個InvocationHandler的實現(xiàn)類。這個實現(xiàn)類實現(xiàn)InvocationHandler中聲明好的一個方法墨微,叫做:
Object invoke(Object proxy, Method method, Object[] args)
看好了道媚,可以說這就很關(guān)鍵了,Proxy被設(shè)計為,我們看上去是在執(zhí)行proxy的getUserByName方法最域,其實是執(zhí)行了proxy的invoke方法谴分,那這是怎么做到的呢:
1:首先,proxy能夠執(zhí)行另外一個類(InvocationHandler實現(xiàn)類)的方法镀脂,那proxy首先得持有這個類不是嗎牺蹄,所以我們才發(fā)現(xiàn)Porxy.newProxyInstance的第三個參數(shù)就是InvocationHandler類型。即創(chuàng)建代理類的時候薄翅,就會把InvocationHandler實現(xiàn)類作為方法參數(shù)傳遞給代理類沙兰;
2:其次,執(zhí)行任何代理類方法翘魄,jvm都會首先引導(dǎo)到執(zhí)行invoke方法鼎天,比如執(zhí)行g(shù)etUserByName,其實已經(jīng)被強制為執(zhí)行invoke方法了暑竟,我們可以在invoke方法斋射,通過反射對原方法為所欲為! 讓我們看看最簡單的invoke方法吧:
package com.zuikc.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class InvocationHandlerImpl implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
? ? System.out.println("被代理的方法要開始執(zhí)行了……");
? ? Object temp = method.invoke(被代理的類的實例, args);
? ? System.out.println("被代理的方法執(zhí)行完畢了……");
? ? return temp;
}
} invoke自帶了參數(shù)但荤,被代理的類的原方法的元數(shù)據(jù)信息作為Method參數(shù)被傳遞起來的罗岖。我們可以對它為所欲為意味著:我們想怎么執(zhí)行它就怎么執(zhí)行它,甚至還可以選擇不執(zhí)行它腹躁。
注意桑包,被代理的方法是獲取到了,但是為了運行它纺非,可不還得有被代理的類的對象本身嗎(上文中捡多,紅色的部分)?既然需要它铐炫,我們就在InvocationHandlerImpl的構(gòu)造器中傳給它就行了垒手,于是,上面的代碼變成了:
package com.zuikc.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class InvocationHandlerImpl implements InvocationHandler {
private Object obj = null;
public? InvocationHandlerImpl(Object obj) {
? ? this.obj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
? ? System.out.println("方法開始執(zhí)行了~~");
? ? System.out.print("? 被截獲的方法為:" + method.getName() + "\t參數(shù)為:");
? ? for (Object arg : args) {
? ? ? ? System.out.print(arg + "\t");
? ? }
? ? System.out.println();
? ? Object temp = method.invoke(this.obj, args);
? ? System.out.println("方法執(zhí)行完畢倒信,返回~~");
? ? return temp;
}
}
經(jīng)過本次改造科贬,客戶端調(diào)用原來的代碼,由:
? ? UserDaoImpl userDaoImpl = new UserDaoImpl();
? ? userDaoImpl.getUserByName("baobao");
變成了被動態(tài)代理的:
? ? UserDaoImpl userDao = new UserDaoImpl();
? ? InvocationHandlerImpl handler = new InvocationHandlerImpl(userDao);
? ? UserDao proxy= (UserDao)Proxy.newProxyInstance(
? ? ? ? ? ? userDao.getClass().getClassLoader(),
? ? ? ? ? ? userDao.getClass().getInterfaces(),
? ? ? ? ? ? handler);
? ? proxy.getUserByName("baobao");
其輸出為:
方法開始執(zhí)行了~~
被截獲的方法為:getUserByName? 參數(shù)為:baobao?
方法執(zhí)行完畢鳖悠,返回~~
1.4.3. 面向切面編程
“代理的最簡單實現(xiàn)”這一小節(jié)中所表現(xiàn)出現(xiàn)的開發(fā)思想就被定義為:面向切面編程(AOP)榜掌。 代理是實現(xiàn)AOP的技術(shù)手段。
未完待續(xù)……