java源码分析-注解基本原理

java源码分析-注解基本原理

​ java注解在实际工作中经常彭代,无论是java内置注解还是框架自带注解,在我们使用这些注解的时候是否思考过这些注解到底是什么作用?是怎么起作用的?java以及框架是怎么识别这些注解的?本篇我们就来聊一聊注解的基本原理。

1.注解是什么

​ 其实注解在大多数情况下与普通的修饰符(public,void,static等)在使用方式上并没有多大区别。我们先来看个例子:

public class AnnotationDemo {

    @Override
    public String toString() {
        return super.toString();
    }

    @Deprecated
    public static void methodA(){
        System.out.println("方法已过期");
    }

    @SuppressWarnings("uncheck")
    public static void methodB(){
        System.out.println("忽略检查警告");
    }

}

​ 上面的例子中@Override是一个注解,这是一个java内置注解,java通过该注解表示某个方法重写了父类的方法。可以看到注解其实就类似于一个标识符,当java运行时会根据注解的功能完成相关功能。而对于@Deprecated和@SuppressWarnings(“uncheck”)这两个注解,也是Java本身内置的注解,在代码中也可以经常看见它们,当方法或是类上面有@Deprecated注解时,说明该方法或是类都已经过期不建议再用,@SuppressWarnings 则表示忽略指定警告,比如@SuppressWarnings(“uncheck”),这就是注解的最简单的使用方式。

​ 自java1.5版本引入注解之后,注解就成了java平台中非常重要的一部分。注解也叫元数据,即一种描述数据的数据。所以可以说注解就是源代码的元数据。

1.1注解的基本语法

元注解

​ 元注解是用来修饰注解的注解,通常用作注解的定义和声明上。那最常见的@Overide注解来看一下它的定义:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

其中元注解就是@Target和@Retention。在java中有以下几个元注解:

  • @Target:注解的作用目标
  • @Retention:注解的生命周期
  • @Documented:注解是否应当被包含在 JavaDoc 文档中
  • @Inherited:是否允许子类继承该注解

@Target注解用来指定声明的注解作用范围,也就是说@Override注解可以在哪些地方使用。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

@Target注解内部是是一个ElementType类型的数组。表示可以通过设置ElementType类型的值来为@Target设置值。看一下ElementType是一个什么?

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, //类型参数(1.8新加入)

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE    //类型使用声明(1.8新加入)
}

可以看到ElementType是一个枚举类,表示@Target注解的value可以设置的范围是这些枚举值。各个枚举值的含义也已经给出。

注意当@Target未指定具体的值时,则表示此注解可用在任何地方。当有多个值时,可以使用{}并用逗号隔开:

@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})

@Retention用来约束注解的生命周期,它的底层也是一个枚举类,但是与@Target不同,@Retention一次只能设置一个值。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

RetentionPolicy枚举类,它有三个值:SOURCE、CLASS、RUNTIME

  • SOURCE:表示注解会被编译期丢弃。该注解只会保留在源码中,源码经过编译,注解将被丢弃,即在class文件中将找不到该注解了;
  • CLASS:表示注解会被VM丢弃。该注解会保留在class文件中,但是当class加载到vm内存中是,该注解会被丢弃。注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解,@Override、@Deprecated、@SuppressWarnning等;
  • RUNTIME:表示该注解将在运行期(jvm)中保留,因此可以通过反射机制获取到该注解的相关信息。如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
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
}

所以@Override上的@Retention(RetentionPolicy.SOURCE)就表示@Override注解只会被保留在源码中。

@Documented注解用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,也就是说被@Documented修饰的注解会生成到javadoc中。Documented是一个标记注解,没有成员。

@Inherited可以让注解被继承,但其实不是真的继承,只是通过使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解,举例如下:

public class MyAnnotation2{
    public static void main(String[] args) {
        ClassA1 classA1 = new ClassA1();
        Annotation[] annotations1 = classA1.getClass().getAnnotations();
        System.out.println(Arrays.toString(annotations1));

        ClassB1 classB1 = new ClassB1();
        Annotation[] annotations2 = classB1.getClass().getAnnotations();
        System.out.println(Arrays.toString(annotations2));
    }

}

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotationA1 {
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotationB1 {
}

@MyAnnotationA1
class ClassA {

}

class ClassA1 extends ClassA{

}

@MyAnnotationB1
class ClassB{

}

class ClassB1 extends ClassB{

}

直接代码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KfZ3GbvI-1614775733571)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210302203823212.png)]

可以看到ClassA、ClassB上分别使用了@MyAnnotationA1、@MyAnnotationA2注解,而只有@MyAnnotationA1住街上使用了@Inherited注解,这样在子类继承ClassA、ClassB时,使用getAnnotations()方法只能获取ClassA1的父类ClassA上的注解信息。

注解元素

​ 在上述对@Override在注解中,我们可以看到该注解内部没有定义其他元素,所以@Override也称为标记注解(marker annotation)。但是一般在定义注解时都会包含一些元素以表示某些值,方便处理器使用。比如我们看到的@SuppressWarnings注解:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

这个注解的注解元素就是String数组类型的value;通过使用元素来进行设置一些值,注解中的元素类似于属性或者参数。注意到注解中的元素声明的方式是方法(无方法体),这一点类似与接口。

这样在使用注解时,就可以为该注解设定相关的值,当猪姐处理器解析(后面会说)注解元素的值后会做相应的处理。

注解支持的元素类型除了上面的String之外还有以下:

  • 基本类型(int、char、byte、double、float、long、boolean)
  • 字符串String
  • 类Class
  • 枚举enum
  • 注解Annotation
  • 上述类型的数组类型

去除这些类型,当我们使用其他类型修饰注解元素时,编译期会报错,Invalid type 'Integer' for annotation member。当然基本类型的包装类型也是不允许在注解中修饰注解元素的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hlUO6mGI-1614775733575)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210302205753669.png)]

这里还需要注意一点:注解Annotation也是可以作为注解元素的修饰类型。也就是嵌套注解。如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation3 {

//    Integer value();	//编译器报错

    AnnotationX value() default @AnnotationX(flag = true);

}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface AnnotationX{
    boolean flag() default false;
}

java内置注解

java的内置注解主要有三个,上面也提到过,这里简单的过一下,分别是@Override、@Deprecated、@SuppressWarnning。

  • @Override:用于标明此方法覆盖了父类的方法,源码如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
  • @Deprecated:用于标明已经过时的方法或类,源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
  • @SuppressWarnnings:用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告,其实现源码如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

该注解内部有一个String数组类型的注解元素value,主要取值如下:

  • deprecation:使用了不建议使用的类或方法时的警告;
  • unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
  • fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
  • path:在类路径、源文件路径等中有不存在的路径时的警告;
  • serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
  • finally:任何 finally 子句不能正常完成时的警告;
  • all:关于以上所有情况的警告。

举个例子:

当我们诗句集合时,没有使用泛型来指定集合保存的类型时;

public static void main(String[] args) {

    List list = new ArrayList();
    list.add("a");
    list.add(1);
    System.out.println(list);

}

编辑器就会有如下警告:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j3Q8Xx1O-1614775733580)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210302222040033.png)]

可以通过@SuppressWarnings(“unchecked”)消除警告:

@SuppressWarnings("unchecked")
List list = new ArrayList();

1.2注解的默认值

​ 注解对注解元素的默认值有着一定的限制。具体的限制如下:

(1)首先,注解元素的默认值必须明确,也就是说元素不能有不能确定的值。这就要求注解元素的默认值要么是在定义注解的时候通过default给出,要么是在使用注解时为元素提供具体的值;

(2)对于非基本类型的注解元素无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为值。通常可以使用一些特殊字符(空字符串、负数等)来解决这个问题。

1.3注解不能够继承

​ 首先注解是不能够被继承的,不能使用关键字extends来继承某个@interface,但是编译器在将注解编译后,编译器会自动继承java.lang.annotation.Annotation接口,本来想把@Override反编译出来论证一下,但发现Idea反编译工具太智能,反编译出来没有变化。但是我通过Idea中的类图工具Diagrams查看如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2ft3aDQD-1614775733588)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210302213212556.png)]

这也说明了Override注解是继承Annotation。

2.自定义注解

2.1如何自定义注解

我们那java内置注解@SuppressWarnings来分析:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

(1)首先@Target元注解不可少,用来标注注解的使用范围;

(2)@Retention注解也是不可少的,通过它能够确定我们定义的注解的生命周期;

(3)定义注解是使用@interface,注意月接口仅相差一个@符号,但是意义完全不同;

(4)注解元素可有可无,需要定义注解元素是通常的格式:类型 字段名()。类型范围在上文艺界说明;

(5)一般会为注解元素通过default设置默认值,如果没有设置,在使用注解时一定要有值。

例如我们定义一个表示人物信息的注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyPersonAnnotation {
    //姓名
    String name() default "";

    //年龄
    int age() default 0;

    //性格
    Character character() default Character.OPEN;
}

enum Character{
    OPEN,
    IRRITABLE,
    GENTLE;
}

简单分析一下:

@MyPersonAnnotation注解:

  • @Target(ElementType.METHOD)表示注解可以使用在方法上;
  • @Retention(RetentionPolicy.RUNTIME)表示该注解在jvm运行时可以通过反射获取到注解信息;
  • 有三个注解元素分别是name、age和character,其中character的类型是我们自定义的枚举类型Character。

2.2使用注解

其实注解使用时,只要根据该注解的定义就可以基本就知道如何使用了,例如上面我们定义的注解可以用在方法上,具体如下:

public class MyPersonAnnotationTest {
    public static void main(String[] args) {
        getPersonInfo();    
    }

    @MyPersonAnnotation(name = "zhangsan", age = 18, character = Character.GENTLE)
    private static void getPersonInfo() {
    }

}

2.3注解处理器

注解定义好了,并且也在方法上使用了,但是通过这个注解能干什么呢?这就是注解处理器的作用了,这也是自定义注解使用的核心。

下面面我们演示通过反射获取动态运行时的注解信息,而这种方式其实就是注解处理器的工作原理:

public class MyPersonAnnotationTest {
    public static void main(String[] args) throws NoSuchMethodException {
        process(MyPersonAnnotationTest.class);
    }

    @MyPersonAnnotation(name = "zhangsan", age = 18, character = Character.GENTLE)
    private static void getPersonInfo() {
    }

    private static void process(Class clazz) throws NoSuchMethodException {
        //通过Class对象拿到getPersonInfo方法对象Method
        Method method = clazz.getDeclaredMethod("getPersonInfo", null);
        System.out.println(method.getName());
        //根据Method获取到该方法上的注解
        MyPersonAnnotation declaredAnnotations = method.getDeclaredAnnotation(MyPersonAnnotation.class);
        System.out.println(declaredAnnotations);
    }

}

打印结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IxUIp733-1614775733589)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210303204231367.png)]

也就是说我们通过反射机制,拿到了运行时getPersonInfo()方法上的注解信息,通过这个方式我们就可以对这个注解标注的类、方法、字段等做更多的操作了。需要注意的是,通过反射机制获取运行时注解信息,需要我们在定义注解是设置@Retention(RetentionPolicy.RUNTIME),否则无法获取到!

实际上这就是注解处理器的原理,在Spring中就是通过通过这种方式定义各种注解处理器类和方法对spring的相关注解,如@Service等进行相应的操作。

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页