05.20 初識Java Reflection

內容導讀

學習Java的時間也過了這麼久了,反射這個名詞耳熟於心,在與小夥伴討論時也能時常提起。反射帶給我們的程序很多便利,譬如運行期間檢查類、接口、變量或者方法等信息。注意第三種獲取Class對象的方式傳入的字符串需要 完整的包名以及類名 。Pig類的字段是私有的,即只有Pig對應的對象能訪問,所以通過普通的 pigClass.getFields() 並不能獲取其field數組,通過getDeclaredFields()方法即可獲取其私有字段。同上,若是一個類/方法/字段上面有泛型數據,反射能幫我們拿到已經處於運行期時候的泛型信息嗎?可以看到只有將返回的代理類實例強轉為接口類型,我們才能調用委託出去的方法。

學習Java的時間也過了這麼久了,反射這個名詞耳熟於心,在與小夥伴討論時也能時常提起。說來慚愧,一直以來浮在技術表面,總是以瞭解了某個新技術的名詞為傲,但當被問起其底層實現時卻無話可說。為了改變現狀,同時記錄自己學習過程,希望通過簡書這個平臺寫下自己對技術的認識。接下來這篇文章我將談談對反射的認識、反射能獲取的信息、反射的應用。那麼,讓我們開始挖掘反射相關的知識點吧^_^

關於反射的理解

談及反射之前我想說說對象在Java中是怎樣存在的以及存放在哪裡。想必很多初學Java的小夥伴會接觸到對象這個概念,Java是面向對象的語言,Java程序就是通過一個又一個對象構建起來的。我們可以通過Object o = new Object()直接生成對象,那麼這個o對象是存放在哪裡的呢?

如果瞭解過JMM的同學肯定會很清楚,對象是存放在Java堆上的,我們可以通過直接new Object()的方式在堆上生成一個對象,這種方式生成的對象是在編譯時就確定了的,那麼有沒有可以讓我們在Java程序運行時生成對象的方法?沒錯,通過反射我們就可以在運行期動態的生成一個對象了

反射帶給我們的程序很多便利,譬如運行期間檢查類、接口、變量或者方法等信息。我能通過反射做什麼呢?對一個運行期對象的值進行改變,也可以拿到類方法操作對象,是不是很有趣額 ~( ̄0 ̄)/

反射獲取到的信息

我認為的最重要的Class對象,有了這個對象,我們就可以為非作歹了(⊙ ▽ ⊙)哈哈不要想歪了,我只是找不到形容詞來描述它了。我們可以通過如下三種方式獲取一個類的Class對象:

  1. Class pigClass = Pig.class;
  2. Class pigClass = new Pig().getClass();
  3. Class pigClass = Class.forName("Pig");

注意第三種獲取Class對象的方式傳入的字符串需要完整的包名以及類名

既然這個Class對象那麼滴重要,What can it take for us?

構造器

通過類的構造器,我們可以生成一個對象。在java.lang.reflect包下有一個Constructor類,這個類生成的實例可以存放關於構造器的一些信息。

通過上面pigClass對象獲取的Constructor對象存放構造器的一些信息如下所示:

 public String pigConstructorTest() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
Class pigClass = Pig.class;
//get constructor from pigClass
Constructor pigConstructor = pigClass.getDeclaredConstructor();
//access private-constructor
pigConstructor.setAccessible(true);
//new-instance through constructor
Pig pig = pigConstructor.newInstance();
return pig.getAge() + " " + pig.getWeight() + " " + pigConstructor.getName();
}

這裡調用的是無參構造器,所以生成的對象初始值為默認值。Constructor還有一些譬如獲取構造器修飾符、構造器參數類型、構造器參數個數等方法。

字段

能夠獲取到類的字段,然後改變運行期對象的值是不是很cool喔(≧▽≦)/

 public void pigFiledTest() throws NoSuchFieldException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
//get class's all fields
Field[] fields = pigClass.getDeclaredFields();
//get one field through certain string
Field weight = pigClass.getDeclaredField("weight");
weight.setAccessible(true);
for (Field f :
fields) {
System.out.print(f.getType() + " " + f.getName());
System.out.println();
}
Pig pig = pigConstructorTest();
System.out.print(weight.get(pig) + " after filed setting");
weight.set(pig,1);

System.out.print(" "+ weight.get(pig));
}

Pig類的字段是私有的,即只有Pig對應的對象能訪問,所以通過普通的pigClass.getFields()並不能獲取其field數組,通過getDeclaredFields()方法即可獲取其私有字段。getDeclaredFields()與getDeclaredField()的區別是一個獲取所有字段,一個根據傳入的字段名獲取相應的field對象。Field字段對應的實例還可以在運行期間給改變對象的私有屬性,上面的代碼運行後結果如下:

