一、什么是反射機制?
在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。簡單來說,就是Java對每一個類和類中的所有成員都進行了封裝,這樣每個類都有一個與之對應的Class對象,通過這個對象可以直接訪問類中的所有成員。
二、Class類
1.在一個Class類中,將被封裝類的方法(構造器之外)封裝為Method類,將字段封裝為Filed類,將構造器封裝為Constructor類。這三個類都是定義在java.lang.reflect這個類庫中。
2.(定義一個Person類,需要得到Person類對應的Class對象)要想通過Class對象對被封裝類進行訪問,就必須首先獲得一個Class對象。獲取Class對象有三種方式:(1)通過Person類的對象,調用getClass方法獲得;(2)通過Person類的class常量直接獲得;(3)通過Class類中的forName(String className)方法。注意這種方法中需要傳入類的全名(包名.類名),而且會拋出異常。同時,這三種方式得到的Class對象是一樣的。代碼示例如下:
1 //Person類定義 2 public class Person { 3 private String name; 4 private int age; 5 6 public Person() { 7 } 8 9 public Person(String name, int age) { 10 this.name = name; 11 this.age = age; 12 } 13 14 public String getName() { 15 return name; 16 } 17 18 public void setName(String name) { 19 this.name = name; 20 } 21 22 public int getAge() { 23 return age; 24 } 25 26 public void setAge(int age) { 27 this.age = age; 28 } 29 30 void eat(String food) { 31 System.out.println("eat " + food); 32 } 33 34 protected void drink(String drink) { 35 System.out.println("drink " + drink); 36 } 37 38 @Override 39 public String toString() { 40 // TODO Auto-generated method stub 41 return ("name: " + name + "age: " + age); 42 } 43 }
1 /** 2 * 前兩種方式必須指定類名,而第三種方式雖然也需要類名),但是可以通過一個字符串傳入 3 * @author hr 4 * 5 */ 6 public class getClassName { 7 public static void main(String[] args) throws Exception { 8 Class clazz1 = getClass1(); 9 Class clazz2 = getClass2(); 10 Class clazz3 = getClass3(); 11 System.out.println(((clazz1 == clazz2) && (clazz2 == clazz3))); 12 } 13 14 /** 15 * 第一種方法:通過Person類的對象,調用getClass方法獲得 16 * 17 * @return 18 */ 19 public static Class getClass1() { 20 // TODO Auto-generated method stub 21 Person person = new Person(); 22 return person.getClass(); 23 } 24 25 /** 26 * 第二種方法:通過Person類的class常量直接獲得 27 * 28 * @return 29 */ 30 public static Class getClass2() { 31 // TODO Auto-generated method stub 32 return Person.class; 33 } 34 35 /** 36 * 第三種方法:通過Class類中的forName(String className)方法。 37 * 注意這種方法中需要傳入類的全名(包名.類名),而且會拋出異常。 38 * @return 39 * @throws Exception 40 */ 41 public static Class getClass3() throws Exception { 42 // TODO Auto-generated method stub 43 return Class.forName("reflect.Person"); 44 } 45 }
3.如上所述,可以通過一個類的Class對象得到這個類中的方法、數據和構造器。對於類中的所有對象,Class都提供了返回單個對象或者是對象集合的方法。例如,getMethod()方法,通過參數返回類中的某一個public方法(包括繼承方法),而getMethods()方法則返回類中所有的public方法(包括繼承方法)——通過getDeclaredMethod()和getDeclaredMethods()方法則可以返回本類中的所有方法(包括private方法,但是不包括繼承來的方法)。對於類中的構造器和數據也是一樣。
有兩種方法可以用來新建類的對象:通過Class對象的newInstance()方法,這樣得到的對象沒有初始參數;首先通過Class對象getConstructor()方法得到對應的構造器(注意傳入的參數代表該構造器的參數類型),然后通過得到的構造器創建新的對象(可以傳入初始化參數)。
類中的方法可以通過Class中的invoke方法直接執行。對於非private方法,都是先通過getDeclaredMethod()或者getMethod()方法得到方法名(兩種方法傳入的參數,第一個是方法名,后面是方法需要的參數類型),然后通過方法名調用invoke()方法執行該方法(傳入形式參數);對於private()方法,在得到方法名之后,需要先通過setAccessible()方法設置訪問權限,然后通過方法名調用invoke()方法執行該方法(傳入形式參數)。
代碼示例如下:
1 public static void main(String[] args) throws Exception { 2 // TODO Auto-generated method stub 3 Class<Person> clazz = (Class<Person>) Class.forName("reflect.Person"); 4 // 獲取除父類方法之外的所有方法 5 Method[] declaredMethods = clazz.getDeclaredMethods(); 6 // 獲取所有的public方法,包括繼承的方法 7 Method[] publicMethods = clazz.getMethods(); 8 // 獲取一個除父類方法之外的方法 9 Method read = clazz.getDeclaredMethod("read", String.class); 10 // 獲取一個本類中的public方法,包括繼承的方法 11 Method toString = clazz.getMethod("toString", null); 12 // 創建類的一個無參對象 13 Object obj1 = clazz.newInstance(); 14 // 創建類的一個帶參對象 15 // 需要首先得到該類的一個帶參構造器,getConstructor傳入的參數代表該構造器需要的數據類型 16 // 然后通過構造器構造該類的一個對象 17 Constructor<Person> constructor = clazz.getConstructor(String.class, int.class); 18 Object obj2 = constructor.newInstance("Sara", 14); 19 // 顯示兩個對象 20 System.out.println(obj1); 21 System.out.println(obj2); 22 // 運行類中的一個public方法 23 // 需要先得到該方法(pay) 24 Method pay = clazz.getMethod("pay", double.class); 25 pay.invoke(obj2, 5.4); 26 // 運行類中的一個protect方法 27 Method drink = clazz.getDeclaredMethod("drink", String.class); 28 drink.invoke(obj2, "water"); 29 // 運行類中的一個default方法 30 Method eat = clazz.getDeclaredMethod("eat", String.class); 31 eat.invoke(obj2, "noodles"); 32 // 運行類中的一個private方法,read方法之前已得到 33 // 需要先將read方法的訪問權限設置為可見 34 read.setAccessible(true); 35 read.invoke(obj2, "Thinking in Java"); 36 37 for (Method method : declaredMethods) { 38 System.out.println(method); 39 } 40 41 System.out.println("---------------"); 42 43 for (Method method : publicMethods) { 44 System.out.println(method); 45 } 46 }
三、反射機制的作用
1.在運行時判斷任意一個對象所屬的類;
2.在運行時構造任意一個類的對象;
3.在運行時判斷任意一個類所具有的成員變量和方法;
4.在運行時調用任意一個對象的方法;
5.生成動態代理。
前四個作用已經在上文中介紹了,下面主要說一下動態代理。
四、靜態代理
在了解動態代理之前,我們首先需要介紹一下靜態代理。簡單來說,代理就是用一個代理類來封裝一個委托類,這樣做有兩個好處:可以隱藏委托類的具體實現;可以在不改變委托類的情況下增加額外的操作。而靜態代理,就是在程序運行之前,代理類就已經存在了。靜態代理一般的實現方式為:委托類和代理類都實現同一個接口或者是繼承自同一個父類,然后在代理類中保存一個委托類的對象引用(父類或者父類接口的對象引用),通過給構造器傳入委托類的對象進行初始化,在同名方法中通過調用委托類的方法實現靜態代理。除此之外,在代理類同名方法中還可以實現一些額外的功能。代碼示例如下(借用Java編程思想中的實例),RealObject類為委托類,SimpleProxy類為代理類:
1 interface Interface { 2 void doSomething(); 3 4 void somethingElse(String arg); 5 } 6 7 class RealObject implements Interface { 8 @Override 9 public void doSomething() { 10 // TODO Auto-generated method stub 11 System.out.println("doSomething"); 12 } 13 14 @Override 15 public void somethingElse(String arg) { 16 // TODO Auto-generated method stub 17 System.out.println("somethingElse " + arg); 18 } 19 } 20 21 class SimpleProxy implements Interface { 22 // 保存委托類(父接口的引用) 23 private Interface proxied; 24 25 // 傳入委托類的對象用於初始化 26 public SimpleProxy(Interface proxied) { 27 this.proxied = proxied; 28 } 29 30 // 兩個同名方法中還實現了其他的功能 31 @Override 32 public void doSomething() { 33 // TODO Auto-generated method stub 34 System.out.println("SimpleProxy doSomething"); 35 proxied.doSomething(); 36 } 37 38 @Override 39 public void somethingElse(String arg) { 40 // TODO Auto-generated method stub 41 System.out.println("SimpleProxy somethingElse " + arg); 42 proxied.somethingElse(arg); 43 } 44 } 45 46 public class SimpleProxyDemo { 47 public static void main(String[] args) { 48 consumer(new RealObject()); 49 consumer(new SimpleProxy(new RealObject())); 50 } 51 52 public static void consumer(Interface iface) { 53 iface.doSomething(); 54 iface.somethingElse("bonobo"); 55 } 56 }
五、動態代理
靜態代理的局限性在於,代理類需要在程序運行之前就編寫好,而動態代理則可以在程序運行的過程中動態創建並處理對所代理方法的調用。在動態代理中,需要定義一個中介類,這個類實現InvocationHandle接口(主要是里面的invoke方法)。這個中介類位於委托類和代理類之間,作為一個調用處理器而存在。它保存一個委托類的引用,通過傳入委托類對象進行初始化;然后在invoke方法中,實現對委托類方法的調用,並增加需要的額外操作。在需要使用動態代理時,首先通過Proxy類中的newProxyInstance方法得到代理類對象(方法的三個參數分別是:(通常是委托類實現接口的)類加載器,希望代理類實現的接口列表(通常也是委托類實現的接口),以及一個調用處理器的對象),然后通過這個代理類對象直接調用代理類的方法。這種調用實際上會通過調用處理器調用invoke方法,進而實現對委托類相應方法的調用。
注意在動態代理中,只實現了一個調用處理器,而沒有真正實現代理類。代理類對象是通過Proxy類中的newProxyInstance方法得到的。這樣,不管你在調用委托類任何方法時需要加入的額外操作都可以僅僅在調用處理器中的invoke方法中實現就可以了。代碼示例如下(來自Java編程思想):
1 public class SimpleDynamiProxyDemo { 2 public static void consumer(Interface iface) { 3 iface.doSomething(); 4 iface.somethingElse("bonobo"); 5 } 6 7 public static void main(String[] args) { 8 RealObject real = new RealObject(); 9 consumer(real); 10 // 通過Proxy.newProxyInstance方法得到代理類對象 11 Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), 12 new Class[] { Interface.class }, new DynamicProxyHandler(real)); 13 // 通過代理類對象直接調用方法,會被重定向到調用處理器上的invoke方法 14 consumer(proxy); 15 16 } 17 } 18 19 // 中介類(調用處理器) 20 class DynamicProxyHandler implements InvocationHandler { 21 // 保存一個委托類的對象 22 private Object proxied; 23 24 public DynamicProxyHandler(Object proxied) { 25 this.proxied = proxied; 26 } 27 28 // 三個參數:代理類的引用,方法名和方法的參數列表 29 @Override 30 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 31 // TODO Auto-generated method stub 32 System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args); 33 if (args != null) { 34 for (Object arg : args) { 35 System.out.println(" " + arg); 36 } 37 } 38 // 實現對委托類方法的調用,參數表示委托類對象和參數 39 return method.invoke(proxied, args); 40 } 41 }
