Java的注解要怎么用?

简介: Java的注解使用指南

说起Java注解,可能大家并不陌生。但是注解的意义是什么呢?到底是干嘛的?我们又如何自定义注解?可能有的同学(指我自己)已经做过很长时间Java开发,甚至在Spring Boot中天天使用各种注解,但是却不知道注解的意义和机制。

今天就对Java的注解来一个总结和分享。

1,注解是干嘛的?

Java注解(Annotation)是一种用于向Java程序中添加元数据的标记,它们是Java SE 5.0版本引入的。

就像我们在日常生活中给各个物品分类,或者是整理的时候,我们会对物品贴标签一样。

例如我们在初高中做化学实验的时候,我们可以看到化学实验室中有着各种各样存放着化学试剂的瓶瓶罐罐,以及各种容器,不过在实验室中,每一个装着化学试剂的瓶子上都贴着标签,标注着其中的化学试剂是什么,有了这些标签,我们可以快速、正确地找到我们所需要的化学试剂,完成我们想要的操作。

事实上,在Java中注解就是标签,只不过,注解是给Java的类、字段、方法或者方法参数贴的标签而已。

这样,可以在程序运行或者编译代码的时候找到这些“标签”,并根据需求完成相应的操作。

相信大多数同学都使用过Spring框架以及Spring Boot进行开发,我们常常使用@Component注解标注类,使其在IoC容器初始化的时候,把这些类实例化为Bean并注册到容器中。Spring框架怎么知道要把哪些类实例化为Bean呢?就是通过去“看”哪些类上面有相关的注解(@Component@Service等等)。可见打上了这些注解,就像是给这个类贴上了个标签,Spring框架在启动时,就可以找到这些“贴了标签”的类了!

2,注解的类型

在Java中,注解也分为几种类型,我们常常从注解的作用目标类型和注解的生命周期两个角度来认识注解的分类。

(1) 作用目标类型

注解的作用目标类型其实就指的是这个注解是标注在什么东西上面的。

前面我们把注解比作标签,那就可以说作用目标类型就表示这个标签贴在什么上面。在Java中,我们常常把“标签”贴在类、方法、成员变量和方法参数上。

所以说,注解根据作用目标类型分为下面几种类型:

  • 类型注解:标注在一个类上的注解,例如Spring框架中的@Component注解
  • 字段注解:标注在类中成员变量上的注解,例如Spring框架常常使用的@Autowired自动装配注解
  • 方法注解:标注在类中方法上的注解,例如@Override标记的方法就表示这个方法是重写了父类的方法(或者实现接口方法的方法)
  • 参数注解:标注在一个方法形参上的注解,例如Spring MVC中,我们用@RequestParam注解表示一个参数是需要在请求URL中获取的
  • 构造方法注解:标注在类中构造函数上的注解,例如@Autowired自动装配注解也可以用在构造函数上,使得Spring框架通过这个类的构造函数完成依赖注入
  • 元注解:标注在注解类上的注解,例如下面我们自定义注解时会接触到的@Target注解,用于指定这个自定义注解的作用目标类型

(2) 生命周期

贴好了标签,我们当然也需要在适当的时候去识别或者读取标签。

所以注解的生命周期指的是在程序的哪个阶段可以读取和处理这个注解的信息,换句话讲,就是什么时候去读取这个标签。在Java中,我们常常在程序运行或者编译的时候去读取“标签”。

所以,根据生命周期我们将注解分为下面几类:

  • 源代码注解:这种注解只是存在于源代码中,编译后不会保留
  • 编译时注解:在编译时生效,并被编译器读取的注解,例如@Deprecated注解表示某个方法是弃用的方法,这样编译器编译时会显示警告
  • 运行时注解:在程序运行时生效,这可能也是我们见得最多的一类注解了,例如@Component注解就是在Spring框架运行的时候去读取到的,通常我们通过反射的方式去读取运行时注解

3,自定义一个注解

我们首先需要定义注解类,通过@interface关键字去定义注解类。

例如我现在想定义一个类型注解,这个注解用于标注一个类的中文名。(可以说我现在想做个标签,标签贴在类上面,标签上写这个类的中文名)

定义注解类如下:

package com.gitee.swsk33.annotationdemo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 定义一个名为Name的注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {
     
   
   

    /**
     * 注解类的属性:中文名
     */
    String chineseName();

}

这样,我们就完成了一个自定义的注解了!上述代码有下列要点:

  • 通过@Target注解标注我们这个自定义注解的作用目标类型,传入ElementType枚举,其中:
    • ElementType.TYPE 作用于类、接口、枚举类上的注解
    • ElementType.FIELD 作用于类的属性上
    • ElementType.METHOD 作用于方法上
    • ElementType.PARAMETER 作用于方法的参数
    • ElementType.CONSTRUCTOR 作用于构造函数
    • ElementType.ANNOTATION_TYPE 作用于注解类
  • 通过@Retention注解标注我们这个自定义注解的生命周期,传入RetentionPolicy枚举,其中:
    • RetentionPolicy.SOURCE 在源码层面,作为纯注释
    • RetentionPolicy.CLASS 在编译阶段有效
    • RetentionPolicy.RUNTIME 在运行阶段有效
  • 注解类也是一个类,所以注解类和其它类一样可以有自己的属性(字段),上述注解类中就有一个属性chineseName,只不过注解中属性要写成方法的形式

需要说明的是,自定义的注解作用目标类型可以是多个,例如我需要上述注解既能够标注在类上面也可以标注在方法上,就将@Target中参数改如下:

@Target({
     
   
   ElementType.TYPE, ElementType.METHOD})

4,读取注解

在运行的时候,我们通过反射的方式读取注解。

(1) 读取类注解

我们新建两个类,并将我们上述自定义注解标注上去试试:

@Name(chineseName = "人类")
public class Person {
     
   
   

    // 省略内容

}

@Name(chineseName = "猫类")
public class Cat {
     
   
   

    // 省略内容

}

好的,我们现在就给这两个类“贴好标签”了!并且分别在标签的chineseName这一栏写上了“人类”和“猫类”。

现在在主方法就可以读取这个注解及其内容:

package com.gitee.swsk33.annotationdemo;

import com.gitee.swsk33.annotationdemo.annotation.Name;
import com.gitee.swsk33.annotationdemo.model.Cat;
import com.gitee.swsk33.annotationdemo.model.Person;

public class Main {
     
   
   

    public static void main(String[] args) {
     
   
   
        // 获取Person和Cat上面的注解以及其属性内容
        Name personAnnotation = Person.class.getAnnotation(Name.class);
        Name catAnnotation = Cat.class.getAnnotation(Name.class);
        // 读取两个注解的属性
        System.out.println(personAnnotation.chineseName());
        System.out.println(catAnnotation.chineseName());
    }

}

结果:

image.png

可见获取一个类上的注解很简单,通过得到类的Class对象,调用其getAnnotation方法即可,方法中传入要读取的注解的类型即可,上述就要读取两个类上标注的Name注解,因此传入这个类型。

事实上,在Java中我们对类和对象的概念非常熟悉了,在上述我们声明了注解类Name,然后在PersonCat类上面标注了注解,这里PersonCat类上面标注的注解就是Name注解类的实例(对象)

(2) 读取方法注解

读取方法注解的方式也不难,我们通过反射的方式获取到一个类中所有的方法对象,就可以获取其注解了!

假设现在我想给类中的方法“贴标签”并写上该方法的中文名呢?

首先把上述Name注解的@Target属性改成方法注解的,如下:

package com.gitee.swsk33.annotationdemo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 定义一个名为Name的注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {
     
   
   

    /**
     * 注解类的属性:中文名
     */
    String chineseName();

}

然后定义猫类如下,并在其方法上标注注解:

package com.gitee.swsk33.annotationdemo.model;

import com.gitee.swsk33.annotationdemo.annotation.Name;

public class Cat {
     
   
   

    @Name(chineseName = "奔跑方法")
    public void run() {
     
   
   
        System.out.println("run");
    }

    @Name(chineseName = "吃饭方法")
    public void eat() {
     
   
   
        System.out.println("eat");
    }

    public void sleep() {
     
   
   
        System.out.println("sleep");
    }

}

然后在主类中,通过反射的方式获取猫类中所有的方法对象,再根据方法对象获取注解:

package com.gitee.swsk33.annotationdemo;

import com.gitee.swsk33.annotationdemo.annotation.Name;
import com.gitee.swsk33.annotationdemo.model.Cat;

import java.lang.reflect.Method;

public class Main {
     
   
   

    public static void main(String[] args) {
     
   
   
        // 获取Cat类所有方法
        Method[] methods = Cat.class.getDeclaredMethods();
        // 遍历方法并获取到方法上的Name注解
        for (Method method : methods) {
     
   
   
            // 获取该方法上标注的Name类型注解
            Name methodNameAnnotation = method.getAnnotation(Name.class);
            if (methodNameAnnotation == null) {
     
   
   
                System.out.println("方法" + method.getName() + "上面没有注解!");
                continue;
            }
            // 读取注解对象的chineseName属性值
            System.out.println("方法" + method.getName() + "的中文名是:" + methodNameAnnotation.chineseName());
        }
    }

}

运行结果:

image.png

可见这和获取类上的注解是一样的,反射得到的方法Method对象也是有getAnnotation方法的,如果某个方法上面没有这种类型的注解,就会返回null

(3) 读取字段注解

假设现在我想给类中的字段“贴标签”并写上该字段的中文名呢?

同样地,修改Name注解类的@Target,使其作用于字段:

package com.gitee.swsk33.annotationdemo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 定义一个名为Name的注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {
     
   
   

    /**
     * 注解类的属性:中文名
     */
    String chineseName();

}

然后定义猫类如下:

package com.gitee.swsk33.annotationdemo.model;

import com.gitee.swsk33.annotationdemo.annotation.Name;

public class Cat {
     
   
   

    @Name(chineseName = "猫的名字")
    private String name;

    @Name(chineseName = "猫的品种")
    private String type;

    private int age;

}

在主类中,通过反射的方式获取字段对象并获取注解即可,这和读取方法注解类似:

package com.gitee.swsk33.annotationdemo;

import com.gitee.swsk33.annotationdemo.annotation.Name;
import com.gitee.swsk33.annotationdemo.model.Cat;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main {
     
   
   

    public static void main(String[] args) {
     
   
   
        // 获取Cat类所有字段
        Field[] fields = Cat.class.getDeclaredFields();
        // 遍历所有字段并获取字段上的Name注解
        for (Field field : fields) {
     
   
   
            // 获取该字段上标注的Name类型注解
            Name fieldAnnotation = field.getAnnotation(Name.class);
            if (fieldAnnotation == null) {
     
   
   
                System.out.println("字段" + field.getName() + "上面没有注解!");
                continue;
            }
            // 读取注解对象的chineseName属性值
            System.out.println("字段" + field.getName() + "的中文名是:" + fieldAnnotation.chineseName());
        }
    }

}

结果:

image.png

可见,获取注解的方式并不难,通过反射得到对应的类、方法、字段对象,再调用getAnnotation方法即可!

事实上,获取构造函数注解等也是一样的,这里不再赘述了。

5,注解属性赋值

上述在打注解的时候,我们也同时设定了对应的属性值,就像是在贴标签时在上面写上相应的内容一样。

注解的属性赋值还有一些要点,我们可以注意一下。

(1) 设定属性默认值

假设在上述标注@Name注解时,我们没有指定其中chineseName的话,就会报错:

image.png

事实上,我们可以给注解的chineseName属性指定默认值,修改Name注解类如下:

package com.gitee.swsk33.annotationdemo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 定义一个名为Name的注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {
     
   
   

    /**
     * 注解类的属性:中文名
     */
    String chineseName() default "没名字";

}

chineseName属性后面加上default可以指定当不设定chineseName属性值时的默认值,上述就设定了默认值没名字

这时,标注注解不设定chineseName属性的值,也不会报错:

image.png

这样的话,如果读取这个name字段上面的注解的chineseName属性时,那就会得到没名字

(2) 多属性值设定

假设再在@Name注解类中加一个字段englishName如下:

package com.gitee.swsk33.annotationdemo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 定义一个名为Name的注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {
     
   
   

    /**
     * 注解类的属性:中文名
     */
    String chineseName() default "没名字";

    /**
     * 注解类属性:英文名
     */
    String englishName() default "undefined";

}

那么使用这个注解的时候,就可以同时指定两个属性值:

package com.gitee.swsk33.annotationdemo.model;

import com.gitee.swsk33.annotationdemo.annotation.Name;

public class Cat {
     
   
   

    // 省略其它字段

    @Name(chineseName = "猫的品种", englishName = "catType")
    private String type;

}

