JAVA反射機制與動態代理

在Java運行時環境中,對於任意一個類,能否知道這個類有哪些屬性和方法?對於任意一個對象,能否調用它的任意一個方法?答案是肯定的。這種動態獲取類的信息以及動態調用對象的方法的功能來自於Java 語言的反射(Reflection)機制。

Java 反射機制主要提供了以下功能

1、在運行時判斷任意一個對象所屬的類。
2、在運行時構造任意一個類的對象。
3、在運行時判斷任意一個類所具有的成員變量和方法。
4、在運行時調用任意一個對象的方法

Reflection 是Java被視爲動態(或準動態)語言的一個關鍵性質。這個機制允許程序在運行時透過Reflection APIs取得任何一個已知名稱的class的內部信息,包括其modifiers(諸如public, static 等等)、superclass(例如Object)、實現之interfaces(例如Serializable),也包括fields和methods的所有信息,並可於運行時改變fields內容或調用methods

一般而言,開發者社羣說到動態語言,大致認同的一個定義是:“程序運行時,允許改變程序結構或變量類型,這種語言稱爲動態語言”。從這個觀點看,Perl,Python,Ruby是動態語言,C++,Java,C#不是動態語言

儘管在這樣的定義與分類下Java不是動態語言,它卻有着一個非常突出的動態相關機制:Reflection。這個字的意思是“反射、映象、倒影”,用在Java身上指的是我們可以於運行時加載、探知、使用編譯期間完全未知的classes。換句話說,Java程序可以加載一個運行時才得知名稱的class,獲悉其完整構造(但不包括methods定義),並生成其對象實體、或對其fields設值、或喚起其methods。這種“看透class”的能力(the ability of the program to examine itself)被稱爲introspection(內省、內觀、反省)。Reflection和introspection是常被並提的兩個術語

在JDK中,主要由以下類來實現Java反射機制,這些類都位於java.lang.reflect包中
1、Class類:代表一個類。
2、Field 類:代表類的成員變量(成員變量也稱爲類的屬性)。
3、Method類:代表類的方法。
4、Constructor 類:代表類的構造方法。
5、Array類:提供了動態創建數組,以及訪問數組的元素的靜態方法

如例程10-1所示DumpMethods類演示了Reflection API的基本作用,它讀取命令行參數指定的類名,然後打印這個類所具有的方法信息:

例程10-1 DumpMethods.java

import java.lang.reflect.*;

public class DumpMethods {

  public static void main(String args[]) throws Exception{

    //加載並初始化命令行參數指定的類

    Class classType = Class.forName(args[0]);

    //獲得類的所有方法

    Method methods[] = classType.getDeclaredMethods();

    for(int i = 0; i < methods.length; i++)

      System.out.println(methods[i].toString());

  }

}
<p class="ad">運行命令“java DumpMethods java.util.Stack”,就會顯示java.util.Stack類所具有的方法,程序的打印結果如下:</p><p class="a8">public synchronized java.lang.Object java.util.Stack.pop()</p><p class="a8">public java.lang.Object java.util.Stack.push(java.lang.Object)</p><p class="a8">public boolean java.util.Stack.empty()</p><p class="a8">public synchronized java.lang.Object java.util.Stack.peek()</p><p class="a8">public synchronized int java.util.Stack.search(java.lang.Object)</p>

如例程10-2所示ReflectTester類進一步演示了Reflection API的基本使用方法。ReflectTester類有一個copy(Object object)方法,這個方法能夠創建一個和參數object同樣類型的對象,然後把object對象中的所有屬性複製到新建的對象中,並將它返回。

這個例子只能複製簡單的JavaBean,假定JavaBean的每個屬性都有public類型的getXXX()和setXXX()方法。

例程10-2 ReflectTester.java

import java.lang.reflect.*;

public class ReflectTester {

 public Object copy(Object object) throws Exception{

//獲得對象的類型

  Class classType=object.getClass();

  System.out.println("Class:"+classType.getName());

  //通過默認構造方法創建一個新的對象
  //new Class[]{} 構造函數參數類型
  Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{}); 
  //獲得對象的所有屬性 Field fields[]=classType.getDeclaredFields(); 
  for(int i=0; i<fields.length;i++){ 
    Field field=fields[i]; 
    String fieldName=field.getName(); 
    String firstLetter=fieldName.substring(0,1).toUpperCase(); 
    //獲得和屬性對應的getXXX()方法的名字 
    String getMethodName="get"+firstLetter+fieldName.substring(1); 
    //獲得和屬性對應的setXXX()方法的名字 
    String setMethodName="set"+firstLetter+fieldName.substring(1); 
    //獲得和屬性對應的getXXX()方法 
    Method getMethod=classType.getMethod(getMethodName,new Class[]{}); 
   //獲得和屬性對應的setXXX()方法 
   Method setMethod=classType.getMethod(setMethodName,new Class[]{field.getType()}); 
   //調用原對象的getXXX()方法 Object value=getMethod.invoke(object,new Object[]{}); 
   System.out.println(fieldName+":"+value); 
   //調用複製對象的setXXX()方法 
   setMethod.invoke(objectCopy,new Object[]{value}); } 
   return objectCopy; 
 } 
  public static void main(String[] args) throws Exception{ 
    Customer customer=new Customer("Tom",21); 
    customer.setId(new Long(1)); 
    Customer customerCopy=(Customer)new ReflectTester().copy(customer); 
    System.out.println("Copy information:"+customerCopy.getName()+""+customerCopy.getAge()); 
 }
}
class Customer{ 
  //Customer類是一個JavaBean 
  private Long id; 
  private String name; 
  private int age; 
  public Customer(){} 
  public Customer(String name,int age){ this.name=name; this.age=age; } 
  public Long getId(){return id;} 
  public void setId(Long id){this.id=id;} 
  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;}

}
 

以上代碼假定每個屬性都有相應的getXXX()和setXXX()方法,並且在方法名中,“get”和“set”的後面一個字母爲大寫。例如,Customer類的name屬性對應getName()和setName()方法。Method類的invoke(Object obj,Object args[])方法用於動態執行一個對象的特定方法,它的第一個obj參數指定具有該方法的對象,第二個args參數指定向該方法傳遞的參數。

在java.lang.Object 類中定義了getClass()方法,因此對於任意一個Java對象,都可以通過此方法獲得對象的類型。Class類是Reflection API 中的核心類,它有以下方法getName():獲得類的完整名字。getFields():獲得類的public類型的屬性。getDeclaredFields():獲得類的所有屬性。getMethods():獲得類的public類型的方法。getDeclaredMethods():獲得類的所有方法。

getMethod(String name, Class[] parameterTypes):獲得類的特定方法,name參數指定方法的名字,parameterTypes 參數指定方法的參數類型。getConstructors():獲得類的public類型的構造方法。getConstructor(Class[] parameterTypes):獲得類的特定構造方法,parameterTypes 參數指定構造方法的參數類型。newInstance():通過類的不帶參數的構造方法創建這個類的一個對象。

通過默認構造方法創建一個新對象:Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});以上代碼先調用Class類的getConstructor()方法獲得一個Constructor 對象,它代表默認的構造方法,然後調用Constructor對象的newInstance()方法構造一個實例。

獲得對象的所有屬性:Field fields[]=classType.getDeclaredFields();Class 類的getDeclaredFields()方法返回類的所有屬性,包括public、protected、默認和private訪問級別的屬性

如例程10-3所示的InvokeTester類的main()方法中,運用反射機制調用一個InvokeTester對象的add()和echo()方法

例程10-3 InvokeTester.java

import java.lang.reflect.*;

public class InvokeTester {

public int add(int param1,int param2){

  return param1+param2;

}

public String echo(String msg){

  return "echo:"+msg;

}

 public static void main(String[] args) throws Exception{

  Class classType=InvokeTester.class;

  Object invokeTester=classType.newInstance();

  //調用InvokeTester對象的add()方法

  Method addMethod=classType.getMethod("add",new Class[]{<strong>int.class,int.class</strong>});

  Object result=addMethod.invoke(invokeTester,new Object[]{new Integer(100),new Integer(200)});

  System.out.println((Integer)result);

  //調用InvokeTester對象的echo()方法

  Method echoMethod=classType.getMethod("echo",new Class[]{String.class});

  result=echoMethod.invoke(invokeTester,new Object[]{"Hello"});

  System.out.println((String)result);

 }

}

add()方法的兩個參數爲int類型,獲得表示add()方法的Method對象的代碼如下:

Method addMethod=classType.getMethod("add",new Class[]{int.class,int.class});

Method類的invoke(Object obj,Object args[])方法接收的參數必須爲對象,如果參數爲基本類型數據,必須轉換爲相應的包裝類型的對象。invoke()方法的返回值總是對象,如果實際被調用的方法的返回類型是基本類型數據,那麼invoke()方法會把它轉換爲相應的包裝類型的對象,再將其返回。

本例中,儘管InvokeTester類的add()方法的兩個參數及返回值都是int類型,調用addMethod對象的invoke()方法時,只能傳遞Integer類型的參數,並且invoke()方法的返回類型也是Integer類型,Integer類是int基本類型的包裝類:

Object result=addMethod.invoke(invokeTester,new Object[]{new Integer(100),new Integer(200)});

System.out.println((Integer)result); //result爲Integer類型


java.lang.Array類提供了動態創建和訪問數組元素的各種靜態方法。

如例程10-4所示的ArrayTester1類的main()方法創建了一個長度爲10的字符串數組,接着把索引位置爲5的元素設爲“hello”,然後再讀取索引位置爲5的元素的值。

例程10-4 ArrayTester1.java

import java.lang.reflect.*;

public class ArrayTester1 {

 public static void main(String args[])throws Exception {

  Class classType = Class.forName("java.lang.String");

  //創建一個長度爲10的字符串數組
 
  Object array = Array.newInstance(classType, 10);

  //把索引位置爲5的元素設爲"hello"

  Array.set(array, 5, "hello");

  //讀取索引位置爲5的元素的值

  String s = (String) Array.get(array, 5);

  System.out.println(s);

 }

}
如例程10-5所示的ArrayTester2類的main()方法創建了一個5×10×15的整型數組,並把索引位置爲[3][5][10]的元素的值爲設37。
例程10-5 ArrayTester2.java

import java.lang.reflect.*;

public class ArrayTester2{

public static void main(String args[]) {

  int dims[] = new int[]{5, 10, 15};
  //生成三維數組
  Object array = Array.newInstance(Integer.TYPE, dims);

  //使arrayObj引用array[3]

  Object arrayObj = Array.get(array, 3);//第一維下標爲3的
  <pre name="code" class="java">  //arrayObj爲兩維數組 
  Class cls = arrayObj.getClass().getComponentType();

  System.out.println(cls);

  //使arrayObj引用array[3][5]

  arrayObj = Array.get(arrayObj, 5);//一維數組

  //把元素array[3][5][10]設爲37

  Array.setInt(arrayObj, 10, 37);//第十個元素設置爲37

  int arrayCast[][][] = (int[][][]) array;

  System.out.println(arrayCast[3][5][10]);

 }

}

擴展:從Object層次來說,沒有重寫euqals的情況下,“==”和equals是相同的,“==”不管什麼情況,都是比較的內存地址,在object中,equals方法調用的“==”,this==obj

Class是Reflection起源。針對任何您想探勘的class,唯有先爲它產生一個Class object,接下來才能經由後者喚起爲數十多個的Reflection APIs

Java允許我們從多種途徑爲一個class生成對應的Class object

欲生成對象實體,在Reflection 動態機制中有兩種作法,一個針對“無自變量構造方法”,一個針對“帶參數構造方法”。如果欲調用的是“帶參數ctor“就比較麻煩些,不再調用Class的newInstance(),而是調用Constructor 的newInstance()。首先準備一個Class[]做爲ctor的參數類型(本例指定爲一個double和一個int),然後以此爲自變量調用getConstructor(),獲得一個專屬ctor。接下來再準備一個Object[] 做爲ctor實參值(本例指定3.14159和125),調用上述專屬ctor的newInstance()。

動態生成“Class object 所對應之class”的對象實體;無自變量。

運行時調用methods

這個動作和上述調用“帶參數之ctor”相當類似。首先準備一個Class[]做爲參數類型(本例指定其中一個是String,另一個是Hashtable),然後以此爲自變量調用getMethod(),獲得特定的Method object。接下來準備一個Object[]放置自變量,然後調用上述所得之特定Method object的invoke()。爲什麼獲得Method object時不需指定回返類型?

因爲method overloading機制要求signature必須唯一,而回返類型並非signature的一個成份。換句話說,只要指定了method名稱和參數列,就一定指出了一個獨一無二的method。

運行時變更fields內容

與先前兩個動作相比,“變更field內容”輕鬆多了,因爲它不需要參數和自變量。首先調用Class的getField()並指定field名稱。獲得特定的Field object之後便可直接調用Field的get()和set(),

代理模式

代理模式的作用是:爲其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個客戶不想或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用

代理模式一般涉及到的角色有 抽象角色:聲明真實對象和代理對象的共同接口代理角色:代理對象角色內部含有對真實對象的引用,從而可以操作真實對象,同時代理對象提供與真實對象相同的接口以便在任何時刻都能代替真實對象。同時,代理對象可以在執行真實對象操作時,附加其他的操作,相當於對真實對象進行封裝真實角色:代理角色所代表的真實對象,是我們最終要引用的對象

1、Subject.java

abstract public class Subject{
    abstract public void request();
}

2、RealSubject.java
public class RealSubject extends Subject{

    public RealSubject()
    {
    }

    public void request()
    {
        System.out.println("From real subject.");
    }

}
3、ProxySubject

public class ProxySubject extends Subject

{
    private RealSubject realSubject; // 以真實角色作爲代理角色的屬性

    public ProxySubject()
    {
    }

    public void request() // 該方法封裝了真實對象的request方法

    {
        preRequest();

        if (realSubject == null)
        {
            realSubject = new RealSubject();
        }

        realSubject.request(); // 此處執行真實對象的request方法

        postRequest();
    }

    private void preRequest()
    {

        // something you want to do before requesting

    }

    private void postRequest()
    {

        // something you want to do after requesting

    }

}

<pre name="code" class="java">Client.java

public class Client
{
    public static void main(String[] args)
    {
        Subject sub = new ProxySubject();

        sub.request();
    }




由以上代碼可以看出,客戶實際需要調用的是RealSubject類的request()方法,現在用ProxySubject來代理 RealSubject類,同樣達到目的,同時還封裝了其他方法(preRequest(),postRequest()),可以處理一些其他問題。 另外,如果要按照上述的方法使用代理模式,那麼真實角色必須是事先已經存在的,並將其作爲代理對象的內部屬性。但是實際使用時,一個真實角色必須對應一個 代理角色,如果大量使用會導致類的急劇膨脹;此外,如果事先並不知道真實角色,該如何使用代理呢?這個問題可以通過Java的動態代理類來解決

動態代理類

Java動態代理類位於java.lang.reflect包下,一般主要涉及到以下兩個類: (1)Interface InvocationHandler:該接口中僅定義了一個方法public object invoke(Object obj,Method method, Object[] args)在實際使用時,第一個參數obj一般是指代理類,method是被代理的方法,如上例中的request(),args爲該方法的參數數組。 這個抽象方法在代理類中動態實現。(2)Proxy:該類即爲動態代理類,作用類似於上例中的ProxySubject,其中主要包含以下內容

protected Proxy(InvocationHandler h):構造函數,用於給內部的h賦值。 static Class getProxyClass (ClassLoader loader, Class[] interfaces):獲得一個代理類,其中loader是類裝載器,interfaces是真實類所擁有的全部接口的數組。static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理類的一個實例,返回後的代理類可以當作被代理類使用(可使用被代理類的在Subject接口中聲明過的方法)

所謂Dynamic Proxy是這樣一種class:它是在運行時生成的class,在生成它時你必須提供一組interface給它,然後該class就宣稱它實現了這些 interface。你當然可以把該class的實例當作這些interface中的任何一個來用。當然,這個Dynamic Proxy其實就是一個Proxy,它不會替你作實質性的工作,在生成它的實例時你必須提供一個handler,由它接管實際的工作

在使用動態代理類時,我們必須實現InvocationHandler接口

public interface Subject
{
    abstract public void request();
}

//具體角色

public class RealSubject implements Subject
{