int age
int weight
0 after filed setting 1

方法

 public void pigMethodTest() {
//same to field,the getMethods() can not obtain private-method
Method[] methods =
pigClass.getMethods();
for (Method method : methods) {
System.out.print(method.getName() + " " + Arrays.toString(method.getParameters()));
System.out.println();
}
}

同樣的,要想獲取類的私有方法只有通過getDeclaredMethods()。下面來看看method的一個比較重要的方法:

 public void pigMethodInvoke() throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Method setAge = pigClass.getMethod("setAge", int.class);
Pig pig = pigConstructorTest();
System.out.println("before method invoke:" + pig.getAge());
setAge.invoke(pig,10);
System.out.println("after method invoke:" + pig.getAge());
}

對應的輸出為:

before method invoke:0
after method invoke:10

可以看到,通過setAge.invoke(pig,10);我們成功地將豬的年齡設置為0。invoke方法的使用需要傳入兩個參數:

  1. Object obj,需要對方法進行調用的對象,如果該對象中沒有該方法那麼會拋出一個IllegalArgumentException:object is not an instance of declaring class。
  2. Object... args,方法需要傳遞的實際參數。

其他

反射能夠帶給我們,如果一個類/方法/字段上面打上了註解,那麼我們也可以通過反射拿到對應的註解內容。同上,若是一個類/方法/字段上面有泛型數據,反射能幫我們拿到已經處於運行期時候的泛型信息嗎?答案是肯定的,畢竟反射這個利器能夠帶給我們的好處不是一星半點。反射的功能太多了這裡我就不一一演示了~~~

反射的應用

我初次對反射感興趣是在查看AOP相關的知識時,對於invoke方法裡面的參數以及method.invoke()到底是個什麼鬼一竅不通/(ㄒoㄒ)/~~

看過別人寫的博客,瞭解AOP、反射相關的知識後大概有了些理解,下面來看看AOP動態代理的基於接口的實現:

public interface BookFacade {
void addBook();
}

所有基於JDK實現的動態代理都需要一個接口,其次是具體的委託類:

public class BookFacadeImpl implements BookFacade {
@Override
public void addBook() {
System.out.println("增加圖書方法。。。");
}
}

這個addBook()方法就是我們需要委託出去的方法,我們怎麼將這個方法委託出去呢?需要用到一個代理類進行:

public class BookFacadeProxy implements InvocationHandler {
/**
* 不確定委託類是誰,被代理對象
*/
private Object target;

public BookFacadeProxy(Object target){
this.target = target;
}

public BookFacadeProxy(){}

/**
* 綁定一個委託類,並返回代理類的實例
* @param target
* @return
*/
public Object createProxy(Object target) {
this.target = target;
//需要target實現了接口
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces()

, this);
}
/**
*
* @param proxy 代理對象
* @param method 委託類實例的方法
* @param args 委託類方法參數
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before proxy");
//target是要調用method方法的對象,args是傳入的參數
//這裡的method是BookFacade的addBook()方法
//result返回invoke執行結果,可能為null
Object result = method.invoke(target,args);
System.out.println("after proxy");
//從委託類方法調用返回的值
return result;
}
}

通過實現InvocationHandler類並重寫其invoke方法來實現對委託類委託的方法增強。具體的看看invoke方法,

  1. 方法參數Object proxy,這個參數一般是用不到的;Method method是委託類具體想要執行的方法,Object[] args是方法傳入的實參。
  2. method.invoke(target,args);這裡的target就是傳入的委託類實際對象。

對於這個類的createProxy()方法,其實沒有必要非得在代理類裡面實現,裡面的Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces() , this)主要用於生成具體的代理對象,this傳遞的是BookFacadeProxy代理類本身的實例,不過想要調用委託類的委託方法,需要將返回的對象強轉為接口類型:

public class BookFacadeTest {
public static void main(String[] args) {
BookFacadeProxy proxy = new BookFacadeProxy();
BookFacade bookFacade = (BookFacade) proxy.createProxy(new BookFacadeImpl());
bookFacade.addBook();

BookFacadeImpl target = new BookFacadeImpl();
BookFacade newProxyInstance = (BookFacade) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new BookFacadeProxy(target));
newProxyInstance.addBook();
}
}

可以看到只有將返回的代理類實例強轉為接口類型,我們才能調用委託出去的方法。

有了動態代理,我們能夠對一個實現了接口的類的方法的使用做一些增強,在方法的運行的前後做一些處理。

後記

天之道,損有餘而補不足,是故虛勝實,不足勝有餘。

漫漫代碼路,磕磕盼盼Review,在搞技術的這條路上,希望你我都能去除浮躁內心。


分享到:


相關文章: