1. 程式人生 > >Java程序語言的後門-反射機制

Java程序語言的後門-反射機制

異常 proxy row 構造 () 程序語言 lean class類 不可

在文章JAVA設計模式-動態代理(Proxy)示例及說明和JAVA設計模式-動態代理(Proxy)源碼分析都提到了反射這個概念。

// 通過反射機制,通知力宏做事情
method.invoke(object, args);
// 通過反射,將h作為參數,實例化代理類,返回代理類實例。
return cons.newInstance(new Object[]{h});
而且在

// 將接口類對象數組clone一份。
final Class<?>[] intfs = interfaces.clone();
也提到一個類對象數組的概念,如果你不知道反射,不知道類對象,那麽你在閱讀者兩篇文章的時候,很可能就會雨裏霧裏,不知所然。通過這篇文章你就能很輕松的掌握類對象和反射。

反射是java語言中一個很基礎,很簡單的知識,不僅僅是在工作中會用到,而且也會經常出現在面試中。如果你認真閱讀本文,對你在技術層面來說又是一個提升。

一,前言

  反射是什麽呢?其實是jiava程序語言的一種機制,我理解為是java的後門。在其他文章中給出的定義和解釋都比較晦澀難懂,不如先看一下具體的代碼,再去理解,這樣就容易很多。

  為了更好的理解反射機制,不得不提到類對象的概念,為了不與類和對象的概念搞混,我們就首先看一下類和對象的概念。相信你已經非常熟悉類和對象的概念了,那麽我就簡單的描述一下類與對象的概念:

    類:一個或一組事物的抽象描述,例如狗是對狗狗這一組事物的抽象描述。

    對象:具體的某一個事物,例如哈士奇等,也可以說是類的一個實例。

  那麽類對象是什麽呢?

    在java中一切皆對象。既然一切皆對象,當然類也是一種對象,那麽類的對象類型是什麽呢?是java.lang.Class,你也許在其他的地方見到過。

  那麽類對象是從哪裏來的,怎麽創建的呢?

    我們都知道,想要得到一個類的對象,最基本的方法就是通過new關鍵字,去實例化一個對象。但類對象是一個特殊的對象,自然不能使用new關鍵字,翻看Class類的源碼就可以證明:

/*
 * 私有化構造方法,只有java 虛擬機才能創建類對象
 * Private constructor. Only the Java Virtual Machine creates Class objects.
 * This constructor is not used and prevents the default constructor being
 * generated.
 */
private Class(ClassLoader loader) {
    // Initialize final field for classLoader.  The initialization value of non-null
    // prevents future JIT optimizations from assuming this final field is null.
    classLoader = loader;
}

  Class類中只有這一個私有的構造方法。其實類對象是java虛擬機(JVM)在加載class文件的時候自動在虛擬機內存中給我們創建的。

    這裏就涉及到了另外一個機制:類加載機制。

    簡單描述一下類加載機制:就是虛擬機將class文件加載到虛擬機內存中,最終形成可以被虛擬機直接使用的java類型。虛擬機會將class文件中的信息按照所需的存儲格式放在方法區中,同時會在內存中(HotSpot是在堆內存中)實例化一個java.lang.Class類對象。

  類加載機制是一個很復雜的過程,不是本篇文章的重點,就不展開來說。至少到這裏我們已經知道了,類對象是由虛擬機創建的而且HotSpot虛擬機將類對象存放在堆內存中。

  那麽怎麽通過類對象來實現反射呢?為了理解方便,來舉一個有趣的例子

二,一個有趣的例子

  有一天一個同事養了一只狗狗哈士奇,在作者面前大肆炫耀,說他的狗狗多麽萌,多麽威武,多麽聽話......,作者聽完心生向往,提出去看一看。但是這個同事高傲的擡起頭說了三個字:想的美。

  竟然不給我看!!!作者一氣之下,就走了java程序的後門-反射。你不讓我看,我偏偏要看。

  哈士奇類的定義:

package com.zcz.reflecttest;

public class HaShiQi implements Dog {
public String color = "黑色";
private String name = "富貴";

//私有化構造
private HaShiQi() {};
@Override
public void eat() {
    // TODO Auto-generated method stub
    System.out.println(name + " 去吃狗糧");
}

@Override
public void run() {
    // TODO Auto-generated method stub
    System.out.println(name + " 去跑著玩兒");
}

private void dance() {
    System.out.println(name + " 來跳一支舞");
}

@Override
public String toString() {
    // TODO Auto-generated method stub
    return "名字:"+name+";顏色:"+color;
}

}

  可以看到同時的哈士奇實現了Dog接口:

