美文网首页
Android 浅谈反射与注解

Android 浅谈反射与注解

作者: 酷酷的Demo | 来源:发表于2019-06-26 22:28 被阅读0次

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

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

    • 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 访问权限的变量
        // 包括本类声明的和从父类继承的
        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

    • 调用 getFields() 方法,输出 SonClass 类以及其所继承的父类( 包括 FatherClass 和 Object ) 的 public 方法。注:Object 类中没有成员变量,所以没有输出
      类的名称:obj.SonClass
      public java.lang.String mSonBirthday
      public java.lang.String mFatherName
      public int mFatherAge
    
    • 调用 getDeclaredFields() , 输出 SonClass 类的所有成员变量,不问访问权限
      类的名称: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());
                }
            }
        }
    }
    
    • 调用 getMethods() 方法 获取 SonClass 类所有 public 访问权限的方法,包括从父类继承的。打印信息中,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(  )
    
    • 调用 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(...) 为例

    /**
     * 访问对象的私有方法
     * 为简洁代码,在方法上抛出总的异常,实际开发别这样
     */
    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中使用编译时注解

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

    • 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'

    • 代码中调用

    将之前打出的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
    

    相关文章

      网友评论

          本文标题:Android 浅谈反射与注解

          本文链接:https://www.haomeiwen.com/subject/vcqkcctx.html