Android 浅谈反射与注解

2019-06-26  本文已影响0人  酷酷的Demo

一、使用反射获取类的信息

现在有两个用于测试的类,代码很简单,我们直接贴出来:

public class FatherClass {
    public String mFatherName;
    public int mFatherAge;

    public void printFatherMsg(){}
}
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 访问权限的变量
    // 包括本类声明的和从父类继承的
    Field[] fields = mClass.getFields();

    //2.2 获取所有本类声明的变量(不问访问权限)
    //Field[] fields = mClass.getDeclaredFields();

    //3. 遍历变量并输出变量信息
    for (Field field :
            fields) {
        //获取访问权限并输出
        int modifiers = field.getModifiers();
        System.out.print(Modifier.toString(modifiers) + " ");
        //输出变量的类型及变量名
        System.out.println(field.getType().getName()
                 + " " + field.getName());
    }
}

以上代码注释很详细,就不再解释了。需要注意的是注释中 2.1 的 getFields() 与 2.2的 getDeclaredFields() 之间的区别,下面分别看一下两种情况下的输出。看之前强调一下:SonClass extends FatherClass extends Object

  类的名称:obj.SonClass
  public java.lang.String mSonBirthday
  public java.lang.String mFatherName
  public int mFatherAge
  类的名称: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 访问权限的方法
    //包括自己声明和从父类继承的
    Method[] mMethods = mClass.getMethods();

    //2.2 获取所有本类的的方法(不问访问权限)
    //Method[] mMethods = mClass.getDeclaredMethods();

    //3.遍历所有方法
    for (Method method :
            mMethods) {
        //获取并输出方法的访问权限(Modifiers:修饰符)
        int modifiers = method.getModifiers();
        System.out.print(Modifier.toString(modifiers) + " ");
        //获取并输出方法的返回值类型
        Class returnType = method.getReturnType();
        System.out.print(returnType.getName() + " "
                + method.getName() + "( ");
        //获取并输出方法的所有参数
        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());
            }
        }
    }
}
  类的名称: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(  )
  类的名称: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、访问或操作类的私有变量和方法

都知道,对象是无法访问或操作类的私有变量和方法的,但是,通过反射,我们就可以做到。没错,反射可以做到!下面,让我们一起探讨如何利用反射访问 类对象的私有方法 以及修改 私有变量或常量。

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(...) 为例

/**
 * 访问对象的私有方法
 * 为简洁代码,在方法上抛出总的异常,实际开发别这样
 */
private static void getPrivateMethod() throws Exception{
    //1. 获取 Class 类实例
    TestClass testClass = new TestClass();
    Class mClass = testClass.getClass();

    //2. 获取私有方法
    //第一个参数为要获取的私有方法的名称
    //第二个为要获取方法的参数的类型,参数为 Class...,没有参数就是null
    //方法参数也可这么写 :new Class[]{String.class , int.class}
    Method privateMethod =
            mClass.getDeclaredMethod("privateMethod", String.class, int.class);

    //3. 开始操作方法
    if (privateMethod != null) {
        //获取私有方法的访问权
        //只是获取访问权,并不是修改实际权限
        privateMethod.setAccessible(true);

        //使用 invoke 反射调用私有方法
        //privateMethod 是获取到的私有方法
        //testClass 要操作的对象
        //后面两个参数传实参
        privateMethod.invoke(testClass, "Java Reflect ", 666);
    }
}

需要注意的是,第3步中的 setAccessible(true) 方法,是获取私有方法的访问权限,如果不加会报异常 IllegalAccessException,因为当前方法访问权限是“private”的

正常运行后,打印如下,调用私有方法成功:

Java Reflect 666

2、修改私有变量

以修改 TestClass 类中的私有变量 MSG 为例,其初始值为 "Original" ,我们要修改为 "Modified"。老规矩,先上代码看注释

/**
 * 修改对象私有变量的值
 * 为简洁代码,在方法上抛出总的异常
 */
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) {
        //获取私有变量的访问权
        privateField.setAccessible(true);

        //修改私有变量,并输出以测试
        System.out.println("Before Modify:MSG = " + testClass.getMsg());

        //调用 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、自定义注解

举个栗子, 结合例子讲解

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

元注解的的意义参考上面的讲解, 不再重复, 这里看注解值的写法,其中默认值是可选的, 可以定义, 也可以不定义:

类型 参数名() 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类, 实现其抽象方法

public boolean process(Set annotations, RoundEnvironment roundEnv)

该方法返回ture表示该注解已经被处理, 后续不会再有其他处理器处理; 返回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中使用编译时注解

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface TestAnnotation {
    String value() default "Hello Annotation";
}
// 支持的注解类型, 此处要填写全类名
@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));

                // 包裹注解元素的元素, 也就是其父元素, 比如注解了成员变量或者成员函数, 其上层就是该类
                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;

                    // 创建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代码.

在java的同级目录新建resources目录, 新建META-INF/services/javax.annotation.processing.Processor文件, 文件中填写你自定义的Processor全类名

第一步 第二步

然后打出jar包以待使用

Android中使用

项目根目录gradle中buildscript的dependencies添加

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

module目录的gradle中, 添加

apply plugin: 'android-apt'

将之前打出的jar包导入项目中, 在MainActivity中写个测试方法

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

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

运行一遍项目之后, 代码就会自动生成.

以下是生成的代码, 在路径yourmodule/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方法中调用自动生成的方法

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

会看到以下打印结果:

代码生成的路径: file:/Users/Pan/AndroidStudioProjects/AndroidTestDemo/app/build/generated/source/apt/debug/com/example/pan/androidtestdemo/MainActivityAutoGenerate.java
注解的元素: test()
注解的值: hehe
上一篇下一篇

猜你喜欢

热点阅读