引序:開始反射概念學習之前,先來段引序。
請牢記:計算機的三大程式結構;順序執行結構、條件分支結構、循環處理結構。
有時我們寫的程式會自我感覺這裡寫了代碼,它是怎麼被排程執行的呢,按照程式的順序執行來考究,有點說不通。我舉幾個例子說明看起來說不通的現象:
- 說不通1:注解
import java.lang.reflect.*;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno{ //注解定義
String name() default "12345";
int value();
}
@MyAnno(name="haha",value=123) //注解釘點
class B {
public static void main(String[] args){
if( B.class.isAnnotationPresent(MyAnno.class) ){
Annotation annos[] = B.class.getAnnotations(); //消費注解
for(Annotation anno : annos){
System.out.println(anno);
System.out.println( ((MyAnno)anno).name() );
System.out.println( ((MyAnno)anno).value() );
System.out.println( anno.annotationType().getName() );
}}}}
上面注解的小示例:如果看《注解定義》《注解釘點》 這兩個地方,能搞懂我們寫這兩處的意思嘛?完全是懵圈的,這是什麼代碼,又什麼時候被排程執行,咋執行的呢?正是因為有 《注解消費》 這個梗,我們才恍然大悟,注解是啥時用起來的。(這也是很多架構利用大量注解的底層技術手段,第三方架構有其自己的《注解定義》以及《注解消費》底層,開放給我們開發時用的就是“注解釘點”,我們利用好《注解釘點》這一點就OK)
- 說不通2:匿名内部類
匿名内部類:作為方法的入參來使用,例如:
1:事件監聽處理器類:處理事件時作為監聽處理器方法的形參的Handler類
event.addEventListener( new Handler() { //這裡的Handler就是匿名内部類
public void xxxMethed(Event event){ //這是匿名類中的方法xxxMethod();
}
}); //這個xxxMtd()是由事件排程線程來執行的。
以上代碼我們隻是調用了addEventListener(Handler hd); 傳進來一個hd對象而已,那麼Handler類中的xxxMethod()方法怎麼會被調用的呢?答:是被事件排程線程來執行的。事件排程線程是JVM管理的,不受我們的代碼控制。
2:多線程程式設計時的任務類:往線程池當中送出一個實作了Runnable接口的類
threadPoolExecutor.execute( new Runnable(){ //這裡的Runnable就是匿名内部類
public void run(){ //這個匿名類中有一個方法run();
}
}); //這個run()是由線程池排程工作線程來執行的。
以上代碼隻是調用了execute(Runnable task); 傳進來一個task任務而已,那麼Runnable類中的run()方法怎麼會被調用的呢?答:是被線程池排程工作線程來執行的。線程池是一個架構,它怎麼排程和管理工作線程,不受我們的代碼控制。我們“寫好匿名内部類中的方法業務邏輯”這一點就OK。了解匿名内部類很重要,它是靜态代理模式的一種展現。之是以匿名内部類很重要,是因為它是了解lambda函數式程式設計思想的基礎。
- 說不通3:動态代理
import java.lang.reflect.*;
//接口
interface InterfaceHello {
void sayHello();
void read();
}
//業務類:實作接口
class HelloImpl implements InterfaceHello {
public void sayHello() {
System.out.println("hello world");
}
public void read() {
System.out.println("I am reading...");
}
}
//我是代理:業務類真正業務邏輯的前後,我搞點事(比如:打個廣告)
class ProxyHello implements InvocationHandler{
private Object target;
public ProxyHello(Object target) {
this.target=target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是代理,看視訊之前我打個廣告,haha!");
Object obj = method.invoke(target,args); // 真正的業務邏輯
System.out.println("我是代理,視訊看完給評價下呗,多謝!\r\n");
return obj;
}
}
//測試
public class HelloTest {
public static void main(String[]args) {
try{
InterfaceHello h = (InterfaceHello)Proxy.newProxyInstance(
HelloTest.class.getClassLoader(),
new Class<?>[]{InterfaceHello.class},
new ProxyHello(new HelloImpl())
);
h.sayHello(); //調用InterfaceHello接口中的sayHello()方法
h.read(); //調用InterfaceHello接口中的read()方法
}
catch(Exception e){}
}
}
程式輸出:
我是代理,看視訊之前我打個廣告,haha!
hello world
我是代理,視訊看完給評價下呗,多謝!
我是代理,看視訊之前我打個廣告,haha!
I am reading...
我是代理,視訊看完給評價下呗,多謝!
如果單看以上的測試類:h.sayHello(); 這句代碼,是蒙圈的,因為按照我們以往的程式經驗,這不就是實作了InterfaceHello接口的HelloImpl幹的事嘛,但是程式的輸出卻不是這樣,前有廣告,中間是正事,後有評論。為什麼這樣呢?是因為:h是Proxy.newProxyInstance()出來的,下面簡要分析下:
Proxy.newProxyInstance(loader,intfs[],handler){
Class<?> cl = getProxyClass0(loader, intfs); //記憶體中生成一個類$Proxy0,實作intfs所有接口
final Constructor<?> cons = cl.getConstructor(constructorParams); //類$Proxy0的構造函數
return cons.newInstance(new Object[]{handler}); //引入handler,new $Proxy0對象傳回
}
那這個記憶體中動态生成的類$Proxy0到底長什麼樣子呢?我寫個簡易版的樣子:
public class $Proxy0{
private InvocationHandler handler;
public $Proxy0(InvocationHandler handler){
this.handler = handler;
}
public void sayHello(){ //這就是動态代理,【魔術揭秘】的地方
handler.invoke();
}
public void read(){
handler.invoke();
}
}
看:InterfaceHello h = (InterfaceHello)Proxy.newProxyInstance(loader,intfs[],handler);
我們恍然大悟,我們拿到的是$Proxy0對象,這個對象實作了所有的接口,是以向下類型強轉後,我們拿到的變量h可以調用接口中的任意方法。我們代碼随後編寫的 h.sayHello(); 這下徹底明白,它的内部邏輯是調用handler.invoke();//看上方的【魔術揭秘】。是以我的總結是:程式的順序執行結構永遠是這麼個套路,大家别以為這不是廢話嘛,程式不順序執行難道還來回胡亂的亂跑執行嘛?不不不,要深刻體會,有助于碰到新技術時的了解的。在用很多第三方架構的時候,我們寫好“業務實作邏輯”就OK,代理都是架構幫我們做好的,比如我們的方法業務邏輯裡面并沒有事務管理,但CRUD增删查改時都是能反應到資料庫并送出/復原的。
總結:以上三個小示例不過是解釋了随着程式設計技術的更多手段,讓我們對程式總是順序執行結構在感官上的認知有所偏差。其實呢?說到底,程式的執行永遠是從上至下的順序執行結構,隻不過有些代碼比較顯而易見,有些代碼并不顯而易見是由于架構/JVM等有一些我們看不見的底層機制,來排程順序執行我們的代碼。是以很多時候我們是了解底層原理即可,(依托底層機制抛出來的)面向應用高層面的一些API來編寫好代碼即可,必定我們不是java公司要制作jvm或者api,我們隻是利用api寫應用程式。
反射:
官方定義,反射是程式在運作時分析代碼的能力(更貼切的說,就是知曉類結構資訊的能力)。結合上述三個小示例的示範和說明,反射不過就是一種程式設計手段而已,就這麼簡單。我給反射下的定義是:“知曉類結構”
- 強調:
類對象 Class<?> clazz = myObj.getClass(); 這個clazz就是類對象。每一個類檔案被JVM加載後,JVM就自動的建立好了一個類對象clazz,這個類對象包裝了這個類的結構資訊,一個類大緻的結構:就是它實作了什麼接口,它繼承了哪個超類,它的構造函數,它的資料成員,它的方法成員,方法成員的入參有幾個都是什麼類型,大緻一個類的結構就這麼點玩意。正是由于JVM自動建立出來一個類對象,是以我們才能寫出來clazz.getFields(); //知道類的資料成員有哪些,clazz.getMethods(); //知道類的方法成員有哪些,等等API。如果這種技術手段給一個更貼切的漢字含義,其實應該叫“知曉類結構”,不過這個名字太土了點,java就給它起了個高大上的名字:反射。
- 又強調:
知道了類對象的存在,那麼現在大家都應該了解所謂的靜态成員為什麼都是 類名.靜态成員名 的通路手法了,這看起來像什麼呢?不就是 對象.方法名 的寫法嘛,這裡的類名其實類對象的意思。
- 再三強調:
請看下方示例代碼,為什麼靜态方法上的synchronized和非靜态方法上的synchronized 并不會互相幹擾呢?因為它們的内置鎖不同。靜态方法的内置鎖是類對象,而非靜态方法(/也叫對象方法)的内置鎖是具體的new出來的對象,它們不是同步一把鎖,是以靜态方法和對象方法并不會互斥。
class A{
//我是靜态方法,我的内置鎖是A類對象
public synchronized static void a1(){
/**我在執行
* 此時想調用a2(), a2()被互斥
* 此時想調用b1()或者b2()是可以的
*/
}
//我是靜态方法,我的内置鎖是A類對象
public synchronized static void a2(){}
//我是對象方法,我的内置鎖是目前調用我的對象
public synchronized void b1(){
/**我在執行
* 此時想調用b2(), b2()被互斥
* 此時想調用a1()或者a2()是可以的
*/
}
//我是對象方法,我的内置鎖是目前調用我的對象
public synchronized void b2(){}
}
以上是對Class類對象的一個淺顯的總結,下面我們接着回到話題:反射。
類對象:知曉類結構
Class<?> clazz = MyClass.class; //得到類對象clazz 第1種手段 Class<?> clazz = myObj.getClass(); //得到類對象clazz 第2種手段 Class<?> clazz = Class.forName(pkg.MyClass); //得到類對象clazz 第3種手段 | ||||
方法 | 方法傳回值 | 說明(/知曉類結構) | ||
類 結 構 | 實作接口 | getInterfaces() | Class<?>[ ] | 類實作的所有接口 |
繼承父類 | getSuperclass() | Class<? extends T> | 類所繼承的父類 | |
構造函數 | getConstructors() | Constructor<?>[ ] | 類所有的構造函數 | |
getConstructor(Class<?>...paramTypes) | Constructor<T> | 類的某個構造函數 | ||
資料成員 | getFields() | Field[ ] | 類所有的資料成員 | |
getField(String name) | Field | 類的某個資料成員 | ||
方法成員 | getMethods() | Method[ ] | 類所有的方法成員 | |
getMethod(String name,Class<?>...paramTypes) | Method | 類的某個方法成員 | ||
注解成員 | 最下方有個注解的單獨表格。 Class類、Field字段、Method方法、Constructor構造函數、都支援注解的。 |
類自身的,不包括繼承 | 方法 | 方法傳回值 | |
類 結 構 | 内部成員 所屬的聲明類 | getDeclaringClass() | Class<?> |
構造函數 | getDeclaredConstructors() | Constructor<?>[ ] | |
getDeclaredConstructor(Class<?>...paramTypes) | Constructor<T> | ||
資料成員 | getDeclaredFields() | Field[ ] | |
getDeclaredField(String name) | Field | ||
方法成員 | getDeclaredMethods() | Method[ ] | |
getDeclaredMethod(String name,Class<?>...paramTypes) | Method |
通過以上對Class類的部分方法(這裡集中出了在編寫反射代碼時常用的一些API)的總結,可以知曉,通過clazz類對象調用這些API我們可以做到知曉類結構的資訊。比如:知道類中有哪些屬性,哪些方法等。
類結構之:内部成員
類對象 Class<?> clazz = myObj.getClass(); 這個clazz就是類對象,通過調用類對象的API,我們可以獲知類的結構,其内部成員我主要從以下三個着手講解:構造函數、資料成員、方法成員。如圖:

