從Java反射機制到Android註解框架

LeBron_Six發表於2016-06-13

一、Java反射機制


1、定義


        JAVA反射機制是在“執行狀態”中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。Java反射機制主要提供了幾個功能:在執行時判斷任意一個物件所屬的類、在執行時構造任意一個類的物件、在執行時判斷任意一個類所具有的成員變數和方法、在執行時呼叫任意一個物件的方法。


2、獲取Class物件


        當我們編譯一個 Java 專案,所有的 Java 檔案都會被編譯成一個.class 檔案,這些 class 檔案在程式執行時會被 ClassLoader 載入到虛擬機器中。當一個類被載入以後,Java 虛擬機器就會在記憶體中自動產生一個 Class 物件。Java中,無論生成某個類的多少個物件,這些物件都會對應於同一個Class物件,這個Class物件是由JVM生成的,通過它能夠獲悉整個類的結構。我們通過 new 建構函式的形式建立物件實際上也是通過這些 Class 來建立。那麼,我們在程式碼中如何獲得Class物件呢?通常有三個方法,如下所示:

    /**
     * 獲取Class物件的三種方式
     */
    public static Class<?> getClassObj() {
        // 根據類名獲取Class物件
        Class<?> clazz1 = People.class;

        // 根據物件獲取Class物件
        People people = new People();
        Class<?> clazz2 = people.getClass();

        // 根據完整類名獲取Class物件
        try {
            Class<?> clazz3 = Class.forName("com.yuyh.reflection.java.People");
        } catch (ClassNotFoundException e) {
            Log.e(TAG, e.toString());
        }

        Log.i(TAG, "clazz1 = " + clazz1);

        return clazz1; // clazz2 clazz3
    }

3、通過Class物件獲取目標類的物件


        平時所熟悉的建立物件的方式就是去new一個類,執行他們的建構函式,那麼當我們拿到Class物件想去建立目標類物件,說是通過反射,實際上還是去執行類的建構函式。如下所示:

    /**
     * 反射獲取類的物件
     *
     * @return
     */
    public static Object getObject() {
        try {
            // 獲取類的Class物件
            Class<?> clz = getClassObj();
            // 獲取類物件的Constructor
            Constructor<?> constructor = clz.getConstructor(String.class, int.class, String.class);
            // 在使用時取消 Java語言訪問檢查,提升反射效能
            constructor.setAccessible(true);
            // 通過 Constructor 來建立物件
            Object obj = constructor.newInstance("yuyh", 25, "xxx@gmail.com");
            Log.i(TAG, obj.toString());

            return obj;
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
        return null;
    }


4、通過Class物件獲取類的所有方法


        同樣,拿到Class物件之後我們可以通過呼叫getDeclaredMethods或getMethods(包括從父類繼承下來的方法) 去獲取類的所有方法,也可呼叫getDeclaredMethod (String name, Class...<?> parameterTypes) 來根據方法名獲取某個方法。如下所示:

    /**
     * 反射獲取類的方法
     */
    public static void getDeclaredMethods() {
        People people = (People) getObject();
        // 獲取到類中的所有方法(不包含從父類繼承的方法)
        Method[] methods = people.getClass().getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            Log.i(TAG, "method[" + i + "] = " + methods[i].getName());
        }

        try {
            // 獲取類中的某個方法
            Method method = people.getClass().getDeclaredMethod("setEMail", String.class);
            // 判斷是否是public方法
            Log.i(TAG, "method is public = " + Modifier.isProtected(method.getModifiers()));
            // 獲取該方法的引數型別列表
            Class<?>[] paramTypes = method.getParameterTypes();
            for (int i = 0; i < paramTypes.length; i++) {
                Log.i(TAG, "paramTypes[" + i + "] = " + paramTypes[i].getName());
            }

            Log.i(TAG, "people.email befor= " + people.getEMail());

            // 執行該方法
            method.invoke(people, "xxx@163.com");

            Log.i(TAG, "people.email after= " + people.getEMail());
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }


5、通過Class物件獲取類的所有屬性


        同理,可通過getDeclaredFields、 getFields、 getDeclaredField (String name)、 getField (String name) 來獲取類的所有屬性或單個屬性。如下所示:

    /**
     * 反射獲取類的屬性
     */
    public static void getDeclaredFields() {
        People people = (People) getObject();
        // 獲取當前類所有屬性
        Field[] fields = people.getClass().getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Log.i(TAG, "fields[" + i + "] = " + fields[i].getName());
        }

        try {
            // 獲取當前類的某個屬性
            Field field = people.getClass().getDeclaredField("name");
            // 獲取屬性值
            Log.i(TAG, "people.name before = " + field.get(people));

            // 設定屬性值
            field.set(people, "yuyh1");

            Log.i(TAG, "people.name after = " + field.get(people));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


6、根據Class物件獲取父類或實現的介面


        如下所示:

    /**
     * 獲取物件的父類
     */
    public static void getSuperClass() {
        Student student = new Student("142315079");
        Class<?> superClass = student.getClass().getSuperclass();
        while (superClass != null) {
            Log.i(TAG, "superClass = " + superClass.getName());
            superClass = superClass.getSuperclass(); // 迴圈獲取上一層父類(如果存在),至少存在一層java.lang.Object
        }
    }


    /**
     * 獲取物件實現的介面
     */
    public static void getInterface() {
        Student student = new Student("142315079");
        // 獲取該類實現的所有介面
        Class<?>[] interfaces = student.getClass().getInterfaces();
        for (int i = 0; i < interfaces.length; i++) {
            Log.i(TAG, "interfaces[" + i + "] = " + interfaces[i].getName());
        }
    }


二、從Java反射到Android註解


        俗話說,不會偷懶的程式設計師不是好程式設計師。相信初學Android的時候,大家都會被一堆findViewById()並且還要進行強轉這種簡單沒營養,又不得不寫的程式碼氣死,顯然,Android註解也在一定程度上幫助了你成為一名偷懶的程式猿。

        什麼是註解?通常我們可以把它理解為一個標記,最常見的註解有:@Override,@Deprecated,@SuppressWarnings等。註解本質上也是一個類,他不是通過class和interface來定義,而是通過@interface來定義一個註解。如下所示:

@Documented                               // 是否儲存到JavaDoc文件
@Retention(RetentionPolicy.RUNTIME)       // SOURCE(原始碼時),CLASS(編譯時),RUNTIME(執行時),預設為 CLASS
@Target(ElementType.METHOD)               // 用於修飾哪些程式元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未標註則表示可修飾所有
@Inherited                                // 是否可以被繼承,預設為 false
public @interface Test {

    int value() default 0;
}


        那麼,我們如何通過註解來免去寫findViewById(), setOnClickListener() ... ... 的麻煩呢?接下來我們就自定義一個簡單的Android註解框架。

        首先,定義一個註解InjectView,如下所示:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectView {

    int value() default 0;
}

        那麼,我們要如何來解析這個註解呢?如下所示:

package com.yuyh.reflection.annotation;

import android.app.Activity;
import android.util.Log;

import java.lang.reflect.Field;

/**
 * @author yuyh.
 * @date 2016/6/13.
 */
public class Inject {
    public static final String TAG = "Reflection";

    public static void inject(Activity activity) {
        getAnnotationInfos(activity);
    }

    private static void getAnnotationInfos(Activity activity) {
        Class clazz = activity.getClass();
        Log.i(TAG, clazz.getName());

        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            InjectView injectView = field.getAnnotation(InjectView.class);
            if (injectView != null) {
                int id = injectView.value();
                try {
                    field.setAccessible(true);
                    field.set(activity, activity.findViewById(id));
                } catch (IllegalAccessException e) {
                    Log.e(TAG, "IllegalAccessException = " + e.toString());
                }
            }
        }
    }
}

        那麼,在Activity中就可以進行註解View了。如下所示:

public class MainActivity extends AppCompatActivity {

    @InjectView(R.id.hello)
    TextView tvHello;

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

        Inject.inject(this); // 初始化註解

        tvHello.setText("hahaha");
    }
}
        tvHello.setText()方法沒有報空指標,並且成功設定值,說明我們註解View實現成功。

        同理,我們還可對實現對setOnClickListener等等的註解。

    /**
     * 解析OnClick以及OnLongClick註解
     *
     * @param activity
     */
    private static void injectClick(final Activity activity) {
        Class clazz = activity.getClass();
        Log.i(TAG, clazz.getName());

        Method[] methods = clazz.getDeclaredMethods();
        for (final Method method : methods) {
            OnClick click = method.getAnnotation(OnClick.class);
            OnLongClick longClick = method.getAnnotation(OnLongClick.class);
            if (click != null && click.value() != 0) {
                View view = activity.findViewById(click.value());//通過註解的值獲取View控制元件
                if (view == null)
                    return;
                view.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        try {
                            method.invoke(activity, v);//通過反射來呼叫被註解修飾的方法,把View傳回去
                        } catch (InvocationTargetException e) {
                            Log.e(TAG, "InvocationTargetException = " + e.toString());
                        } catch (IllegalAccessException e) {
                            Log.e(TAG, "IllegalAccessException = " + e.toString());
                        }
                    }
                });
            }

            if (longClick != null && longClick.value() != 0) {
                View view = activity.findViewById(click.value());
                if (view == null)
                    return;
                view.setOnLongClickListener(new View.OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View v) {
                        try {
                            method.invoke(activity, v);
                        } catch (InvocationTargetException e) {
                            Log.e(TAG, "InvocationTargetException = " + e.toString());
                        } catch (IllegalAccessException e) {
                            Log.e(TAG, "IllegalAccessException = " + e.toString());
                        }
                        return true;
                    }
                });
            }
        }
    }

原始碼地址:https://github.com/smuyyh/ReflectionDemo

Thank you for reading~~


相關文章