Java常用注解


注解

使用注解

什么是注解(Annotation)?注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”:

// this is a component:
@Resource("hello")
public class Hello {
    @Inject
    int n;

    @PostConstruct
    public void hello(@Param String name) {
        System.out.println(name);
    }

    @Override
    public String toString() {
        return "Hello";
    }
}

注释会被编译器直接忽略,注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”。

注解的作用

从JVM的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。

Java的注解可以分为三类:

第一类是由编译器使用的注解,例如:

  • @Override:让编译器检查该方法是否正确地实现了覆写;
  • @SuppressWarnings:告诉编译器忽略此处代码产生的警告。
    这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。

第二类是由工具处理.class文件使用的注解,比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。
这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。

第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,这也是最常用的注解。例如,一个配置了@PostConstruct的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)。

定义一个注解时,还可以定义配置参数。配置参数可以包括:

  • 所有基本类型;
  • String;
  • 枚举类型;
  • 基本类型、String、Class以及枚举的数组。

因为配置参数必须是常量,所以,上述限制保证了注解在定义时就已经确定了每个参数的值。

注解的配置参数可以有默认值,缺少某个配置参数时将使用默认值。

此外,大部分注解会有一个名为value的配置参数,对此参数赋值,可以只写常量,相当于省略了value参数。

如果只写注解,相当于全部使用默认值。

举个栗子,对以下代码:

public class Hello {
    @Check(min=0, max=100, value=55)
    public int n;

    @Check(value=99)
    public int p;

    @Check(99) // @Check(value=99)
    public int x;

    @Check
    public int y;
}

@Check就是一个注解。第一个@Check(min=0, max=100, value=55)明确定义了三个参数,第二个@Check(value=99)只定义了一个value参数,它实际上和@Check(99)是完全一样的。最后一个@Check表示所有参数都使用默认值。

定义注解

Java语言使用@interface语法来定义注解(Annotation),它的格式如下:

public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

注解的参数类似无参数方法,可以用default设定一个默认值(强烈推荐)。最常用的参数应当命名为value

元注解

有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解。

@Target

最常用的元注解是@Target。使用@Target可以定义Annotation能够被应用于源码的哪些位置:

  • 类或接口:ElementType.TYPE
  • 字段:ElementType.FIELD
  • 方法:ElementType.METHOD
  • 构造方法:ElementType.CONSTRUCTOR
  • 方法参数:ElementType.PARAMETER

例如,定义注解@Report可用在方法上,我们必须添加一个@Target(ElementType.METHOD)

@Target(ElementType.METHOD)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

定义注解@Report可用在方法或字段上,可以把@Target注解参数变为数组{ ElementType.METHOD, ElementType.FIELD }

@Target({
    ElementType.METHOD,
    ElementType.FIELD
})
public @interface Report {
    ...
}

实际上@Target定义的valueElementType[]数组,只有一个元素时,可以省略数组的写法。

@Retention

另一个重要的元注解@Retention定义了Annotation的生命周期:

  • 仅编译期:RetentionPolicy.SOURCE
  • 仅class文件:RetentionPolicy.CLASS
  • 运行期:RetentionPolicy.RUNTIME

如果@Retention不存在,则该Annotation默认为CLASS。因为通常我们自定义的Annotation都是RUNTIME,所以,务必要加上@Retention(RetentionPolicy.RUNTIME)这个元注解:

@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}
@Repeatable

使用@Repeatable这个元注解可以定义Annotation是否可重复。这个注解应用不是特别广泛。

@Repeatable(Reports.class)
@Target(ElementType.TYPE)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

@Target(ElementType.TYPE)
public @interface Reports {
    Report[] value();
}

经过@Repeatable修饰后,在某个类型声明处,就可以添加多个@Report注解:

@Report(type=1, level="debug")
@Report(type=2, level="warning")
public class Hello {
}
@Inherited

使用@Inherited定义子类是否可继承父类定义的Annotation@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效:

@Inherited
@Target(ElementType.TYPE)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

在使用的时候,如果一个类用到了@Report

@Report(type=1)
public class Person {
}

则它的子类默认也定义了该注解:

public class Student extends Person {
}
如何定义Annotation

第一步,用@interface定义注解:

public @interface Report {
}

第二步,添加参数、默认值:

public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

把最常用的参数定义为value(),推荐所有参数都尽量设置默认值。

第三步,用元注解配置注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

其中,必须设置@Target@Retention@Retention一般设置为RUNTIME,因为我们自定义的注解通常要求在运行期读取。一般情况下,不必写@Inherited@Repeatable

处理注解

Java的注解本身对代码逻辑没有任何影响。根据@Retention的配置:

  • SOURCE类型的注解在编译期就被丢掉了;
  • CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM;
  • RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取。

如何使用注解完全由工具决定。SOURCE类型的注解主要由编译器使用,因此我们一般只使用,不编写。CLASS类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。只有RUNTIME类型的注解不但要使用,还经常需要编写。

因此,我们只讨论如何读取RUNTIME类型的注解。

因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。

Java提供的使用反射API读取Annotation的方法包括:

判断某个注解是否存在于ClassFieldMethodConstructor

  • Class.isAnnotationPresent(Class)
  • Field.isAnnotationPresent(Class)
  • Method.isAnnotationPresent(Class)
  • Constructor.isAnnotationPresent(Class)

例如:

// 判断@Report是否存在于Person类:
Person.class.isAnnotationPresent(Report.class);

使用反射API读取Annotation

  • Class.getAnnotation(Class)
  • Field.getAnnotation(Class)
  • Method.getAnnotation(Class)
  • Constructor.getAnnotation(Class)

例如:

// 获取Person定义的@Report注解:
Report report = Person.class.getAnnotation(Report.class);
int type = report.type();
String level = report.level();

使用反射API读取Annotation有两种方法。方法一是先判断Annotation是否存在,如果存在,就直接读取:

Class cls = Person.class;
if (cls.isAnnotationPresent(Report.class)) {
    Report report = cls.getAnnotation(Report.class);
    ...
}

第二种方法是直接读取Annotation,如果Annotation不存在,将返回null

Class cls = Person.class;
Report report = cls.getAnnotation(Report.class);
if (report != null) {
   ...
}

读取方法、字段和构造方法的AnnotationClass类似。但要读取方法参数的Annotation就比较麻烦一点,因为方法参数本身可以看成一个数组,而每个参数又可以定义多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示。
例如,对于以下方法定义的注解:

public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
}

要读取方法参数的注解,我们先用反射获取Method实例,然后读取方法参数的所有注解:

// 获取Method实例:
Method m = ...
// 获取所有参数的Annotation:
Annotation[][] annos = m.getParameterAnnotations();
// 第一个参数(索引为0)的所有Annotation:
Annotation[] annosOfName = annos[0];
for (Annotation anno : annosOfName) {
    if (anno instanceof Range r) { // @Range注解
        r.max();
    }
    if (anno instanceof NotNull n) { // @NotNull注解
        //
    }
}
使用注解

注解如何使用,完全由程序自己决定。例如,JUnit是一个测试框架,它会自动运行所有标记为@Test的方法。

我们来看一个@Range注解,我们希望用它来定义一个String字段的规则:字段长度满足@Range的参数定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
    int min() default 0;
    int max() default 255;
}

在某个JavaBean中,我们可以使用该注解:

public class Person {
    @Range(min=1, max=20)
    public String name;

    @Range(max=10)
    public String city;
}

但是,定义了注解,本身对程序逻辑没有任何影响。我们必须自己编写代码来使用注解。这里,我们编写一个Person实例的检查方法,它可以检查Person实例的String字段长度是否满足@Range的定义:

void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {
    // 遍历所有Field:
    for (Field field : person.getClass().getFields()) {
        // 获取Field定义的@Range:
        Range range = field.getAnnotation(Range.class);
        // 如果@Range存在:
        if (range != null) {
            // 获取Field的值:
            Object value = field.get(person);
            // 如果值是String:
            if (value instanceof String s) {
                // 判断值是否满足@Range的min/max:
                if (s.length() < range.min() || s.length() > range.max()) {
                    throw new IllegalArgumentException("Invalid field: " + field.getName());
                }
            }
        }
    }
}

这样一来,我们通过@Range注解,配合check()方法,就可以完成Person实例的检查。注意检查逻辑完全是我们自己编写的,JVM不会自动给注解添加任何额外的逻辑。

Java

Spring

核心注解

注解说明支持版本
@Component标识一个类为 Spring 组件Spring 2.5+
@Repository标识数据访问组件Spring 2.0+
@Service标识服务层组件Spring 2.0+
@Controller标识控制器组件Spring 2.5+
@RestController@Controller + @ResponseBodySpring 4.0+
@Configuration标识配置类Spring 3.0+
@Bean声明一个方法的返回值为 BeanSpring 3.0+
@Autowired自动装配依赖Spring 2.5+
@Qualifier指定注入的 Bean 名称Spring 2.5+
@Primary优先注入的 BeanSpring 3.0+
@Value注入属性值Spring 3.0+
@PropertySource加载属性文件Spring 3.1+
@Profile指定组件在特定环境下激活Spring 3.1+
@Scope定义 Bean 的作用域Spring 2.5+
@Lazy延迟初始化 BeanSpring 3.0+
@DependsOn定义 Bean 初始化依赖Spring 3.0+

Web MVC 注解

注解说明支持版本
@RequestMapping映射请求路径Spring 2.5+
@GetMappingGET 请求映射Spring 4.3+
@PostMappingPOST 请求映射Spring 4.3+
@PutMappingPUT 请求映射Spring 4.3+
@DeleteMappingDELETE 请求映射Spring 4.3+
@PatchMappingPATCH 请求映射Spring 4.3+
@RequestParam获取请求参数Spring 2.5+
@PathVariable获取路径变量Spring 3.0+
@RequestBody获取请求体内容Spring 3.0+
@ResponseBody返回内容直接写入响应体Spring 3.0+
@ModelAttribute绑定参数到模型对象Spring 2.5+
@SessionAttribute访问会话属性Spring 3.1+
@CookieValue获取 Cookie 值Spring 3.0+
@RequestHeader获取请求头值Spring 3.0+
@CrossOrigin跨域支持Spring 4.2+
@ExceptionHandler异常处理方法Spring 3.0+
@ControllerAdvice全局控制器增强Spring 3.2+
@ResponseStatus定义响应状态码Spring 3.0+

事务注解

注解说明支持版本
@Transactional声明事务Spring 2.0+
@EnableTransactionManagement启用事务管理Spring 3.1+

数据访问注解

注解说明支持版本
@EntityJPA 实体类JPA 1.0+
@Table指定实体对应的表JPA 1.0+
@Id标识主键JPA 1.0+
@GeneratedValue主键生成策略JPA 1.0+
@Column指定字段映射JPA 1.0+
@OneToOne一对一关系JPA 1.0+
@OneToMany一对多关系JPA 1.0+
@ManyToOne多对一关系JPA 1.0+
@ManyToMany多对多关系JPA 1.0+
@JoinColumn指定关联列JPA 1.0+
@Query自定义查询语句Spring Data 1.0+
@Modifying标识修改操作Spring Data 1.0+
@EnableJpaRepositories启用 JPA 仓库Spring Data 1.0+

Spring Boot 特有注解

注解说明支持版本
@SpringBootApplication主启动类注解Spring Boot 1.2+
@EnableAutoConfiguration启用自动配置Spring Boot 1.0+
@ComponentScan组件扫描Spring 3.1+
@ConditionalOnClass类路径存在时生效Spring Boot 1.0+
@ConditionalOnMissingBeanBean 不存在时生效Spring Boot 1.0+
@ConditionalOnProperty属性条件判断Spring Boot 1.0+
@ConfigurationProperties绑定配置属性Spring Boot 1.0+
@EnableConfigurationProperties启用配置属性绑定Spring Boot 1.0+
@EnableAsync启用异步方法Spring 3.0+
@Async标识异步方法Spring 3.0+
@EnableScheduling启用定时任务Spring 3.0+
@Scheduled定时任务方法Spring 3.0+
@EnableCaching启用缓存Spring 3.1+
@Cacheable缓存方法结果Spring 3.1+
@CacheEvict清除缓存Spring 3.1+
@CachePut更新缓存Spring 3.1+

测试注解

注解说明支持版本
@SpringBootTestSpring Boot 测试Spring Boot 1.4+
@DataJpaTestJPA 测试Spring Boot 1.4+
@WebMvcTestMVC 控制器测试Spring Boot 1.4+
@MockBean模拟 BeanSpring Boot 1.4+
@TestJUnit 测试方法JUnit 4+
@BeforeEach每个测试方法前执行JUnit 5+
@AfterEach每个测试方法后执行JUnit 5+
@BeforeAll所有测试方法前执行JUnit 5+
@AfterAll所有测试方法后执行JUnit 5+

安全注解 (Spring Security)

注解说明支持版本
@EnableWebSecurity启用 Web 安全Spring Security 3.2+
@PreAuthorize方法执行前权限检查Spring Security 3.0+
@PostAuthorize方法执行后权限检查Spring Security 3.0+
@Secured方法权限检查Spring Security 2.0+
@RolesAllowedJSR-250 角色检查Spring Security 2.0+
@EnableGlobalMethodSecurity启用方法级安全Spring Security 3.0+

Spring Cache

@Cacheable

添加在方法上,缓存方法的执行结果。执行过程如下:

  • 1)首先,判断方法执行结果的缓存。如果有,则直接返回该缓存结果。
  • 2)然后,执行方法,获得方法结果。
  • 3)之后,根据是否满足缓存的条件。如果满足,则缓存方法结果到缓存。
  • 4)最后,返回方法结果。

@CachePut

添加在方法上,缓存方法的执行结果。不同于 @Cacheable 注解,它的执行过程如下:

  • 1)首先,执行方法,获得方法结果。也就是说,无论是否有缓存,都会执行方法。
  • 2)然后,根据是否满足缓存的条件。如果满足,则缓存方法结果到缓存。
  • 3)最后,返回方法结果。

@CacheEvict

添加在方法上,删除缓存

Lombok

注解说明支持版本
@val用在局部变量前面,相当于将变量声明为final-
@NonNull给方法参数增加这个注解会自动在方法内对该参数进行是否为空的校验,如果为空,则抛出NPE(NullPointerException)-
@Cleanup自动管理资源,用在局部变量之前,在当前变量范围内即将执行完毕退出之前会自动清理资源,自动生成try-finally这样的代码来关闭流-
@Getter/@Setter用在属性上,再也不用自己手写setter和getter方法了,还可以指定访问范围-
@ToString用在类上,可以自动覆写toString方法,当然还可以加其他参数,例如@ToString(exclude=”id”)排除id属性,或者@ToString(callSuper=true, includeFieldNames=true)调用父类的toString方法,包含所有属性-
@EqualsAndHashCode用在类上,自动生成equals方法和hashCode方法-
@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor用在类上,自动生成无参构造和使用所有参数的构造函数以及把所有@NonNull属性作为参数的构造函数,如果指定staticName = “of”参数,同时还会生成一个返回类对象的静态工厂方法,比使用构造函数方便很多-
@Data注解在类上,相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstrutor这些注解,对于POJO类十分有用-
@Value用在类上,是@Data的不可变形式,相当于为属性添加final声明,只提供getter方法,而不提供setter方法-
@Builder用在类、构造器、方法上,为你提供复杂的builder APIs,让你可以像如下方式一样调用Person.builder().name(“Adam Savage”).city(“San Francisco”).job(“Mythbusters”).job(“Unchained Reaction”).build();更多说明参考Builder-
@SneakyThrows自动抛受检异常,而无需显式在方法上使用throws语句-
@Synchronized用在方法上,将方法声明为同步的,并自动加锁,而锁对象是一个私有的属性$lock或$LOCK,而java中的synchronized关键字锁对象是this,锁在this或者自己的类对象上存在副作用,就是你不能阻止非受控代码去锁this或者类对象,这可能会导致竞争条件或者其它线程错误-
@Getter(lazy=true)可以替代经典的Double Check Lock样板代码-
@Log根据不同的注解生成不同类型的log对象,但是实例名称都是log,有六种可选实现类-
  • @CommonsLog Creates log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
  • @Log Creates log = java.util.logging.Logger.getLogger(LogExample.class.getName());
  • @Log4j Creates log = org.apache.log4j.Logger.getLogger(LogExample.class);
  • @Log4j2 Creates log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
  • @Slf4j Creates log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
  • @XSlf4j Creates log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);

Hibernate Validator

注解说明支持版本
@NotBlank只能用于字符串不为 null ,并且字符串 #trim() 以后 length 要大于 0-
@NotEmpty集合对象的元素不为 0 ,即集合不为空,也可以用于字符串不为 null-
@NotNull不能为 null
@Pattern(value)被注释的元素必须符合指定的正则表达式-
@Max(value)该字段的值只能小于或等于该值-
@Min(value)该字段的值只能大于或等于该值-
@Range(min=, max=)检被注释的元素必须在合适的范围内-
@Size(max, min)检查该字段的 size 是否在 min 和 max 之间,可以是字符串、数组、集合、Map 等-
@Length(max, min)被注释的字符串的大小必须在指定的范围内。-
@AssertFalse被注释的元素必须为 false-
@AssertTrue被注释的元素必须为 true-
@Email被注释的元素必须是电子邮箱地址-
@URL(protocol=,host=,port=,regexp=,flags=)被注释的字符串必须是一个有效的 URL-
@Null必须为 null-
@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值-
@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值-
@Digits(integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内-
@Positive判断正数-
@PositiveOrZero判断正数或 0-
@Negative判断负数-
@NegativeOrZero判断负数或 0-
@Future被注释的元素必须是一个将来的日期-
@FutureOrPresent判断日期是否是将来或现在日期-
@Past检查该字段的日期是在过去-
@PastOrPresent判断日期是否是过去或现在日期-
@SafeHtml判断提交的 HTML 是否安全。例如说,不能包含 JavaScript 脚本等等-

easyExcel

注解说明支持版本
@ExcelProperty这是最常用的一个注解,注解中有三个参数value,index,converter分别代表列明,列序号,数据转换方式,value和index只能二选一,通常不用设置converter-
@ColumnWith用于设置列宽度的注解,注解中只有一个参数value,value的单位是字符长度,最大可以设置255个字符,因为一个excel单元格最大可以写入的字符个数就是255个字符。-
@ContentFontStyle用于设置单元格内容字体格式的注解-
@ContentLoopMerge用于设置合并单元格的注解-
@ContentRowHeight用于设置行高-
@ContentStyle设置内容格式注解-
@HeadFontStyle用于定制标题字体格式-
@HeadRowHeight设置标题行行高-
@HeadStyle设置标题样式-
@ExcelIgnore不将该字段转换成Excel-
@ExcelIgnoreUnannotated没有注解的字段都不转换-

References

https://liaoxuefeng.com/books/java/annotation/index.html

https://learnku.com/docs/springboot-like-laravel/spring-zhu-jie-da-quan/16925

https://sites.google.com/site/javahuide9/brief/lombok-in-10-minutes

https://juejin.cn/post/6844904177974542343


文章作者: 江湖义气
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 江湖义气 !
  目录