还有人没玩过Java的注解Annotation吗

注解简介

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

Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。

注解的意义

Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。处理获取注解值的过程是我们开发者直接写的注解提取逻辑,处理提取和处理 Annotation 的代码统称为APT(Annotation Processing Tool),主要可实现以下功能:

  • 提供信息给编译器: 编译器可以利用注解来检测出错误或者警告信息,打印出日志。

  • 编译阶段时的处理: 软件工具可以用来利用注解信息来自动生成代码、文档或者做其它相应的自动处理。

  • 运行时处理: 某些注解可以在程序运行的时候接受代码的提取,自动做相应的操作。

  • 代码可读性:注解语法很简单,除了使用@开头,它基本与java基础语法一致。通常我们通过注解名称就可以简单了解到注解的作用,比如@Test,@Override,@Bean 等。

内置的注解

作用在代码的注解(在 java.lang 中)是

  • @Override - 标注该方法是重写方法。如果其父类或者接口中并没有该方法时,会报编译错误。
  • @Deprecated - 标记过时的或者不建议使用的api。如果使用该方法,会报编译警告。
  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。若是注释的api范围内存在警告则可以忽略告警,但是需要填写需要压制的报警类型,若存在多个警告,可以通过配置数组形式进行压制。

作用在其他注解的注解(或者说 元注解,在 java.lang.annotation)是:

  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
  • @Documented - 标记这些注解是否包含在用户文档中。
  • @Target - 标记这个注解应该是哪种 Java 成员。
  • @Inherited - 它所标注的Annotation将具有继承性。

从 Java 7 开始,额外添加了 3 个注解:

  • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
  • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

从 Java 7 开始,额外添加了 3 个注解:

  • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
  • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

注解属性类型

注解属性类型可以有以下列出的类型

  • 1.基本数据类型
  • 2.String
  • 3.枚举类型
  • 4.注解类型
  • 5.Class类型
  • 6.以上类型的一维数组类型

Annotation的使用范围

默认情况下一个自定义的Annotation可以在任何方法、属性以及类上使用。若想限定Annotation的使用范围可以使用@Target注解。

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    public String key() default "default_key";
    public String value() default "default_value";
}
ElementType的取值注解使用范围
METHOD可用于方法上
TYPE可用于类或者接口上
ANNOTATION_TYPE可用于注解类型上(被@interface修饰的类型)
CONSTRUCTOR可用于构造方法上
FIELD可用于域上
LOCAL_VARIABLE可用于局部变量上
PACKAGE用于记录java文件的package信息
PARAMETER可用于参数上

注解源码解析

image-20210528115046103

Annotation

@interface

使用 @interface 定义注解时,意味着它实现了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。

定义 Annotation 时,@interface 是必须的。

注意:它和我们通常的 implemented 实现接口的方法不同。Annotation 接口的实现细节都由编译器完成。通过 @interface 定义注解后,该注解不能继承其他的注解或接口。

  • 注解的本质就是一个Annotation接口,我们使用的其它注解默认都继承了Annotation
  • 每 个 Annotation都与 1个RetentionPolicy关联,并且与 多个 ElementType关联。
package java.lang.annotation;
/**Annotation接口源码*/
public interface Annotation {

    boolean equals(Object obj);

    int hashCode();
    
    String toString();
	//注解类型
    Class<? extends Annotation> annotationType();
}

ElementType用来指定注解类型的枚举类

package java.lang.annotation;

public enum ElementType {
    TYPE,               /* 类、接口(包括注释类型)或枚举声明  */

    FIELD,              /* 字段声明(包括枚举常量)  */

    METHOD,             /* 方法声明  */

    PARAMETER,          /* 参数声明  */

    CONSTRUCTOR,        /* 构造方法声明  */

    LOCAL_VARIABLE,     /* 局部变量声明  */

    ANNOTATION_TYPE,    /* 注释类型声明  */

    PACKAGE             /* 包声明  */
}

RetentionPolicy用来表示注解的保留策略的枚举类:

package java.lang.annotation;
public enum RetentionPolicy {
    SOURCE,            /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了  例如,使用@Override修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,@Override就会被抹去。 */

    CLASS,             /* 编译器将Annotation存储于类对应的.class文件中。默认行为,用于自动生成代码或文档等  */

    RUNTIME            /* 编译器将Annotation由JVM读入java内存中存放,通常我们使用自定义的注解就需要指定该策略*/
}

下面我们主要讲下JDK提供的元注解相关源码:

Retention

用来标识一个注解的保存策略,是只在java源代码中,还是编入.class文件中,或者是在运行时可以通过反射在java内存中访问。其中RetentionPolicyAnnotation 的策略属性,而 @Retention 的作用,就是配合RetentionPolicy 指定 Annotation 的策略属性。

@Retention(RetentionPolicy.RUNTIME) 的意思就是指定该 Annotation 的策略是 RetentionPolicy.RUNTIME。这就意味着,编译器会将该 Annotation 信息加载到JVM运行时数据区中。

定义 Annotation 时,@Retention 可有可无,通常自定义注解使用RetentionPolicy.RUNTIME。若没有 @Retention,则默认是 RetentionPolicy.CLASS。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * 返回保留策略的值,在RetentionPolicy的实例范围中取值
     */
    RetentionPolicy value();
}

Documented

标记这些注解是否包含在用户文档中。指示默认情况下,javadoc和类似工具将记录具有类型的注释。此类型应用于注释其注释会影响客户端使用注释元素的类型的声明。如果使用Documented对类型声明进行注释,则其注释将成为注释元素的公共API的一部分。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

