从Java反射机制到Android注解框架

一、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, "[email protected]");
            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, "[email protected]");

            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~~


你可能感兴趣的:(Android开发)