1、概述

熟悉android开发的都会了解到Android的xUtils框架的ViewUtils模块,简化了findViewById的编写,通过完全注解方式就可以进行UI,资源和事件绑定。实现基本原理就是通过java中的注解和反射实现,本文主要介绍java中的反射机制和自定义注解的原理和实例编写。

2、反射机制

定义:反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

(1)反射机制提供的主要功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;
  • 生成动态代理。

3、反射机制应用案例

(1)获取某个类的属性

package com.chunsoft.testReflect;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class TestReflect{
    private String testField1 = "testField1";
    public int value = 2017;
    public static void main(String[] args) throws Exception {
        //获取当前类的字节码
        Class<?> clazz = Class.forName("com.chunsoft.testReflect.TestReflect");
         System.out.println("===============本类所有属性===============");
         //获取本类的所有属性
         Field[] declaredFields = clazz.getDeclaredFields();
         for(int i = 0;i < declaredFields.length;i ++) {
             //权限修饰符
             int mo = declaredFields[i].getModifiers();
             String priv = Modifier.toString(mo);

             //属性类型
             Class<?> type = declaredFields[i].getType();
             String name = type.getName();

             System.out.println(priv + " " + 
             name + " " + declaredFields[i].getName() + ";");
         }
         System.out.println("==========获取public的属性==========");
        // 取得public的属性
         Field[] fields = clazz.getFields();
         for (int i = 0; i < fields.length; i++) {
             //权限修饰符
             int mod = fields[i].getModifiers();
             String priv = Modifier.toString(mod);

             //属性类型
             Class<?> type = fields[i].getType();
             String name = type.getName();

             System.out.println(priv + " " + 
             name + " " + fields[i].getName() + ";");
        }
    }
}

(2)获取某个类的全部方法

package com.chunsoft.testReflect;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class TestReflectMothod {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("com.chunsoft.testReflect.TestReflectMothod");
        //获取Public类型方法和实现的接口或者父类的方法
        //Method method[] = clazz.getMethods();
        //获取所有方法
        Method method[] = clazz.getDeclaredMethods();
        for (int i = 0; i < method.length; i++) {
            //获取返回类型
            Class<?> returnType = method[i].getReturnType();
            //获取参数类型
            Class<?>[] parameterTypes = method[i].getParameterTypes();
            //获取权限修饰符
            int temp = method[i].getModifiers();

            System.out.print(Modifier.toString(temp) + " ");
            System.out.print(returnType.getName() + "  ");
            System.out.print(method[i].getName() + " ");
            System.out.print("(");
            for (int j = 0; j < parameterTypes.length; ++j) {
                System.out.print(parameterTypes[j].getName() + " " + "arg" + j);
                if (j < parameterTypes.length - 1) {
                    System.out.print(",");
                }
            }
            //获取异常类型
            Class<?> exce[] = method[i].getExceptionTypes();
            if (exce.length > 0) {
                System.out.print(") throws ");
                for (int k = 0; k < exce.length; ++k) {
                    System.out.print(exce[k].getName() + " ");
                    if (k < exce.length - 1) {
                        System.out.print(",");
                    }
                }
            } else {
                System.out.print(")");
            }
            System.out.println();
        }
    }
    private String testMethod(int age ,String name) {
        return name+":"+age;
    }
}

(3)通过反射机制调用某个类的方法

package com.chunsoft.testReflect;

import java.lang.reflect.Method;

public class TestReflectUse {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("com.chunsoft.testReflect.TestReflectUse");
        // 调用TestReflectUse类中的reflect1方法
        Method method = clazz.getMethod("reflect1");
        method.invoke(clazz.newInstance());
        // Java 反射机制 - 调用某个类的方法1.
        // 调用TestReflect的reflect2方法
        method = clazz.getMethod("reflect2", int.class, String.class);
        method.invoke(clazz.newInstance(), 20, "张三");
        // Java 反射机制 - 调用某个类的方法2.
        // age -> 20. name -> 张三
    }
    public void reflect1() {
        System.out.println("Java 反射机制 - 调用某个类的方法1.");
    }
    public void reflect2(int age, String name) {
        System.out.println("Java 反射机制 - 调用某个类的方法2.");
        System.out.println("age -> " + age + ". name -> " + name);
    }
}

更多用法示例

4、自定义注解

Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在java.lang.annotation 包中。

(1)注解的原理

JDK5.0中提供了注解的功能,允许开发者定义和使用自己的注解类型。该功能由一个定义注解类型的语法和描述一个注解声明的语法,读取注解的API,一个使用注解修饰的class文件和一个注解处理工具组成。

目前已被广泛应用于各种Java框架,如Hibernate,Jersey,Spring。注解相当于是一种嵌入在程序中的元数据,可以使用注解解析工具或编译器对其进行解析,也可以指定注解在编译期或运行期有效。

元注解是指注解的注解。包括 @Retention @Target @Document @Inherited四种。

(2)创建自定义注解

使用@interface自定义注解时,自动继承java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。

  • 自定义注解:
