Android 淺談反射與注解

一扳抽、使用反射獲取類的信息

現(xiàn)在有兩個用于測試的類湖员,代碼很簡單屑迂,我們直接貼出來:

  • FatherClass.java
public class FatherClass {
    public String mFatherName;
    public int mFatherAge;

    public void printFatherMsg(){}
}
  • SonClass.java
public class SonClass extends FatherClass{

    private String mSonName;
    protected int mSonAge;
    public String mSonBirthday;

    public void printSonMsg(){
        System.out.println("Son Msg - name : "
                + mSonName + "; age : " + mSonAge);
    }

    private void setSonName(String name){
        mSonName = name;
    }

    private void setSonAge(int age){
        mSonAge = age;
    }

    private int getSonAge(){
        return mSonAge;
    }

    private String getSonName(){
        return mSonName;
    }
}

1、獲取類的所有變量信息

/**
 * 通過反射獲取類的所有變量
 */
private static void printFields(){
    //1.獲取并輸出類的名稱
    Class mClass = SonClass.class;
    System.out.println("類的名稱:" + mClass.getName());

    //2.1 獲取所有 public 訪問權(quán)限的變量
    // 包括本類聲明的和從父類繼承的
    Field[] fields = mClass.getFields();

    //2.2 獲取所有本類聲明的變量(不問訪問權(quán)限)
    //Field[] fields = mClass.getDeclaredFields();

    //3. 遍歷變量并輸出變量信息
    for (Field field :
            fields) {
        //獲取訪問權(quán)限并輸出
        int modifiers = field.getModifiers();
        System.out.print(Modifier.toString(modifiers) + " ");
        //輸出變量的類型及變量名
        System.out.println(field.getType().getName()
                 + " " + field.getName());
    }
}

以上代碼注釋很詳細栅隐,就不再解釋了塔嬉。需要注意的是注釋中 2.1 的 getFields() 與 2.2的 getDeclaredFields() 之間的區(qū)別,下面分別看一下兩種情況下的輸出租悄〗骶浚看之前強調(diào)一下:SonClass extends FatherClass extends Object

  • 調(diào)用 getFields() 方法,輸出 SonClass 類以及其所繼承的父類( 包括 FatherClass 和 Object ) 的 public 方法泣棋。注:Object 類中沒有成員變量胶哲,所以沒有輸出
  類的名稱:obj.SonClass
  public java.lang.String mSonBirthday
  public java.lang.String mFatherName
  public int mFatherAge
  • 調(diào)用 getDeclaredFields() , 輸出 SonClass 類的所有成員變量潭辈,不問訪問權(quán)限
  類的名稱:obj.SonClass
  private java.lang.String mSonName
  protected int mSonAge
  public java.lang.String mSonBirthday

2鸯屿、獲取類的所有方法信息

/**
 * 通過反射獲取類的所有方法
 */
private static void printMethods(){
    //1.獲取并輸出類的名稱
    Class mClass = SonClass.class;
    System.out.println("類的名稱:" + mClass.getName());

    //2.1 獲取所有 public 訪問權(quán)限的方法
    //包括自己聲明和從父類繼承的
    Method[] mMethods = mClass.getMethods();

    //2.2 獲取所有本類的的方法(不問訪問權(quán)限)
    //Method[] mMethods = mClass.getDeclaredMethods();

    //3.遍歷所有方法
    for (Method method :
            mMethods) {
        //獲取并輸出方法的訪問權(quán)限(Modifiers:修飾符)
        int modifiers = method.getModifiers();
        System.out.print(Modifier.toString(modifiers) + " ");
        //獲取并輸出方法的返回值類型
        Class returnType = method.getReturnType();
        System.out.print(returnType.getName() + " "
                + method.getName() + "( ");
        //獲取并輸出方法的所有參數(shù)
        Parameter[] parameters = method.getParameters();
        for (Parameter parameter:
             parameters) {
            System.out.print(parameter.getType().getName()
                    + " " + parameter.getName() + ",");
        }
        //獲取并輸出方法拋出的異常
        Class[] exceptionTypes = method.getExceptionTypes();
        if (exceptionTypes.length == 0){
            System.out.println(" )");
        }
        else {
            for (Class c : exceptionTypes) {
                System.out.println(" ) throws "
                        + c.getName());
            }
        }
    }
}
  • 調(diào)用 getMethods() 方法 獲取 SonClass 類所有 public 訪問權(quán)限的方法,包括從父類繼承的把敢。打印信息中寄摆,printSonMsg() 方法來自 SonClass 類, printFatherMsg() 來自 FatherClass 類修赞,其余方法來自 Object 類
  類的名稱:obj.SonClass
  public void printSonMsg(  )
  public void printFatherMsg(  )
  public final void wait(  ) throws java.lang.InterruptedException
  public final void wait( long arg0,int arg1, ) throws java.lang.InterruptedException
  public final native void wait( long arg0, ) throws java.lang.InterruptedException
  public boolean equals( java.lang.Object arg0, )
  public java.lang.String toString(  )
  public native int hashCode(  )
  public final native java.lang.Class getClass(  )
  public final native void notify(  )
  public final native void notifyAll(  )
  • 調(diào)用 getDeclaredMethods() 方法
  類的名稱:obj.SonClass
  private int getSonAge(  )
  private void setSonAge( int arg0, )
  public void printSonMsg(  )
  private void setSonName( java.lang.String arg0, )
  private java.lang.String getSonName(  )