内部成員--構造函數
Constructor<T> cons = clazz.getConstructor(Class<?>...paramTypes);
T obj = cons.newInstance(initargs[]); //平時主要用構造函數的這句代碼來生成一個對象
内部成員--資料成員
Field f = clazz.getDeclaredField(String fieldName);
f.setAccessible(true); //意指能通路f字段的值(預設反射隻能通路public修飾的)
f.getAnnotationByType(MyAnno.class); //字段除了值以外能攜帶額外資訊,注解
内部成員--方法成員
Method m = clazz.getMethod(String mtdName, Class<?>...paramTypes); //參數的類型
m.invoke(Object target, Object...paramArgs); //實際的參數
靜态代理:
靜态代理要求:接口,實作該接口的兩貨色:真實角色,代理角色;代理角色要持有真實角色的引用。靜态代理的本質是運用java的多态性的展現模式一,方法重寫。java多态性,請參考我的部落格 Java基礎 -- 02類 /接口 /繼承
靜态代理模式:匿名内部類就是一種靜态代理模式的展現。lambda表達式又是匿名内部類的一種“文法糖”的簡易版本,是以lambda表達式也是一種靜态代理模式的展現。
靜态代理這個簡單,一個小示例:
//接口
interface Hello {
void say();
}
// 真實角色
class HelloImpl implements Hello {
public void say() {
System.out.println("..我是業務實作方!");
}
}
// 代理角色
class ProxyH implements Hello {
private Hello hImpl; // 持有真實業務方的引用
public ProxyH(){}
public ProxyH(Hello hImpl){ this.hImpl = hImpl; }
public void say() {
System.out.println("代理幹的事:業務開始前,來段廣告~~");
hImpl.say(); //真實業務執行
System.out.println("代理幹的事:業務結束後,下評論呗^^");
}
}
//測試
public class StaticProxyTest {
public static void main(String[] args) {
Hello hImpl = new HelloImpl(); //建立真實業務
Hello proxyH = new ProxyH(hImpl); //建立靜态代理
proxyH.say(); //代理執行
}
}
靜态代理小示例執行結果:
代理幹的事:業務開始前,來段廣告~~
..我是業務實作方!
代理幹的事:業務結束後,下評論呗^^
動态代理:
在本文剛開始的引序裡面的3,動态代理,已經說的很詳細。這裡隻是總結下:
動态代理--核心技術:
運作時JVM動态生成記憶體中的代理類 $Proxy0
public class $Proxy0{
private InvocationHandler handler;
public $Proxy0(InvocationHandler handler){
this.handler = handler;
}
public void sayHello(){ //這就是動态代理,【魔術揭秘】的地方
handler.invoke();
}
}
動态代理--解決問題:
在不影響原有代碼結構的情況下(有的時候也影響不了,比如:别人封裝好的jar包,無法反編譯的,我們就無法窺探class檔案所對應的源碼java檔案到底長什麼樣,自然無法對其進行修改擴容。當然此話不絕對,比如Hibernate架構就是通過CGLIB技術分析class檔案的位元組碼來動态的在記憶體中生成代理類的,隻是這種手段一般程式員無法掌握而已,太複雜),我們又想在原有代碼做業務行為的前後橫加幹涉一下,比如穿插廣告,事後提醒評論,記錄日志等,那這時用動态代理就很合适。
動态代理--使用場景:
日志系統,Spring的AOP技術,聲明式事務管理,等等。Hibernate用CGLIB技術實作動态代理,Spring用Proxy技術實作動态代理。
反射配套注解,更強悍:
舉一個簡單的說明大家即可明白,我們通過反射能知道類的資料成員有 id,name,age等等,但是通過反射Field的若幹方法,我們能知道哪個是主鍵嘛?這玩意單單靠反射Field的API達不到要求。我們回想一下,注解,幹嘛的呢?注解就是個标簽(标簽可以攜帶額外資訊),注解釘點在哪裡,目前被釘點的元素(可以是類/字段/方法/構造函數等)就能攜帶更多的額外資訊,然後我們簡單的針對目前元素調用出注解field.getAnnotations(MyAnno.class)來消費,就能很好的在這麼多的字段中,判别出哪個字段才是主鍵。注解不了解的話,可以參考我的部落格 Java基礎 -- 06注解Annotation
反射結合泛型再配套注解,更更更強悍:
以上兩個圖,是jdk5之後,java為了迎合到底怎麼處理泛型而擴充出來的類型。從jdk5之後,java有了這些類型:
- 8種基本資料類型、
- Class類類型、
- ParameterizedType泛型、
- TypeVariable泛型變量類型、
- AnnotatedArrayType泛型(/泛型變量)數組類型
上圖中的配套小示例以及不了解泛型的話,請參考我的部落格 Java基礎 -- 04泛型
為什麼說,更更更強悍呢?先看小示例: