Java中的注解和反射

Posted milovetingting

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中的注解和反射相关的知识,希望对你有一定的参考价值。

个人博客

http://www.milovetingting.cn

Java中的注解和反射

注解

Java注解(Annotation)又称Java标注,是JDK5.0引入的一种注释机制。

注解定义

通过@interface来声明一个注解

public @interface Anno {
    
}

元注解

对注解进行注解的类就是元注解(meta-annotation),在自定义时,一般需要指定两个元注解

@Target

限制可以应用注解的Java元素类型,包括以下几种:

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}
  • TYPE:作用于类、接口或者枚举

  • FIELD:作用于字段

  • METHOD:作用于方法

  • PARAMETER:作用于方法参数

  • CONSTRUCTOR:作用于构造方法

  • LOCAL_VARIABLE:作用于局部变量

  • ANNOTATION_TYPE:作用于注解

@Retention

指定注解的保留阶段,有以下几种

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}
  • SOURCE:注解只保留在源码中,编译时会被忽略。

  • CLASS:注解在编译时会保留,但JVM会忽略。

  • RUNTIME: 注解会被JVM保留,因此运行环境可以使用。

注解类型元素

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Anno {

    //无默认值,在应用注解时必须设置
    String value();

    //有默认值
    int age() default 18;
}

注解应用场景

根据Retention的类型,注解的应用场景有以下三种:

SOURCE

作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用。

IDE语法检查

android中提供了@IntDef注解

@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
    /** Defines the allowed constants for this element */
    int[] value() default {};

    /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
    boolean flag() default false;
}

这个注解的意义在于能够取代枚举,实现如方法入参限制。

如我们要限制参数只能在MONDAYTUESDAY中的一个,可以先定义常量

public class WeekDay {

    public static final int MONDAY = 1;

    public static final int TUESDAY = 2;

}

然后定义注解

@IntDef(value = {WeekDay.MONDAY, WeekDay.TUESDAY})
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.SOURCE)
public @interface Week {

}

使用注解

public void test(@Week int week) {

}

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    test(WeekDay.MONDAY);
}
APT注解处理器

APT全称为:"Anotation Processor Tools",意为注解处理器。编写好的Java源文
件,需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码Class文件。注解处理器是 javac 自带的一个工
具,用来在编译时期扫描处理注解信息。

注解处理器是对注解应用最为广泛的场景。在Glide、EventBus、ButterKnife、ARouter等常用框架中都有注解处理器的身影。

CLASS

定义为CLASS的注解,会保留在class文件中,但是会被JVM忽略。应用场景为:字节码增强,通过修改字节码文件来达到修改代码执行逻辑的目的。常用的框架有:AspectJ、热修复Roubust。

RUNTIME

注解保留至运行期,我们可以通过反射获取注解中的所有信息。

反射

反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。

Class

Class是一个类,封装了当前对象所对应的类的信息。

获取Class对象

获取Class对象的三种方法

  • 通过类名获取:类名.class

  • 通过对象获取:对象名.getClass()

  • 通过全类名获取:Class.forName(全类名) classLoader.loadClass(全类名)

创建实例

  • 使用Class对象的newInstance()方法来创建Class对象对应类的实例。
Class<?> c = String.class;
Object str = c.newInstance();
  • 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这
    种方法可以用指定的构造器构造类的实例
//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("hello");
System.out.println(obj);

获取构造器信息

得到构造器的方法

Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的public构造函数(包括父类)
Constructor[] getConstructors() -- 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(包括私有)
Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)

获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的
一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:

public T newInstance(Object ... initargs)

获取类的成员变量信息

Field getField(String name) -- 获得命名的公共字段
Field[] getFields() -- 获得类的所有公共字段
Field getDeclaredField(String name) -- 获得类声明的命名的字段
Field[] getDeclaredFields() -- 获得类声明的所有字段

调用方法

获得方法信息的方法

Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法
Method[] getMethods() -- 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法
Method[] getDeclaredMethods() -- 获得类声明的所有方法

当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。 invoke 方法的原型为:

public Object invoke(Object obj, Object... args)

利用反射创建数组

数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference 其中的Array类为
java.lang.reflect.Array类。我们通过Array.newInstance()创建数组对象,它的原型是:

public static Object newInstance(Class<?> componentType, int length);

反射获取泛型真实类型

Type genType = object.getClass().getGenericSuperclass();
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
return (Class<?>) params[0];

基于注解和反射的简单应用

通常我们获取Intent传过来的extra,是通过这样的形式:

getIntent().getStringExtra("name");
getIntent().getIntExtra("age",18);

现在,我们通过注解和反射实现自动获取Extra,类似这样:

@InjectExtra
private String name;

@InjectExtra("age")
private int age;

@InjectExtra("gender")
private boolean gender;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    InjectHelper.inject(this);
    Log.i(TAG, "name:" + name + ",age:" + age + ",gender:" + gender);
}

实现步骤

  1. 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectExtra {
    String value() default "";
}
  1. 定义InjectHelper
public class InjectHelper {

    /**
     * 注入Extra
     *
     * @param activity
     */
    public static void inject(Activity activity) {
        try {
            Class<? extends Activity> clz = activity.getClass();
            Field[] declaredFields = clz.getDeclaredFields();
            for (Field field : declaredFields) {
                boolean annotationPresent = field.isAnnotationPresent(InjectExtra.class);
                if (annotationPresent) {
                    InjectExtra annotation = field.getAnnotation(InjectExtra.class);
                    String name = annotation.value();
                    if (TextUtils.isEmpty(name)) {
                        //如果注解没有指定value,就用字段名
                        name = field.getName();
                    }
                    Object object = activity.getIntent().getExtras().get(name);
                    field.setAccessible(true);
                    field.set(activity, object);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
  1. 使用
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, TargetActivity.class);
        intent.putExtra("name", "zs");
        intent.putExtra("age", 18);
        intent.putExtra("gender", true);
        startActivity(intent);
    }
}

public class TargetActivity extends AppCompatActivity {

    private static final String TAG = InjectHelper.class.getSimpleName();

    @InjectExtra
    private String name;

    @InjectExtra("age")
    private int age;

    @InjectExtra("gender")
    private boolean gender;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectHelper.inject(this);
        Log.i(TAG, "name:" + name + ",age:" + age + ",gender:" + gender);
    }
}
  1. 输出结果
2020-04-27 10:38:22.495 19687-19687/com.wangyz.annotation I/InjectHelper: name:zs,age:18,gender:true

这里为了演示注解与反射,指定Retentaion为RUNTIME,实际上可以指定为SOURCE级别,通过APT来生成辅助类,来减少手动获取Extra的工作量。

以上是关于Java中的注解和反射的主要内容,如果未能解决你的问题,请参考以下文章

注解与反射

Java中的注解和反射

Java中的注解和反射

注解以及注解在反射中的应用

Java注解教程:自定义注解示例,利用反射进行解析

基于NACOS和JAVA反射机制动态更新JAVA静态常量非@Value注解