什么是反射
反射是能夠讓java代碼訪問一個已經(jīng)加載的類的字段割择,變量,方法和構(gòu)造器等信息慎式,并能夠訪問他們不受訪問權(quán)限的控制安接,即能訪問私有構(gòu)造方法屬性等
什么是Class
Class
因為 Java 是面向?qū)ο蟮恼Z言,基本上是以類為基礎(chǔ)構(gòu)造了整個程序系統(tǒng)雾家,反射中要求提供的規(guī)格說明書其實就是一個類的規(guī)格說明書铃彰,它就是 Class。
注意的是 Class 是首字母大寫榜贴,不同于 class 小寫豌研,class 是定義類的關(guān)鍵字,而 Class 的本質(zhì)也是一個類唬党,因為在 Java 中一切都是對象鹃共。
Class 就是一個對象,它用來代表運行在 Java 虛擬機(jī)中的類和接口驶拱。
把 Java 虛擬機(jī)類似于高速公路霜浴,那么 Class 就是用來描述公路上飛馳的汽車,也就是我前面提到的規(guī)格說明書蓝纲。
獲取反射對象的三種方法
反射的入口是 Class阴孟,但是反射中 Class 是沒有公開的構(gòu)造方法的,所以就沒有辦法像創(chuàng)建一個類一樣通過 new 關(guān)鍵字來獲取一個 Class 對象
任何一個類都是Class類的實例對象税迷,這個實例對象有三種表示方式:(我們新建一個Student類)
Class c1 = Student.class;//實際告訴我們?nèi)魏我粋€類都有一個隱含的靜態(tài)成員變量class(知道類名時用)
Class c2 = stu.getClass();//已知該類的對象通過getClass方法(知道對象時用) Student是一個類永丝,stu是它的對象,通過 stu.getClass() 就獲取到了 Car 這個類的 Class 對象
Class c3 = Class.forName("類的全名");//會有一個ClassNotFoundException異常
有時候箭养,我們沒有辦法創(chuàng)建一個類的實例慕嚷,甚至沒有辦法用 Student.class 這樣的方式去獲取一個類的 Class 對象。這在 Android 開發(fā)領(lǐng)域很常見,因為某種目的喝检,Android 工程師把一些類加上了 @hide 注解嗅辣,所示這些類就沒有出現(xiàn)在 SDK 當(dāng)中,Java 給我們提供了 Class.forName() 這個方法挠说。
只要給這個方法中傳入一個類的全限定名稱就好了澡谭,那么它就會到 Java 虛擬機(jī)中去尋找這個類有沒有被加載。
當(dāng)我們執(zhí)行System.out.println(c1==c2);語句损俭,結(jié)果返回的是true蛙奖,這是為什么呢?原因是不管c1還是c2都代表了Student類的類類型杆兵,一個類可能是Class類的一個實例對象外永。
我們完全可以通過類的類類型創(chuàng)建該類的對象實例,即通過c1或c2創(chuàng)建Student的實例拧咳。
Student stu = (Student)c1.newInstance();//前提是必須要有無參的構(gòu)造方法,因為該語句會去調(diào)用其無參構(gòu)造方法囚灼。該語句會拋出異常骆膝。
1.Class clazz = xxx.class 只會觸發(fā)類的加載,不會觸發(fā)初始化
2.Class clazz = new xxx().getClass()
- Class.forName(“全限定名稱com.shadow.Hello”) 2,3方法都同時觸發(fā)類的加載和初始化灶体。阅签。 全限定名稱,它包括包名+類名
動態(tài)加載類和靜態(tài)加載類
①.編譯時加載類是靜態(tài)加載類
new 創(chuàng)建對象是靜態(tài)加載類蝎抽,在編譯時刻就需要加載所有可用使用到的類政钟,如果有一個用不了,那么整個文件都無法通過編譯
②.運行時加載類是動態(tài)加載類
Class c = Class.forName("類的全名")樟结,不僅表示了類的類型养交,還表示了動態(tài)加載類,編譯不會報錯瓢宦,在運行時才會加載碎连,使用接口標(biāo)準(zhǔn)能更方便動態(tài)加載類的實現(xiàn)。
功能性的類盡量使用動態(tài)加載驮履,而不用靜態(tài)加載鱼辙。
很多軟件比如QQ,360的在線升級,并不需要重新編譯文件玫镐,只是動態(tài)的加載新的東西
動態(tài)加載小例子
需求:在不知道的情況下倒戏,可能只加載World也有可能只加載Excel
package shadow.android.com.lib.reflected;
/**
* Author : shadow
* Desc : 動態(tài)加載類的基類
* Date :2018/11/2/002
*/
public interface OfficeBase {
public void start();
}
//==================================
package shadow.android.com.lib.reflected;
/**
* Author : shadow
* Desc :
* Date :2018/11/2/002
*/
public class World implements OfficeBase {
@Override
public void start() {
System.out.println("hello shadow,this is world");
}
}
//============================
package shadow.android.com.lib.reflected;
/**
* Author : shadow
* Desc :
* Date :2018/11/2/002
*/
public class Excel implements OfficeBase {
@Override
public void start() {
System.out.println("hello shadow恐似,this is excel6捧巍!!");
}
}
靜態(tài)加載如下:靜態(tài)加載要求編譯時期World或者Excel必須都在葱椭,然后根據(jù)情況判斷來決定是否要使用捂寿,不然編譯時期就會出錯
package shadow.android.com.lib.reflected;
/**
* Author : shadow
* Desc :靜態(tài)加載測試
* Date :2018/11/2/002
*/
public class StaticLoaderTest {
public static void main(String[] args) {
if ("world".equals(args[0])) {
World world = new World();
world.start();
}
if ("excel".equals(args[0])) {
Excel excel = new Excel();
excel.start();
}
}
}
動態(tài)加載如下:不要求在編譯時刻,World和Excel同時存在孵运,只需要在加載的時候秦陋,根據(jù)需要去加載需要Class,如果加載不到治笨,會拋出ClassNotFoundException
package shadow.android.com.lib.reflected;
/**
* Author : shadow
* Desc :動態(tài)加載測試
* Date :2018/11/2/002
*/
public class DynamicLoaderTest {
public static void main(String[] args){
try {
//動態(tài)加載類驳概,
// Class clazz = Class.forName(args[0]);
Class clazz = Class.forName("shadow.android.com.lib.reflected.World");
//由類類型,創(chuàng)建類的實例
OfficeBase officeBase = (OfficeBase) clazz.newInstance();
//擴(kuò)展類
officeBase.start();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
class對象的操作
如果你想得到一個類的信息旷赖,首先就要獲取該類的類類型顺又。
拿到class對象之后,可以操作一下幾類對象等孵,比如類的實現(xiàn)接口稚照,類的變量,類的方法俯萌,類的構(gòu)造器等果录,class類提供了對應(yīng)的方法來訪問
getName() 返回類類型的名字
getSuperClass() 返回繼承的父類對象
getInterfaces() — 返回當(dāng)前類實現(xiàn)的所有接口(不包括從父類繼承來的)
getClasses() — 返回當(dāng)前類和從父類繼承來的public內(nèi)部類
getDeclaredClasses() — 返回當(dāng)前類的所有內(nèi)部類(包括private類型,但是不包括從父類繼承來的)
getConstructors() — 返回當(dāng)前類所有的public構(gòu)造器
getDeclaredConstructors() — 返回當(dāng)前類所有的構(gòu)造器(包括private類型)
getConstructor(Class<?>… parameterTypes) — 根據(jù)參數(shù)咐熙,返回最匹配的構(gòu)造器對象
getMethods() — 返回當(dāng)前類和從父類繼承來的所有public方法
getDeclaredMethods() — 返回當(dāng)前類所有的Method方法(包括private類型)
getDeclaredMethod(String name, Class<?>… parameterTypes) — 根據(jù)參數(shù)弱恒,返回最匹配的方法
getFields() — 返回當(dāng)前類和從父類繼承來的public字段
getDeclaredFields() — 返回當(dāng)前類定義的所有字段(包括private)
getDeclaredField(String name) —返回當(dāng)前類定義的字段通過參數(shù)
Class類api 有很多,需要用時可以查看api文檔
1.如何獲取某個方法
方法的名稱和方法的參數(shù)列表才能唯一決定某個方法
Method m = c.getDeclaredMethod("方法名"棋恼,可變參數(shù)列表(參數(shù)類型.class))
2.方法的反射操作
m.invoke(對象返弹,參數(shù)列表)
方法如果沒有返回值,返回null爪飘,如果有返回值返回Object類型义起,然后再強(qiáng)制類型轉(zhuǎn)換為原函數(shù)的返回值類型
通過反射,了解泛型只在編譯時期有效
ArrayList list1 =newArrayList();
ArrayList<String> list2 =newArrayList<String>();
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1==c2);//結(jié)果為true悦施,為什么并扇??
結(jié)果分析:因為反射的操作都是編譯之后的操作抡诞,也就是運行時的操作穷蛹,c1==c2返回true,說明編譯之后集合的泛型是去泛型化的昼汗。
那么我們就可以理解為肴熏,Java集合中的泛型,是用于防止錯誤類型元素輸入的顷窒,比如在list2中我們add一個int蛙吏,add(10)就會編譯報錯源哩,那么這個泛型就可以只在編譯階段有效,通過了編譯階段鸦做,泛型就不存在了励烦。可以驗證泼诱,我們繞過編譯坛掠,用反射動態(tài)的在list2中add一個int是可以成功的,只是這時因為list2中存儲了多個不同類型的數(shù)據(jù)(String型治筒,和int型)屉栓,就不能用for-each來遍歷了,會拋出類型轉(zhuǎn)換錯誤異常ClassCastException耸袜。
例子實現(xiàn)
需求:現(xiàn)在有個Apple類友多,它繼承于Fruit類,F(xiàn)ruit有一個私有方法seal 參數(shù)是一個float類型, 現(xiàn)在要求我們通過反射來調(diào)用該私有方法
Fruit.class
public class Fruit {
private int price;
public Fruit(int price) {
this.price = price;
}
private void sale(int price) {
System.out.println("hello shadow ,fruit " + price);
}
}
Apple.class
package shadow.android.com.lib.reflected;
public class Apple extends Fruit{
public Apple(int price) {
super(price);
}
}
反射類
package shadow.android.com.lib.reflected;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Author : shadow
* Desc :
* Date :2018/11/2/002
*/
public class TestReflected {
public static void main(String[] args) {
invokeFruitSale();
}
private static void invokeFruitSale() {
try {
//獲取Apple類
Class clazz = Class.forName("shadow.android.com.lib.reflected.Apple");
//獲取Apple的直接父類Fruit
Class fruitClass = clazz.getSuperclass();
//獲取fruit的私有方法
Method saleMethod = fruitClass.getDeclaredMethod("sale", int.class);
saleMethod.setAccessible(true);
//獲取父類的私有構(gòu)造器
Constructor constructor = fruitClass.getDeclaredConstructor(int.class);
//設(shè)置私有可訪問
constructor.setAccessible(true);
//通過私有構(gòu)造器新建Fruit對象
Fruit fruit = (Fruit) constructor.newInstance(0);
//調(diào)用方法
saleMethod.invoke(fruit, 100);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
例子實現(xiàn)2
這次我們在Fruit類中新建一個Size類堤框,然后新增一個final的Size常量域滥,我們需要利用反射對其進(jìn)行修改(正常final常量初始化之后是不能夠被修改的,但是利用反射能夠做到-蜈抓。-)
fruit類
package shadow.android.com.lib.reflected;
/**
* Author : shadow
* Desc :
* Date :2018/11/2/002
*/
public class Fruit {
private int price;
private final Size size = new Size(50);
public Fruit(int price) {
this.price = price;
}
@Override
public String toString() {
return "fruit size is : " + size;
}
private void sale(int price) {
System.out.println("hello shadow ,fruit " + price);
}
public static class Size {
private int count;
public Size(int count) {
this.count = count;
}
@Override
public String toString() {
return "size = " + count;
}
}
}
執(zhí)行反射的類
private static void modifyFruitSize() {
try {
Class clazz = Class.forName("shadow.android.com.lib.reflected.Apple");
Class fruitClass = clazz.getSuperclass();
Constructor constructor = fruitClass.getDeclaredConstructor(int.class);
constructor.setAccessible(true);
Fruit fruit = (Fruit) constructor.newInstance(0);
//修改之前打印fruit size
System.out.println("before modify fruit size" + fruit);
//獲取私有變量
Field sizeField = fruitClass.getDeclaredField("size");
sizeField.setAccessible(true);
//我們利用反射將size改為非final類型
Field modifierField = sizeField.getClass().getDeclaredField("modifiers");
modifierField.setAccessible(true);
//修改類型
int modifiers = sizeField.getModifiers() & ~Modifier.FINAL;
modifierField.set(sizeField, modifiers);
//設(shè)置新的size
Fruit.Size size = new Fruit.Size(500);
sizeField.set(fruit, size);
System.out.println("after modify fruit size" + fruit);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
例子3 Android Activity 啟動 Hook骗绕。
Android Activity 啟動 Hook
當(dāng)然關(guān)于Android Activity 啟動 Hook的技術(shù)網(wǎng)上很多,也有很多很優(yōu)秀的開源項目资昧,我在這里談這個有點 關(guān)公面前耍大刀的感覺了,但是我在這里談這個只是為了說明 利用反射 我們可以做很多事情荆忍,
所以我就找了這么一個切入點來展示反射能做什么事情格带,權(quán)當(dāng)拋磚引玉。
我們的需求是刹枉,在啟動activity時叽唱,打印一個Log日志,廢話不多說微宝,我們開始棺亭。
我們這里只Hook Activity.startActivity 這種方式的啟動, 看下android framework的相關(guān)源碼
public void startActivity(Intent intent) {
//常用的activity.startActivity方法
this.startActivity(intent, null);
}
//...
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
//...
}
//...
}
private Instrumentation mInstrumentation;
我們看到activity.startActivity方法,最終去執(zhí)行啟動操作會用到mInstrumentation這個私有成員變量蟋软,所以自然想到它是一個很好的Hook點镶摘,分下面三步來走
第一步,先獲取到該Activity的mInstrumentation
第二步岳守,新建一個新的Instrumentation類凄敢,重寫execStartActivity方法,在執(zhí)行父類的方法之前加入我們需要的Log日志
第三步湿痢,將我們新建的新的Instrumentation對象涝缝,設(shè)置給activity
第一步
public static void hook(Activity activity) {
try {
Field instrumentationField = Activity.class.getDeclaredField("mInstrumentation");
instrumentationField.setAccessible(true);
//...
}
}
第二步
public class HookInstrumention extends Instrumentation{
private Instrumentation mTarget;
public HookInstrumention(Instrumentation target) {
this.mTarget = target;
}
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
L.d("before start activity!");
Class superClass = Instrumentation.class;
try {
Method method = superClass.getDeclaredMethod("execStartActivity",
Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class);
method.setAccessible(true);
return (ActivityResult) method.invoke(this.mTarget, who, contextThread, token, target, intent, requestCode, options);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
第三步
public static void hook(Activity activity) {
try {
Field instrumentationField = Activity.class.getDeclaredField("mInstrumentation");
instrumentationField.setAccessible(true);
Instrumentation instrumentation = (Instrumentation) instrumentationField.get(activity);
HookInstrumention hookInstrumention = new HookInstrumention(instrumentation);
instrumentationField.set(activity, hookInstrumention);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
//測試一下:
HookUtil.hook(this);
startActivity(new Intent(MainActivity.this, SecondActivity.class));
//查看log
08-27 14:43:33.391 10298-10298/com.aliouswang.practice.olympic E/hook: before start activity!
08-27 14:43:33.392 10298-10298/com.aliouswang.practice.olympic I/Timeline: Timeline: Activity_launch_request time:427958941 intent:Intent { cmp=com.aliouswang.practice.olympic/.SecondActivity }
可以看到我們在Activity 啟動之前,成功的打印了一條日志!拒逮!
總結(jié)反射
Java 中的反射是非常規(guī)編碼方式罐氨。
Java 反射機(jī)制的操作入口是獲取 Class 文件。 有 Class.forName()滩援、 .class 和 Object.getClass() 3 種栅隐。
獲取 Class 對象后還不夠,需要獲取它的 Members狠怨,包含 Field约啊、Method、Constructor佣赖。
Field 操作主要涉及到類別的獲取恰矩,及數(shù)值的讀取與賦值。
Method 算是反射機(jī)制最核心的內(nèi)容憎蛤,通常的反射都是為了調(diào)用某個 Method 的 invoke() 方法外傅。
通過 Class.newInstance() 和 Constructor.newInstance() 都可以創(chuàng)建類的對象實例,但推薦后者俩檬。因為它適應(yīng)于任何構(gòu)造方法萎胰,而前者只會調(diào)用可見的無參數(shù)的構(gòu)造方法。
數(shù)組和枚舉可以被看成普通的 Class 對待棚辽。
凡事有得必有失技竟,反射也有它的缺點,反射的缺點主要有2點屈藐。
我們通過反射獲得了靈活性榔组,同時也要付出代價,我們會失去編譯器優(yōu)化我們代碼的機(jī)會联逻,這樣我們的代碼執(zhí)行效率會低一些搓扯,但是隨著JDK版本的不斷升級,性能差距在不斷的縮小包归。
反射打破了我們代碼的封裝性锨推,增加了維護(hù)成本。