admin管理员组

文章数量:1438710

注解3

在说注解的使用之前,让我们再回顾一下元注解Retention,其参数决定了注解的生命周期。三个生命周期,处理的方法也不尽相同。

不同生命周期注解的使用

生命周期SOURCE注解使用

生命周期为SOURCE的注解,在编译之后就被丢掉了,不会进入编译后的字节码文件,其主要由java编译器使用,比如Override注解,就是编译器在编译生成字节码之前对注解的元素进行检查。这种注解一般我们只是直接拿来使用,不会自己定义和处理(至少我没有能力去动java的编译器)。

生命周期CLASS注解使用

生命周期为CLASS的注解,在编译后会保留在生成的字节码文件中,但是在字节码加载类到JVM时,会被抛弃掉。这种注解的作用主要是通过编译时注解处理器自动在字节码中生成二进制代码,提高开发效率。这种生命周期的注解比较少见(反正我基本上没有见过),听说一些安卓的开发框架会定义和使用这样的注解。

编译时注解处理器是Java 6引入的一种工具,允许在编译时读取和处理注解信息。处理器可以生成额外的源代码文件,从而用于构建过程中的代码生成。至于这玩意怎么使用?我只知道它继承自AbstractProcessor类,其他的内容感兴趣的自己搜索吧,我不会(我懒)。

生命周期RUNTIME注解使用

生命周期为RUNTIME的注解,不仅会进入字节码文件,还会被载入JVM。对我来说这种注解比较常见,Spring全家桶,JUnit等框架中这种注解用的非常多。

在java语言中,万物皆为对象,注解显然也属于“万物”的一员,一种注解也可以看成是一个类。一个类,又会被载入JVM中,这意味着这么?意味着我们可以用反射去获取和使用它。

通过反射使用注解

在前面的博客中,我们定义了Person类、Student类以及一个自定义注解Myannot并通过编译后的字节码进行了一些简单的观察,可是并没有赋予注解实际的意义。现在我们假设Myannot有这样的实际意义:现在Person类有两个变量:id和name。我们通过给Person打上Myannot注解的方式,实现Person中的id和name的自动赋值。

首先我们要修改下Myannot注解的定义,只保留两个必要的元注解,设置其两个参数为id和name:

代码语言:java复制
package anno;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Myannot {
    int id() default 1000;
    String name() default "Hell";
}

而后,Person类和Student类的代码也要做一定程度的修改,在Person中添加上id和name两个变量(其实按照标准的JavaBean写法,应该是两个变量为private,然后设置public的getter和setter,但是我懒,所以这里直接把两个变量设置为public了),并且修改下hello方法,使其可以输出这两个变量。当然,别忘了打上注解

代码语言:java复制
import anno.Myannot;

@Myannot(id = 20, name = "JOJO")
public class Person {
    public int id;
    public String name;
    public void hello() {
        System.out.println("Person id is");
        System.out.println(id);
        System.out.println("Person name is");
        System.out.println(name);
    }
}

修改Main类,如下所示。这样我们运行后,就可以自动将Myannot中的id和name赋值给Person对象p了.....吗?

代码语言:java复制
import anno.Myannot;

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        p.hello();
    }
}
代码语言:txt复制
输出:
Person id is
0
Person name is
null

想的挺美。这就是之前反复提到的:注解只是标记,如果没有对应的处理器去处理它,它也就是一个长得漂亮的注释。所以我们必须有一个专门的处理逻辑来实现赋值操作。由于这个处理逻辑非常简单,我们直接在Main类中编写静态函数来实现处理逻辑。这里直接给出实现代码:

代码语言:java复制
import anno.Myannot;

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        setIdAndName(p);    // 这里添加注解处理逻辑运行
        p.hello();
    }

    private static void setIdAndName(Person p) {
        // 首先通过反射的方式获取到Person类上的Myannot注解
        Myannot myannot = p.getClass().getAnnotation(Myannot.class);
        // 在使用之前,要先判断注解存不存在
        if(myannot != null) {
            // 实现赋值逻辑
            int id = myannot.id();
            String name = myannot.name();
            p.id = id;
            p.name = name;
        }
    }
}
代码语言:txt复制
输出:
Person id is
20
Person name is
JOJO

通过注解处理逻辑,我们实现了通过注解自动赋值对象属性。

回头看看@Inherited元注解

在上一篇博客中,我们留了一个坑:对于打上了Inherited元注解的注解,其作用是让注解可以被子类继承,但是从编译后的字节码中完全看不到这一点,那这个元注解到底有没有用呢?

在前文中我们已经在Person类上打上了Myannot注解并实现了自动赋值,那Person的子类Student能够这么方便吗?我们修改Student类和main函数:

代码语言:java复制
// Student类上没有任何注解
public class Student extends Person{
}
代码语言:java复制
public static void main(String[] args) {
    Student s = new Student();  // 将变量类型改为Student,其他不变
    setIdAndName(s);
    s.hello();
}
代码语言:txt复制
输出:
Person id is
0
Person name is
null

根据输出来看,注解Myannot和setIdAndName函数总得有一个出问题的。

我们给Myannot注解打上Inherited元注解,其他均不变,再重新运行下试试:

代码语言:java复制
package anno;

import java.lang.annotation.*;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Myannot {
    int id() default 1000;
    String name() default "Hell";

}
代码语言:txt复制
输出:
Person id is
20
Person name is
JOJO

可以看到,此时打在Person类上的注解在其子类Student类上生效了。

本文标签: 注解3