注解后面括号中,通过属性名 = 值的形式指定即可,多个属性之间使用英文逗号,隔开。

(3) 默认属性

大家发现,我们自定义的注解使用时,传入属性值需要指定属性名,但是为什么@Target注解就可以省略属性名呢?

image.png

可以看看@Target注解源码:

image.png

相信大家能够恍然大悟了:在注解中可以指定一个默认属性叫做value,如果注解类中有且只有这个默认属性,那么传入值的时候就可以省略属性名直接传值了!

修改上述@Name注解类如下:

package com.gitee.swsk33.annotationdemo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 定义一个名为Name的注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {
     
   
   

    String value();

}

然后使用时就可以这么写了:

@Name("猫的品种")
private String type;

这等效于:

@Name(value = "猫的品种")
private String type;

当然,获取到这个注解对象的时候,也只需要读取value属性:

// 获取注解对象
Name fieldAnnotation = field.getAnnotation(Name.class);
// 读取注解的value属性值
System.out.println("字段" + field.getName() + "的中文名是:" + fieldAnnotation.value());

(4) 数组属性传值

我们再仔细观察一下@Target注解,发现其value属性是数组类型的:

image.png

那为什么传入参数的时候可以不传数组呢?

image.png

这是因为如果你传入的数组中只有一个元素,那就可以直接写你传入的元素即可!

例如@Target(ElementType.FIELD),我们传入的数组只有ElementType.FIELD这个元素,那么就不用写成数组形式了!当然,这和@Target({ ElementType.FIELD })是等效的。

相关文章
|
16天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
52 7
|
2月前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
98 43
Java学习十六—掌握注解:让编程更简单
|
21天前
|
Java 编译器 数据库
Java 中的注解(Annotations):代码中的 “元数据” 魔法
Java注解是代码中的“元数据”标签,不直接参与业务逻辑,但在编译或运行时提供重要信息。本文介绍了注解的基础语法、内置注解的应用场景,以及如何自定义注解和结合AOP技术实现方法执行日志记录,展示了注解在提升代码质量、简化开发流程和增强程序功能方面的强大作用。
60 5
|
1月前
|
Java 开发者 Spring
[Java]自定义注解
本文介绍了Java中的四个元注解(@Target、@Retention、@Documented、@Inherited)及其使用方法,并详细讲解了自定义注解的定义和使用细节。文章还提到了Spring框架中的@AliasFor注解,通过示例帮助读者更好地理解和应用这些注解。文中强调了注解的生命周期、继承性和文档化特性,适合初学者和进阶开发者参考。
57 14
|
1月前
|
前端开发 Java
[Java]讲解@CallerSensitive注解
本文介绍了 `@CallerSensitive` 注解及其作用,通过 `Reflection.getCallerClass()` 方法返回调用方的 Class 对象。文章还详细解释了如何通过配置 VM Options 使自定义类被启动类加载器加载,以识别该注解。涉及的 VM Options 包括 `-Xbootclasspath`、`-Xbootclasspath/a` 和 `-Xbootclasspath/p`。最后,推荐了几篇关于 ClassLoader 的详细文章,供读者进一步学习。
37 12
|
1月前
|
Java 编译器
Java进阶之标准注解
Java进阶之标准注解
34 0
|
2月前
|
JSON Java 数据库
java 常用注解大全、注解笔记
关于Java常用注解的大全和笔记,涵盖了实体类、JSON处理、HTTP请求映射等多个方面的注解使用。
43 0
java 常用注解大全、注解笔记
|
3月前
|
Arthas Java 测试技术
Java字节码文件、组成,jclasslib插件、阿里arthas工具,Java注解
Java字节码文件、组成、详解、分析;常用工具,jclasslib插件、阿里arthas工具;如何定位线上问题;Java注解
Java字节码文件、组成,jclasslib插件、阿里arthas工具,Java注解
|
2月前
|
IDE Java 编译器
java的反射与注解
java的反射与注解
20 0
|
3月前
|
Java 编译器 程序员
Java注解,元注解,自定义注解的使用
本文讲解了Java中注解的概念和作用,包括基本注解的用法(@Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface),Java提供的元注解(@Retention, @Target, @Documented, @Inherited),以及如何自定义注解并通过反射获取注解信息。
Java注解,元注解,自定义注解的使用