public @interface MyAnnotation {

}
  • 自定义注解使用
public class AnnotationTest {
    @MyAnnotation
    public void execute(){
        System.out.println("method");
    }
}
  • 添加变量
public @interface MyAnnotation {
    String value1();
}

当注解中使用的属性名为value时,对其赋值时可以不指定属性的名称而直接写上属性值接口;除了value意外的变量名都需要使用name=value的方式赋值。

  • 自定义注解使用
public class AnnotationTest {
    @MyAnnotation(value1="abc")
    public void execute(){
        System.out.println("method");
    }
}
  • @Retention注解可以在定义注解时为编译程序提供注解的保留策略。
    CLASS
    编译器将把注释记录在类文件中,但在运行时 VM 不需要保留注释。
    RUNTIME
    编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。
    SOURCE
    编译器要丢弃的注释。
  • @Target – 表示支持注解的程序元素的种类,一些可能的值有TYPE, METHOD, CONSTRUCTOR, FIELD等。如果Target元注解不存在,那么该注解就可以使用在任何程序元素之上。
  • Java提供3种内置注解
  1. @Override – 当我们想要覆盖父类的一个方法时,需要使用该注解告知编译器我们正在覆盖一个方法。这样的话,当父类的方法被删除或修改了,编译器会提示错误信息。大家可以学习一下为什么我们总是应该在覆盖方法时使用Java覆盖注解。
  2. @Deprecated – 当我们想要让编译器知道一个方法已经被弃用(deprecate)时,应该使用这个注解。Java推荐在javadoc中提供信息,告知用户为什么这个方法被弃用了,以及替代方法是什么。
  3. @SuppressWarnings – 这个注解仅仅是告知编译器,忽略它们产生了特殊警告,比如:在java泛型中使用原始类型。它的保持性策略(retention policy)是SOURCE,在编译器中将被丢弃。

5、反射和自定义注解结合

大部分java框架的实现通过反射机制和自定义的结合,本文介绍反射和自定义注解的简单结合。

(1)实现自定义注解类

新建Annotation方法,ViewInject.class:

package com.chunsoft.AnnotationTest;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//自定义注解类
@Target(ElementType.FIELD) //用于限制当前自定义注解类的作用的对象
//@Retention(RetentionPolicy.SOURCE) //该注解类只会在源码中出现,当将源码编译成注解码时,注解信息就会被清除
//@Retention(RetentionPolicy.CLASS) //该注解类会被编译到注解码中,但是当虚拟机加载字节码时,注解信息会被清除
@Retention(RetentionPolicy.RUNTIME) //该注解类,永远保留到被加载到虚拟机中
public @interface ViewInject {
    int age();
    String name();
}

(2)在实体类中使用注解

新建一个实体类并在属性上使用注解,User.class:

package com.chunsoft.AnnotationTest;

public class User {
    @ViewInject(age=21,name="chunsoft")
    private String name;
    private int age;
    private String eat(String eat) {
        System.out.println("eat:"+eat);
        return eat + " 真好吃";
    }
    @Override
    public String toString() {
        return "User [name=" + name + ", age=" + age + "]";
    }
}

(3)反射和自定义注解测试

package com.chunsoft.AnnotationTest;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class AnnotationMainTest {

    public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        //需求:获取User类中name字段上的自定义注解的值,
        //然后将该值的age通过反射设置给User对象的age属性,将name设置给User对象的name属性

        User user = new User();
        /**
         * 1.获取User类的字节码
         */
//      user.getClass();
//      User.class;
//      Class.forName("");
        Class clazz= User.class;
        /**
         * 2.将字节码中的name字段获取到
         */
//      clazz.getField("");//只能获取声明为public的字段
        Field declaredField = clazz.getDeclaredField("name");
        Field declaredFieldAge = clazz.getDeclaredField("age");

        /**
         * 3.将当前字段上的注解对象获取到
         */
        ViewInject viewInject =  declaredField.getAnnotation(ViewInject.class);
        if (viewInject != null) {
            /**
             * 4.获取自定义注解对象的参数
             */
            int age = viewInject.age();
            String name = viewInject.name();

            System.out.println("name="+name+",age="+age);
            /**
             * 5.通过反射将这两个值反射给User对象
             */
            declaredField.setAccessible(true); //设置允许访问,其实就是允许暴力反射
            declaredFieldAge.setAccessible(true);
            //将user对象的declaredField设置为name
            declaredField.set(user, name);
            declaredFieldAge.set(user, age);
            System.out.println(user.toString());
        }else {
            System.out.println("字段上面没有自定义注解");
        }
        //通过反射调用eat对象的方法
        Method declaredMethod = clazz.getDeclaredMethod("eat", String.class);
        //设置允许访问
        declaredMethod.setAccessible(true);
        //暴力反射调用该方法
        Object result = declaredMethod.invoke(user, "牛肉拉面");
        System.out.println(result);
    }
}

本文简单介绍了java反射机制和自定义注解原理分析和实例,下篇文章将实现基本的ViewUtils基本功能,减少重复编码。