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的工作量。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容