    public RealSubject()
    {
    }

    public void request()
    {
        System.out.println("From real subject.");
    }

}

//代理處理器 

/**
 * 該代理類的內部屬性爲Object類,實際使用時通過該類的構造函數DynamicSubject(Object obj)對其賦值;
 * 此外,在該類還實現了invoke方法,該方法中的 method.invoke(sub,args);
 * 其實就是調用被代理對象的將要被執行的方法,方法參數sub是實際的被代理對象,
 * args爲執行被代理對象相應操作所需的參數。
 * 通過動態代理類,我們可以在調用之前或之後執行一些相關操作
 */

public class DynamicSubject implements InvocationHandler
{
    private Object sub;

    public DynamicSubject()
    {
    }

    public DynamicSubject(Object obj)
    {
        sub = obj;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        System.out.println("before calling " + method);

        method.invoke(sub, args);

        System.out.println("after calling " + method);

        return null;
    }

}

public class Client
{
    static public void main(String[] args) throws Throwable
    {

        RealSubject rs = new RealSubject(); // 在這裏指定被代理類
        InvocationHandler ds = new DynamicSubject(rs);
        Class<?> cls = rs.getClass();

        // 以下是一次性生成代理

        Subject subject = (Subject) Proxy.newProxyInstance(
                cls.getClassLoader(), cls.getInterfaces(), ds);

        subject.request();
    }
}
getClassLoader,類加載器,把類加載到內存中


通過這種方式,被代理的對象(RealSubject)可以在運行時動態改變,需要控制的接口(Subject接口)可以在運行時改變,控制的方式(DynamicSubject類)也可以動態改變,從而實現了非常靈活的動態代理關係

1.創建一個實現接口InvocationHandler的類,它必須實現invoke方法
2.創建被代理的類以及接口
3.通過Proxy的靜態方法
newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 創建一個代理
4.通過代理調用方法


例子

public interface Foo
{
    void doAction();
}

public class FooImpl implements Foo
{
    public FooImpl()
    {
    }

    public void doAction()
    {
        System.out.println("in FooImp1.doAction()");
    }
}

public class FooImpl2 implements Foo
{
    public FooImpl2()
    {
    }

    public void doAction()
    {
        System.out.println("in FooImp2.doAction()");
    }

} 

public class CommonInvocationHandler implements InvocationHandler
{

    // 動態執行對象,需要回調的對象
    private Object target;

    // 支持構造子注射
    public CommonInvocationHandler()
    {

    }

    // 支持構造子注射
    public CommonInvocationHandler(Object target)
    {
        setTarget(target);
    }

    /**
     * 
     * 採用setter方法注射
     * 
     * @param target
     * 
     */
    public void setTarget(Object target)
    {
        this.target = target;
    }

    /**
     * 
     * 調用proxy中指定的方法method,並傳入參數列表args
     * 
     * @param proxy
     *            代理類的類型,例如定義對應method的代理接口
     * 
     * @param method
     *            被代理的方法
     * 
     * @param args
     *            調用被代理方法的參數
     * 
     * @return
     * 
     * @throws java.lang.Throwable
     * 
     */

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        return method.invoke(target, args);
    }

}

public class Demo
{
    public static void main(String[] args)
    {

        // 1.通用的動態代理實現

        CommonInvocationHandler handler = new CommonInvocationHandler();

        Foo f;

        // 2.接口實現1

        handler.setTarget(new FooImpl());

        // 方法參數說明:代理類、代理類實現的接口列表、代理類的處理器

        // 關聯代理類、代理類中接口方法、處理器,當代理類中接口方法被調用時,會自動分發到處理器的invoke方法

        // 如果代理類沒有實現指定接口列表,會拋出非法參數異常

        f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),

        new Class[] { Foo.class },

        handler);

        f.doAction();

        // 3.接口實現2

        handler.setTarget(new FooImpl2());

        f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),

        new Class[] { Foo.class },

        handler);

        f.doAction();
    }
}












發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章