3婶恼、訪問或操作類的私有變量和方法

都知道,對象是無法訪問或操作類的私有變量和方法的柏副,但是勾邦,通過反射,我們就可以做到割择。沒錯眷篇,反射可以做到!下面荔泳,讓我們一起探討如何利用反射訪問 類對象的私有方法 以及修改 私有變量或常量蕉饼。

  • TestClass.java
public class TestClass {

    private String MSG = "Original";

    private void privateMethod(String head , int tail){
        System.out.print(head + tail);
    }

    public String getMsg(){
        return MSG;
    }
}

1 訪問私有方法

以訪問 TestClass 類中的私有方法 privateMethod(...) 為例

/**
 * 訪問對象的私有方法
 * 為簡潔代碼虐杯,在方法上拋出總的異常,實際開發(fā)別這樣
 */
private static void getPrivateMethod() throws Exception{
    //1. 獲取 Class 類實例
    TestClass testClass = new TestClass();
    Class mClass = testClass.getClass();

    //2. 獲取私有方法
    //第一個參數(shù)為要獲取的私有方法的名稱
    //第二個為要獲取方法的參數(shù)的類型昧港,參數(shù)為 Class...厦幅,沒有參數(shù)就是null
    //方法參數(shù)也可這么寫 :new Class[]{String.class , int.class}
    Method privateMethod =
            mClass.getDeclaredMethod("privateMethod", String.class, int.class);

    //3. 開始操作方法
    if (privateMethod != null) {
        //獲取私有方法的訪問權(quán)
        //只是獲取訪問權(quán),并不是修改實際權(quán)限
        privateMethod.setAccessible(true);

        //使用 invoke 反射調(diào)用私有方法
        //privateMethod 是獲取到的私有方法
        //testClass 要操作的對象
        //后面兩個參數(shù)傳實參
        privateMethod.invoke(testClass, "Java Reflect ", 666);
    }
}

需要注意的是慨飘,第3步中的 setAccessible(true) 方法确憨,是獲取私有方法的訪問權(quán)限,如果不加會報異常 IllegalAccessException瓤的,因為當(dāng)前方法訪問權(quán)限是“private”的

正常運行后休弃,打印如下,調(diào)用私有方法成功:

Java Reflect 666

2圈膏、修改私有變量

以修改 TestClass 類中的私有變量 MSG 為例塔猾,其初始值為 "Original" ,我們要修改為 "Modified"稽坤。老規(guī)矩丈甸,先上代碼看注釋

/**
 * 修改對象私有變量的值
 * 為簡潔代碼,在方法上拋出總的異常
 */
private static void modifyPrivateFiled() throws Exception {
    //1. 獲取 Class 類實例
    TestClass testClass = new TestClass();
    Class mClass = testClass.getClass();

    //2. 獲取私有變量
    Field privateField = mClass.getDeclaredField("MSG");

    //3. 操作私有變量
    if (privateField != null) {
        //獲取私有變量的訪問權(quán)
        privateField.setAccessible(true);

        //修改私有變量尿褪,并輸出以測試
        System.out.println("Before Modify:MSG = " + testClass.getMsg());

        //調(diào)用 set(object , value) 修改變量的值
        //privateField 是獲取到的私有變量
        //testClass 要操作的對象
        //"Modified" 為要修改成的值
        privateField.set(testClass, "Modified");
        System.out.println("After Modify:MSG = " + testClass.getMsg());
    }
}

此處代碼和訪問私有方法的邏輯差不多睦擂,就不再贅述,從輸出信息看出 修改私有變量 成功:

Before Modify:MSG = Original
After Modify:MSG = Modified

二杖玲、注解

1顿仇、自定義注解

舉個栗子, 結(jié)合例子講解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TestAnnotation {
    String value();
    String[] value2() default "value2";
}

元注解的的意義參考上面的講解, 不再重復(fù), 這里看注解值的寫法,其中默認值是可選的, 可以定義, 也可以不定義:

類型 參數(shù)名() default 默認值;

2、處理運行時注解

Retention的值為RUNTIME時, 注解會保留到運行時, 因此使用反射來解析注解.
使用的注解就是上一步的@TestAnnotation, 解析示例如下:

public class Demo {

    @TestAnnotation("Hello Annotation!")
    private String testAnnotation;

    public static void main(String[] args) {
        try {
            // 獲取要解析的類
            Class cls = Class.forName("myAnnotation.Demo");
            // 拿到所有Field
            Field[] declaredFields = cls.getDeclaredFields();
            for(Field field : declaredFields){
                // 獲取Field上的注解
                TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
                if(annotation != null){
                    // 獲取注解值
                    String value = annotation.value();
                    System.out.println(value);
                }

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

此處只演示了解析成員變量上的注解, 其他類型與此類似

2摆马、解析編譯時注解

解析編譯時注解需要繼承AbstractProcessor類, 實現(xiàn)其抽象方法

public boolean process(Set annotations, RoundEnvironment roundEnv)

該方法返回ture表示該注解已經(jīng)被處理, 后續(xù)不會再有其他處理器處理; 返回false表示仍可被其他處理器處理.

舉個例子:

// 指定要解析的注解
@SupportedAnnotationTypes("myAnnotation.TestAnnotation")
// 指定JDK版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcesser extends AbstractProcessor {
    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        for (TypeElement te : annotations) {
            for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
                TestAnnotation testAnnotation = element.getAnnotation(TestAnnotation.class);
                // do something
            }
        }
        return true;
    }
}

三臼闻、Android中使用編譯時注解

  • 1、自定義編譯時注解
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface TestAnnotation {
    String value() default "Hello Annotation";
}
  • 2囤采、解析編譯時注解
// 支持的注解類型, 此處要填寫全類名
@SupportedAnnotationTypes("myannotation.TestAnnotation")
// JDK版本,一般推薦java8
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
    // 類名的前綴后綴
    public static final String SUFFIX = "AutoGenerate";
    public static final String PREFIX = "My_";
    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {

        for (TypeElement te : annotations) {
            for (Element e : roundEnv.getElementsAnnotatedWith(te)) {
                // 準備在gradle的控制臺打印信息
                Messager messager = processingEnv.getMessager();
                // 打印
                messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.toString());
                messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.getSimpleName());
                messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.getEnclosingElement().toString());

                // 獲取注解
                TestAnnotation annotation = e.getAnnotation(TestAnnotation.class);

                // 獲取元素名并將其首字母大寫
                String name = e.getSimpleName().toString();
                char c = Character.toUpperCase(name.charAt(0));
                name = String.valueOf(c+name.substring(1));

                // 包裹注解元素的元素, 也就是其父元素, 比如注解了成員變量或者成員函數(shù), 其上層就是該類
                Element enclosingElement = e.getEnclosingElement();
                // 獲取父元素的全類名, 用來生成包名
                String enclosingQualifiedName;
                if(enclosingElement instanceof PackageElement){
                    enclosingQualifiedName = ((PackageElement)enclosingElement).getQualifiedName().toString();
                }else {
                    enclosingQualifiedName = ((TypeElement)enclosingElement).getQualifiedName().toString();
                }
                try {
                    // 生成的包名
                    String genaratePackageName = enclosingQualifiedName.substring(0, enclosingQualifiedName.lastIndexOf('.'));
                    // 生成的類名
                    String genarateClassName = PREFIX + enclosingElement.getSimpleName() + SUFFIX;

                    // 創(chuàng)建Java文件
                    JavaFileObject f = processingEnv.getFiler().createSourceFile(genarateClassName);
                    // 在控制臺輸出文件路徑
                    messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + f.toUri());
                    Writer w = f.openWriter();
                    try {
                        PrintWriter pw = new PrintWriter(w);
                        pw.println("package " + genaratePackageName + ";");
                        pw.println("\npublic class " + genarateClassName + " { ");
                        pw.println("\n    /** 打印值 */");
                        pw.println("    public static void print" + name + "() {");
                        pw.println("        // 注解的父元素: " + enclosingElement.toString());
                        pw.println("        System.out.println(\"代碼生成的路徑: "+f.toUri()+"\");");
                        pw.println("        System.out.println(\"注解的元素: "+e.toString()+"\");");
                        pw.println("        System.out.println(\"注解的值: "+annotation.value()+"\");");
                        pw.println("    }");
                        pw.println("}");
                        pw.flush();
                    } finally {
                        w.close();
                    }
                } catch (IOException x) {
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                            x.toString());
                }
            }
        }
        return true;
    }
}

看似代碼很長, 其實很好理解. 只做了兩件事, 1.解析注解并獲取需要的值 2.使用JavaFileObject類生成java代碼.

  • 3述呐、向JVM聲明解析器

在java的同級目錄新建resources目錄, 新建META-INF/services/javax.annotation.processing.Processor文件, 文件中填寫你自定義的Processor全類名

第一步
第二步

然后打出jar包以待使用

Android中使用

  • 使用apt插件

項目根目錄gradle中buildscript的dependencies添加

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

module目錄的gradle中, 添加

apply plugin: 'android-apt'

  • 代碼中調(diào)用

將之前打出的jar包導(dǎo)入項目中, 在MainActivity中寫個測試方法

 @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    test();
}

@TestAnnotation("hehe")
public void test(){
}

運行一遍項目之后, 代碼就會自動生成.

以下是生成的代碼, 在路徑y(tǒng)ourmodule/build/generated/source/apt/debug/yourpackagename中:

public class My_MainActivityAutoGenerate { 

    /** 打印值 */
    public static void printTest() {
        // 注解的父元素: com.example.pan.androidtestdemo.MainActivity
        System.out.println("代碼生成的路徑: file:/Users/Pan/AndroidStudioProjects/AndroidTestDemo/app/build/generated/source/apt/debug/My_MainActivityAutoGenerate.java");
        System.out.println("注解的元素: test()");
        System.out.println("注解的值: hehe");
    }
}

然后在test方法中調(diào)用自動生成的方法

@TestAnnotation("hehe")
public void test(){
    My_MainActivityAutoGenerate.printTest();
}

會看到以下打印結(jié)果:

代碼生成的路徑: file:/Users/Pan/AndroidStudioProjects/AndroidTestDemo/app/build/generated/source/apt/debug/com/example/pan/androidtestdemo/MainActivityAutoGenerate.java
注解的元素: test()
注解的值: hehe
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蕉毯,隨后出現(xiàn)的幾起案子乓搬,更是在濱河造成了極大的恐慌,老刑警劉巖恕刘,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缤谎,死亡現(xiàn)場離奇詭異抒倚,居然都是意外死亡褐着,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門托呕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來含蓉,“玉大人频敛,你說我怎么就攤上這事∠诳郏” “怎么了斟赚?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長差油。 經(jīng)常有香客問我拗军,道長,這世上最難降的妖魔是什么蓄喇? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任发侵,我火速辦了婚禮,結(jié)果婚禮上妆偏,老公的妹妹穿的比我還像新娘刃鳄。我一直安慰自己,他們只是感情好钱骂,可當(dāng)我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布叔锐。 她就那樣靜靜地躺著,像睡著了一般见秽。 火紅的嫁衣襯著肌膚如雪愉烙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天解取,我揣著相機與錄音齿梁,去河邊找鬼。 笑死肮蛹,一個胖子當(dāng)著我的面吹牛勺择,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播伦忠,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼省核,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了昆码?” 一聲冷哼從身側(cè)響起气忠,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赋咽,沒想到半個月后旧噪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡脓匿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年淘钟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陪毡。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡米母,死狀恐怖勾扭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铁瞒,我是刑警寧澤妙色,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站慧耍,受9級特大地震影響身辨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芍碧,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一栅表、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧师枣,春花似錦怪瓶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至陨倡,卻和暖如春敛滋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背兴革。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工绎晃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杂曲。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓庶艾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親擎勘。 傳聞我的和親對象是個殘疾皇子咱揍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 前言## 最近一段時間在研究EventBus和Retrofit 的過程中,都遇到了注解這個概念棚饵。由于在學(xué)習(xí)Java...
    IAM四十二閱讀 9,698評論 18 86
  • 一煤裙、Java 反射機制 參考了許多博文,總結(jié)了以下個人觀點噪漾,若有不妥還望指正: Java 反射機制在程序運行時硼砰,對...
    野夢M閱讀 470評論 0 1
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,113評論 1 32
  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful 2欣硼、Retrofit解析...
    隔壁老李頭閱讀 6,539評論 4 31
  • 拜年啦拜年啦题翰!
    李李拉拉閱讀 239評論 0 1