Target

标记这个注解应该是哪种 Java 成员。前面我们说过,ElementTypeAnnotation 的类型属性。而 @Target 的作用,就是来指定 Annotation 的类型属性。

@Target(ElementType.TYPE) 的意思就是指定该 Annotation 的类型是 ElementType.TYPE。这就意味着,该注解类是来修饰"类、接口(包括注解类型)或枚举声明"的注解。

定义 Annotation 时,@Target 可有可无。若有 @Target,则该 Annotation 只能用于它所指定的地方;若没有 @Target,则该 Annotation 可以用于任何地方。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
	*返回可应用于注解类型的元素的数组
	*/
    ElementType[] value();
}

Inherited

它所标注的Annotation将具有继承性。举个例子,某个类 Base 使用了MyAnnotation,则 BaseClass 被注解 了MyAnnotation的注解;如果另一个类OtherClass继承了 BaseClass ,如果 MyAnnotation 上包含 @Inherited的注解,则OtherClass也具有了注解 MyAnnotation,反正则不会被MyAnnotation所注解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

注解的语法要求

  1. 使用@interface关键字定义注解
  2. 成员以无参无异常方式声明
  3. 可以用default为成员指定一个默认值
  4. 如果注解只有一个成员,则成员名必须取名value(),在使用时可以忽略成员名和赋值
    号(=);
  5. 成员类型是受限的,合法的类型包括原始类型String,Class,Annotation,Enumeration
  6. 注解类可以没有成员,没有成员的注解称为标识注解

自定义注解

示例如下:

public @interface MyAnnotation {
}

//注解一个接口
@MyAnnotation
interface inter{
}

//注解一个注解
@MyAnnotation
@interface MyAnnotationA{   
}

//注解在属性上
@MyAnnotation
class Info{
	//注解在属性上
    @MyAnnotation
    public static String desc = "info";
    
    //注解在构造器上
    @MyAnnotation
    Info(){
		System.out.println("InheritableFather:"+Info.class.isAnnotationPresent(Inherited.class));//false
	}

	//注解在方法和参数上
    @MyAnnotation
    public static void main(@MyAnnotation String[] args) {
        System.out.println(desc);
    }
}

在一个Annotation中也可以定义多个属性:

public @interface MyAnnotation {
    public String key();
    public String value();
}

@MyAnnotation(key="my",value = "Anntotation")
class Info{
}

若是想为Annotation设置默认值可以使用default关键字

public @interface MyAnnotation {
    public String key() default "default_key";
    public String value() default "default_value";
}

@MyAnnotation
class Info{
}

如果想要限制Annotation属性的取值范围,可以使用枚举

public @interface MyAnnotation {
    public String key() default "default_key";
    public String value() default "default_value";
    public Color color() default Color.RED;
}

@MyAnnotation(color = Color.BLUE)
class Info{
}

enum Color{
    RED,GREEN,BLUE;
}

使用元注解去注解一个注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited 
public @interface MyAnnotation {
}

注解与反射机制

有关Java反射机制的原理请参考这篇博文Java反射机制
通常我们的自定义注解若想取得作用,必然需要结合反射机制实现其意义。通过反射可以取得属性、方法、构造方法以及类上的所有@Retention值为RetentionPolicy.RUNTIME的注解内容。在Filed、Method、Construct、Class类中全部实现了AnnotatedElement接口,通过Annotation[] getAnnotations();
方法便可以获取。其API如下:
这里写图片描述

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    public String key() default "default_key";
    public String value() default "default_value";
}

@MyAnnotation
class Info extends Thread{
    @Override
    @Deprecated
    @SuppressWarnings({"deprecated","unused"})
    @MyAnnotation
    public void run() {
        List list = new ArrayList();
    }
}

测试如下:

    @Test
    public void test1() throws NoSuchMethodException {
        Class cs = Info.class;
        //获取run方法对象
        Method method = cs.getMethod("run");
        //获取run方法上注解,注意只能得到@Retention值为RetentionPolicy.RUNTIME的注解
        Annotation[] annotations = method.getAnnotations();
        for(Annotation an : annotations){
            System.out.println(an.toString());
            //判断是否是指定Annotation
        }

        //判断是否包含指定Annotation
        if(method.isAnnotationPresent(MyAnnotation.class)){
            //获取指定Annotation
            MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
            //获取Annotation的属性
            String key = myAnnotation.key();
            String value = myAnnotation.value();
            System.out.println(key+" --> "+value);
        }
    }

这里写图片描述

第三方框架中注解的应用

  • 作为一个服务器应用的开发者,平常我们所使用的第三方框架如mvc框架SSM、测试框架junit、微服务框架Spring Cloud、日志框架log4j等等都有注解的应用,如果我们要熟练使用和理解这些框架的原理,则注解的基础知识则是必不可少的。

我们以SpringBoot为例:

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

其**@SpringBootApplication**注解源码如下:

//下面四个就是元注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//下面三个就是SpringBoot自定义的注解,想了解的同学请自行查看源码
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = 		{TypeExcludeFilter.class}), 
	@Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {

	//@AliasFor是一个注解,用于为注解属性声明别名
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    @AliasFor(annotation = ComponentScan.class,attribute = "basePackages")
    String[] scanBasePackages() default {};

    @AliasFor(annotation = ComponentScan.class,attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值