package com.zcz.reflecttest;

public interface Dog {
public void eat();
public void run();
}

  從代碼中可以看到,我的同事不僅僅沒告訴作者他的哈士奇竟然會跳舞,甚至連哈士奇的名字都不想讓作者知道,而且連構造器都私有化了。

  那麽接下來,走後門開始。在上提到類對象,正好我想通過反射看狗狗,也需要用到類對象。那麽接下來第一步就先獲取HaShiQi的類對象。

三,類對象的獲取

  超級簡單:

package com.zcz.reflecttest;

/**

  • 通過反射查看同事狗狗的信息
  • @author zhangchengzi
  • */
    public class LookLook {

    public static void main(String[] args) {
    // TODO Auto-generated method stub
    //獲取類對象,除了這份方法外還有另外兩種方法
    Class clazz = HaShiQi.class;

    }

}

  從這裏開始我們的反射的使用的開始了,先看看HaShiQi類中有哪些屬性吧?

四,獲取屬性

  代碼:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//獲取類對象,除了這份方法外還有另外兩種方法
Class clazz = HaShiQi.class;

    System.out.println("——————— 獲取所有公有的屬性  —————————");
    Field[] fields = clazz.getFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }    
}

  打印結果:

——————— 獲取所有公有的屬性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
  可以看到只有一個color屬性,使用getFields方法是獲取不到私有屬性了,想要獲取私有屬性就要使用下方的方法:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//獲取類對象,除了這份方法外還有另外兩種方法
Class clazz = HaShiQi.class;

    System.out.println("——————— 獲取所有公有的屬性  —————————");
    Field[] fields = clazz.getFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }

    System.out.println("——————— 獲取所有的屬性  —————————");
    fields = clazz.getDeclaredFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }
}

  打印結果:

——————— 獲取所有公有的屬性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
——————— 獲取所有的屬性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
private java.lang.String com.zcz.reflecttest.HaShiQi.name
  可以看到,HaShiQi類中私有(private修飾的)的name屬性打印出來了。

五,獲取方法

  代碼:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//獲取類對象,除了這份方法外還有另外兩種方法
Class clazz = HaShiQi.class;

    System.out.println("——————— 獲取所有公有的屬性  —————————");
    Field[] fields = clazz.getFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }

    System.out.println("——————— 獲取所有的屬性  —————————");
    fields = clazz.getDeclaredFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }
    System.out.println("——————— 獲取所有公有的方法  —————————");
    Method[] methods = clazz.getMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
}

  打印結果:

——————— 獲取所有公有的屬性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
——————— 獲取所有的屬性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
private java.lang.String com.zcz.reflecttest.HaShiQi.name
——————— 獲取所有公有的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
//重寫的toString 方法
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
public void com.zcz.reflecttest.HaShiQi.eat()
//Object類中的方法
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

  從結果中可以發現,getMethod方法也是只能獲取到公有的方法,而且連Object中的方法也獲取到了。跟獲取屬性也是一樣的,也有方法可以獲取到HaShiQi類中所有的方法:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//獲取類對象,除了這份方法外還有另外兩種方法
Class clazz = HaShiQi.class;

    System.out.println("——————— 獲取所有公有的屬性  —————————");
    Field[] fields = clazz.getFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }

    System.out.println("——————— 獲取所有的屬性  —————————");
    fields = clazz.getDeclaredFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }
    System.out.println("——————— 獲取所有公有的方法  —————————");
    Method[] methods = clazz.getMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
    System.out.println("——————— 獲取所有類中聲明的方法  —————————");
    methods = clazz.getDeclaredMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
}

  打印結果:

——————— 獲取所有公有的屬性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
——————— 獲取所有的屬性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
private java.lang.String com.zcz.reflecttest.HaShiQi.name
——————— 獲取所有公有的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
public void com.zcz.reflecttest.HaShiQi.eat()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
——————— 獲取所有類中聲明的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
private void com.zcz.reflecttest.HaShiQi.dance()
public void com.zcz.reflecttest.HaShiQi.eat()

  這樣就可以同時獲取到HaShiQi類中的私有方法(private修飾的)dance了。

