Java - Class物件、反射、動態代理學習
Java是一個動態連結語言。Java中的類在需要時才會被載入,這個類我們可以視為一個Class物件(xxx.class)。管理這些Class物件的類則就是Class類。
這裡有點拗口,對於初學者來說(比如:筆者),需要仔細的理解一下。我們知道.java檔案在編譯之後會生成一個.class檔案。比如Student.java,該檔案裡存放著Student類的定義。編譯之後會生成一個Student.class檔案。在JVM中,可以簡單的理解Student.class就是一個Class物件(不是指那個檔案,而是JVM中的一個數據物件)。它管理著Student這個類的全部資訊。
我們直接看Class的原始碼。在 java.lang中。本文主要介紹幾個常用的方法,以及它們使用的使用方法。
首先是構造器:Class的構造器是私有化的,私有化構造器一般用於禁止使用者直接進行建立物件。
/** * 構造器私有化,引數為類載入器 * 禁止外界直接進行建立物件 * */ private Class(ClassLoader loader) { classLoader = loader; }
那麼Class物件該怎麼建立呢?
在解答這個問題之前,我們先看一個靜態方法:Class.forName(String s);用Java連線過資料庫的同學應該對此並不陌生。遺憾的是,該方法的底層實現使用native方法 forName0實現的。forName0的引數有四個:String name, boolean initialize,ClassLoader loader, Class<?> caller。其中ClassLoader是類載入器,用於載入一個類。
其使用方法和功能如下:
/*******************************************************
* 得到類名為className的一個Class物件的引用 xxx.class
* 如果找不到會丟擲異常 ClassNotFoundException
*
* 呼叫該方法的主要目的是,className.class 如果沒有被載入,
* 則會被載入,從而執行className.class的靜態子句等。
*
* Class c = Class.forName("java.util.Random");
*
* String driver = "oracle.jdbc.driver.OracleDriver"; //orale 驅動
* Class.forName(driver); //註冊驅動,使用類載入
******************************************************/
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException;
現在我們來解答一下,如何對Class進行例項化,得到一個xxxx.class。
1、通過靜態方法.Class.forName(String s) 適用於知道類名(People)的情況:
Class c= Class.forName("People"); // c就是 People.class
2,我們還可以通過物件來獲得Class物件,適用於知道例項物件(people)的情況。
Class c=people.getClass();
getClass(),方法是Object類中的方法,可以返回people的Class物件。
通過Class物件得到一個例項
得到Class物件之後,我們就可以使用Class中的成員方法了。而不是隻能使用靜態方法。
還有另一個常用的方法。叫newInstance();該方法,是用來得到一個Class物件(比如上面的People.class)的一個例項。
People people = c.newInstance();
通常情況下,我們會寫成 Class.forName("People").newInstance();
Class物件的屬性
看到這裡,我們應該對Class物件有了一定的瞭解。下面我們來看看Class物件的屬性。
Class物件是用於管理類資訊的,包括類的方法,屬性,型別,名稱,載入器等等。(比如String.class 管理String類的全部資訊)。比如c.getClassLoader() ,就可以得到People的類載入器。
Class物件的存在,使得我們可以訪問類資訊。
反射:由物件得到類屬性和方法資訊。
Class類和java.lang.reflect類庫一起提供了反射的支援。reflect中包含了三個重要的類:Field,Method,Constructor。見名知義,分別管理欄位,方法,構造器。
Class的getMethods()以及getConstructor()方法,分別返回Method物件陣列和Constructor物件陣列。這兩個類,都提供了深層方法,來解析物件所代表的方法、並獲取其名字、引數、返回值等資訊。
這是我們下面動態代理能實現的基礎。通過物件obj 得到Object.class,再得到Object的各個方法Method。
動態代理
動態代理:產生一個物件obj的代理物件Proxy。代理物件存在的價值主要在於攔截對真實業務物件obj的訪問。(代理物件:經紀人,業務物件:明星)。代理物件應該具有和業務物件相同的方法。最後真正執行的,依然是業務物件的方法。
在這裡需要介紹一下Proxy類的一個靜態方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
throws IllegalArgumentException
用於生成一個代理例項。
/******************************************
* 代理類:
* 用於生成一個代理物件,通常使用靜態方法:Proxy.newProxyInstance( , , )。共有三個引數:
* ClassLoader loader用來指明生成代理物件使用哪個類裝載器,
* Class<?>[] interfaces用來指明生成哪個物件(介面)的代理物件,通過介面指定,
* InvocationHandler h用來指明產生的這個代理物件要做什麼事情。
* 所以我們只需要呼叫newProxyInstance方法就可以得到某一個物件的代理物件了。
*****************************************/
public class Proxy implements java.io.Serializable {
...
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException{
...
}
...
}
這裡又有一個新的型別,InvocationHandler h ,該型別是一個介面。裡面只有一個方法invoke()。動態代理實現invoke()用於通知業務物件進行執行相應的操作。
下面,我們用一個例子來說明動態代理具體是怎麼實現的。
1、先規定一個介面,代理想要做的操作
package proxy;
/**
* @ClassName: Subscribe
* @Description: 邀約
* @author: 西瓜shine
* @date: 2018-12-28 下午7:19:02
*
*/
public interface Subscribe {
void confirmTime(String s);
void confirmPlace(String s);
}
2、業務物件:老闆
package proxy;
/**
* @ClassName: Boss
* @Description: 老闆,業務物件類。
* @author: 西瓜shine
* @date: 2018-12-28 下午7:19:02
*/
public class Boss implements Subscribe{
private String name;
public Boss(String name) {
this.name =name;
}
@Override
public void confirmTime(String s) {
System.out.println("Boss:Time is no problem:"+s);
}
@Override
public void confirmPlace(String s) {
System.out.println("Boss:Place is no problem:"+s);
}
}
3、代理物件:祕書
這裡需要實現一個介面InvocationHandler中的invoke()。引數和作用,在上文已經見過。
該方法內可以對請求進行初步處理,通過判斷客戶端發來的請求方法(一個代理可以實現多個方法)method的名稱(method.getName)來判斷,可在呼叫業務物件的方法之前,進行簡單的處理。之後呼叫業務物件的介面方法來實現
具體的操作: method.invoke(boss,args); 該語句的意思是:呼叫boss的method方法,引數為args。
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @ClassName: Secretary
* @Description: 這個代理類負責生成Boss物件的代理:祕書
* @author: 西瓜shine
* @date: 2018-12-28 下午7:19:02
*
*/
public class Secretary implements InvocationHandler {
private Boss boss ; // 每個祕書只有一個老闆
private String name;
public Secretary(String name,String boss) {
this.name = name;
this.boss = new Boss(boss);
}
public Secretary(String boss) {
this.name = "Secretary of"+boss;
this.boss = new Boss(boss);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("confirmTime"))
System.out.println("Secretary: Just a minute,I will ask my boss ,Is the time ok?");
if(method.getName().equals("confirmPlace"))
System.out.println("Secretary: Just a minute,I will ask my boss, Is the place ok?");
return method.invoke(boss,args);
}
}
4、測試。
package proxy;
import java.lang.reflect.Proxy;
/**
* @ClassName: Interview
* @Description: 模擬訪客先老闆預約,只知道老闆的姓名“Ma Yun”
* @author: 西瓜shine
* @date: 2018-12-28 下午7:19:02
*
*/
public class Interview {
public static void require(Subscribe proxy){
proxy.confirmTime("tomorrow");
proxy.confirmPlace("meeting room");
}
public static void main(String[] args){
Secretary secretary = new Secretary("Ma Yun"); // 得到一個馬雲的祕書的聯絡方式
Subscribe proxy = (Subscribe) Proxy.newProxyInstance( // 得到預約的代理
Subscribe.class.getClassLoader(),
new Class[] {Subscribe.class},
secretary
);
require(proxy);
}
}
執行結果:
Secretary: Just a minute,I will ask my boss ,Is the time ok?
Boss:Time is no problem:tomorrow
Secretary: Just a minute,I will ask my boss, Is the place ok?
Boss:Place is no problem:meeting room
此時執行已經正確。
但是,獲取代理的方法
Subscribe proxy = (Subscribe) Proxy.newProxyInstance( // 得到預約的代理
Subscribe.class.getClassLoader(),
new Class[] {Subscribe.class},
secretary
);
放在客戶端有些不太符合面向物件設計,而且每個客戶都要重複該程式碼。所以對Subscribe類和客戶端進行的修改。
public class Secretary implements InvocationHandler {
private Boss boss ; // 每個祕書只有一個老闆。實際
private String name;
public Secretary(String name,String boss) {
this.name = name;
this.boss = new Boss(boss);
}
public Secretary(String boss) {
this.name = "Secretary of"+boss;
this.boss = new Boss(boss);
}
public Subscribe getProxy(){
return (Subscribe) Proxy.newProxyInstance( // 得到預約的代理
Subscribe.class.getClassLoader(),
new Class[] {Subscribe.class},
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("confirmTime"))
System.out.println("Secretary: Just a minute,I will ask my boss ,Is the time ok?");
if(method.getName().equals("confirmPlace"))
System.out.println("Secretary: Just a minute,I will ask my boss, Is the place ok?");
return method.invoke(boss,args);
}
}
package proxy;
/**
* @ClassName: Interview
* @Description: 模擬訪客先老闆預約,只知道老闆的姓名“Ma Yun”
* @author: 西瓜shine
* @date: 2018-12-28 下午7:19:02
*
*/
public class Interview {
public static void require(Subscribe proxy){
proxy.confirmTime("tomorrow");
proxy.confirmPlace("meeting room");
}
public static void main(String[] args){
Secretary secretary = new Secretary("Ma Yun"); // 得到一個馬雲的祕書的聯絡方式
Subscribe proxy = secretary.getProxy(); // 從祕書那邊得到一個邀約方法(可視為邀約信的格式)
require(proxy); // 填寫邀約。
}
}