六,獲取構造器

  代碼:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//獲取類對象,除了這份方法外還有另外兩種方法
Class clazz = HaShiQi.class;

    System.out.println("——————— 獲取所有公有的屬性  —————————");
    Field[] fields = clazz.getFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }

    System.out.println("——————— 獲取所有的屬性  —————————");
    fields = clazz.getDeclaredFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }
    System.out.println("——————— 獲取所有公有的方法  —————————");
    Method[] methods = clazz.getMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
    System.out.println("——————— 獲取所有類中聲明的方法  —————————");
    methods = clazz.getDeclaredMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
    System.out.println("——————— 獲取所有公有的構造器  —————————");
    Constructor[] constructors = clazz.getConstructors();
    for(Constructor constructor : constructors) {
        System.out.println(constructor);
    }

}

  打印結果:

——————— 獲取所有公有的屬性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
——————— 獲取所有的屬性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
private java.lang.String com.zcz.reflecttest.HaShiQi.name
——————— 獲取所有公有的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
public void com.zcz.reflecttest.HaShiQi.eat()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
——————— 獲取所有類中聲明的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
private void com.zcz.reflecttest.HaShiQi.dance()
public void com.zcz.reflecttest.HaShiQi.eat()
——————— 獲取所有公有的構造器 —————————

  因為HaShiQi類中沒有公有的構造器,所以這裏什麽都沒有打印出來。自然也有獲取到私有構造器的方法:

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// TODO Auto-generated method stub
//獲取類對象,除了這份方法外還有另外兩種方法
Class clazz = HaShiQi.class;

    System.out.println("——————— 獲取所有公有的屬性  —————————");
    Field[] fields = clazz.getFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }

    System.out.println("——————— 獲取所有的屬性  —————————");
    fields = clazz.getDeclaredFields();
    for(Field field : fields) {
        field.setAccessible(true);
        System.out.println(field);
    }
    System.out.println("——————— 獲取所有公有的方法  —————————");
    Method[] methods = clazz.getMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
    System.out.println("——————— 獲取所有類中聲明的方法  —————————");
    methods = clazz.getDeclaredMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
    System.out.println("——————— 獲取所有公有的構造器  —————————");
    Constructor[] constructors = clazz.getConstructors();
    for(Constructor constructor : constructors) {
        System.out.println(constructor);
    }
    System.out.println("——————— 獲取所有的構造器  —————————");
    constructors = clazz.getDeclaredConstructors();
    for(Constructor constructor : constructors) {
        System.out.println(constructor);
    }
}

  打印結果:

——————— 獲取所有公有的屬性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
——————— 獲取所有的屬性 —————————
public java.lang.String com.zcz.reflecttest.HaShiQi.color
private java.lang.String com.zcz.reflecttest.HaShiQi.name
——————— 獲取所有公有的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
public void com.zcz.reflecttest.HaShiQi.eat()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
——————— 獲取所有類中聲明的方法 —————————
public void com.zcz.reflecttest.HaShiQi.run()
public java.lang.String com.zcz.reflecttest.HaShiQi.toString()
private void com.zcz.reflecttest.HaShiQi.dance()
public void com.zcz.reflecttest.HaShiQi.eat()
——————— 獲取所有公有的構造器 —————————
——————— 獲取所有的構造器 —————————
private com.zcz.reflecttest.HaShiQi()

七,實例化對象

  HaSHiQi類中的信息,包括屬性,方法,構造器,我們都已經通過反射瀏覽了一遍,那麽接下來就要再使用反射實例化HaShiQi類的對象了,因為只有實例對象才能調用屬性和方法。

  因為HaShiQi類中只顯示聲明了一個空參構造器,所以我們只能使用這個構造器來實例化對象。

  正常情況下獲取指定參數的構造器,需要使用方法clazz.getConstructor(parameterTypes(參數類對象數組))。但是HaShiQi的構造方法是私有的,所以使用這個方法去獲取構造器會報錯:

Constructor cons = clazz.getConstructor();
Exception in thread "main" java.lang.NoSuchMethodException: com.zcz.reflecttest.HaShiQi.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getConstructor(Class.java:1825)
at com.zcz.reflecttest.LookLook.main(LookLook.java:61)
  所以我們使用另外一個方法解決這個問題:

    // 實例化對象
    // 獲取構造器
    Constructor con = clazz.getDeclaredConstructor();
    // 強制設置為可以訪問
    con.setAccessible(true);
    HaShiQi haShiQi = (HaShiQi)con.newInstance();
    System.out.println("沒有做任何修改前:"+haShiQi.toString());

  打印結果:  

沒有做任何修改前:名字:富貴;顏色:黑色
  哈哈,機智如我,怎麽能被這種問題打到,從代碼中可以看到我們使用了構造器的newInstance方法實例化了一個HaShiQi對象。

    代碼:con.newInstance();

    是不是很熟悉?,在文章JAVA設計模式-動態代理(Proxy)源碼分析中,實例化代理類對象的時候,也使用到了:

// 通過反射,將h作為參數,實例化代理類,返回代理類實例。
return cons.newInstance(new Object[]{h});
  接下來就是訪問對象的屬性和方法了如果我們把所有的屬性和方法都訪問到,不就把同事的狗狗看了一遍了嗎?。

八,修改屬性

    // 修改狗狗的顏色
    // 獲取狗狗的color屬性
    Field filed = clazz.getField("color");
    filed.set(haShiQi, "紅色");
    System.out.println("修改狗狗的顏色:"+haShiQi.toString());

  打印結果: 

沒有做任何修改前:名字:富貴;顏色:黑色
修改狗狗的顏色:名字:富貴;顏色:紅色
  修改成功,接著修改狗狗的名字:

// 修改狗狗的顏色
// 獲取狗狗的color屬性
Field filed = clazz.getField("color");
filed.set(haShiQi, "紅色");
System.out.println("修改狗狗的顏色:"+haShiQi.toString());
// 修改狗狗的名字
filed = clazz.getDeclaredField("name");
// 強制設置為可以訪問
filed.setAccessible(true);
filed.set(haShiQi, "驚喜");
System.out.println("修改狗狗的名字:"+haShiQi.toString());

  打印結果:

沒有做任何修改前:名字:富貴;顏色:黑色
修改狗狗的顏色:名字:富貴;顏色:紅色
修改狗狗的名字:名字:驚喜;顏色:紅色
  修改成功,但是千萬要註意是的是的getDeclaredField方法,而不是getField方法,使用getField方法會拋出異常:Exception in thread "main" java.lang.NoSuchFieldException: name;

  同時filed.setAccessible(true);也是必不可少了,否則將拋出異常:Exception in thread "main" java.lang.IllegalAccessException: Class com.zcz.reflecttest.LookLook can not access a member of class com.zcz.reflecttest.HaShiQi with modifiers "private"

  屬性修改完,我們就開始調用方法吧。

九,調用方法

    //調用run方法
    Method method = clazz.getMethod("run");
    method.invoke(haShiQi, null);

  打印結果:

驚喜 去跑著玩兒
  調用成功;

    代碼:method.invoke(haShiQi, null);

  是不是覺得這句代碼也是很熟悉的?在文章JAVA設計模式-動態代理(Proxy)示例及說明中也有相同的用法:

// 通過反射機制,通知力宏做事情
method.invoke(object, args);
  我們繼續,eat方法和run方法一樣,都是公有的方法,這裏就不再演示了,接下來演示一下私有的dance方法:

    //調用run方法
    Method method = clazz.getMethod("run");
    method.invoke(haShiQi, null);
    //調用dance方法
    method = clazz.getDeclaredMethod("dance");
    method.setAccessible(true);
    method.invoke(haShiQi, null);

  打印結果:

驚喜 去跑著玩兒
驚喜 來跳一支舞
  也同樣成功了,但是也要註意getDeclaredMethod方法和method.setAccessible(true);

  到這裏,同事不讓我看的狗狗,我通過java的反射機制從裏到外的全部都看了一遍,不僅僅是看了一遍,我還給他的狗狗改了顏色和名字。調用了所有的方法。是不是很神奇,很有成就感?

十,總結

  反射的基本用法已經都在上面的實例中演示到了,但是反射還是有其他的方法在這裏並沒有提到,有機會的話繼續補充。

  在上面的代碼過程中,你也許也發現了,不僅僅是有類對象,而且還有屬性,方法和構造器都是對象,屬性是java.lang.reflect.Field類的對象,方法是java.lang.reflect.Method類的對象,構造器是java.lang.reflect.Constructor類的對象。看來在java中確實是一切皆對象。

Java程序語言的後門-反射機制