JAVA如何自定义注解

Java注解介绍

注解是java语言用于工具处理的标注
从JVM的角度来看,注解本身对代码逻辑没有任何影响,想要实现的功能完全由程序代码决定

注解的分类(按注解的生命周期):

  1. SOURCE:形如@Override等,不会被编译到.class文件中,在编译期由编译期进行覆写方法的检查,编译后就被忽略了。
  2. CLASS:还有一些会被编译到.class,但JVM加载.class文件时不会把这类注解放到内存中,一般被一些底层库使用。
  3. RUNTIME:运行期注解,最常见的注解,会被加载到JVM中,在运行时可以获取注解信息。

注解的参数:
支持的参数类型:基本类型、String、Class以及枚举的数组。

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

例如:@Service(value=“TestServiceName”)可以写成@Service(“TestServiceName”)

如何自定义Java注解

Java语言使用@interface语法来定义注解(Annotation)

// 看一看@RestController注解源码
@Target(ElementType.TYPE) //表示@RestController可以定义在类上
@Retention(RetentionPolicy.RUNTIME) //运行期注解,可以在运行时获取注解信息并处理
@Documented // 表示该注解会出现在javadoc文档中
// RestController集成了@Controller和@ResponseBody,所以使用@RestController相当于同时使用了Controller和responseBody两个注解
@Controller
@ResponseBody
public @interface RestController {
	@AliasFor(annotation = Controller.class)
	String value() default "";
}

@Target、@Retention、@Documented都是用于修饰其他注解的元注解
我们使用这些元注解来自定义注解。

Java内置元注解说明

  • @Retention(RetentionPolicy)
    用来标识注解的生命周期(如果@Retention不存在,默认为CLASS)
    RetentionPolicy.SOURCE 仅编译期
    RetentionPolicy.CLASS (默认) 仅class文件
    RetentionPolicy.RUNTIME 运行期
    通常我们自定义的Annotation都是RUNTIME所以,务必加上@Retention(RetentionPolicy.RUNTIME)

  • @Target(ElementType)
    可以定义Annotation能够被应用于哪些注解
    ELementType.TYPE 类或接口
    ELementType.FIELD 字段
    ElementType.METHOD 方法
    ElementType.PARAMETER 方法参数
    ElementType.CONSTRUCTOR 构造方法

@Target可以接收ElementType数组:

@Target([
      ElementType.METHOD,
      ElementType.FIELD
])

表示被修饰的注解可以应用于方法和字段上

  • @Documented
    标识注解是否成为api的一部分

  • @Inherited(了解,应用不广泛)
    使用@Inherited定义子类是否可继承父类定义的Annotation,例如B继承了A,A添加了注解C(该注解被@Inherited修饰),那么B也添加了注解C(可以获取注解C上的信息)
    @Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效(类),并且仅针对class的继承,对interface的继承无效

  • @Repeatable (了解,应用不广泛,可以定义注解是否可以重复)
    注解默认只能直接修饰某个元素一次,如果修饰了多次那么会出现编译异常,如果想要注解在某个元素上出现多次,可以使用@Repeatable

自定义注解小结

  1. Java使用@interface定义注解;
  2. 可定义多个参数和默认值,核心参数使用value名称;
  3. 必须设置@Target来指定Annotation可以应用的范围;
  4. 应当设置@Retention(RetentionPolicy.RUNTIME)便于运行期读取该Annotation。

使用注解,给注解添加逻辑

单纯标注注解并不会对程序逻辑有任何影响,注解的功能由读取它并执行逻辑的代码决定

注解定义后也属于class,所有注解都继承自java.lang.annotation.Annotation
读取java注解,需要使用反射API

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

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

  • 判断注解是否存在于Class\FIeld\Method\Constructor:
    1. Class.isAnnotationPresent(Class)
    2. Field.isAnnotationPresent(Class)
    3. Method.isAnnotationPresent(Class)
    4. Constructor.isAnnotationPresent(Class)

例如:判断@Report是否存在于User类

User.class.isAnnotationPresent(Report.class);
  • 使用反射API读取注解信息
    1. XXX.getAnnotation(Class)

例如:获取User类中定义的@Report注解

// 当注解是一种接口,获取之后可以调用接口方法得到定义的值
Report report = User.class.getAnnotation(Report.class);
int type = report.type();
String level = report.level();

读取方法、字段和构造方法的Annotation和Class类似。

(方法参数上的注解,不感兴趣可以忽略这个部分)
但要读取方法参数的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) { // @Range注解
        Range r = (Range) anno;
    }
    if (anno instanceof NotNull) { // @NotNull注解
        NotNull n = (NotNull) anno;
    }
}

读取注解时一般可以先判断注解是否存在,再读取注解,或者直接读取,不存在则返回null

自定义注解Demo

JVM不会对自定义的注解添加任何逻辑,怎样处理完全由Java代码决定

下边定义一个注解@Range用来校验对象的字段长度是否符合自定义约束

Range.class

package com.annotation;

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

/**
 * @author sunhongmin
 * @Date 2020-10-21
 * @Describe 字段长度约束,默认0-255
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Range {
    int min() default 0;
    int max() default 255;
}

RangeTestDemo.class

package com.annotation;

import java.lang.reflect.Field;

class User {
    /**
     * 最大长度为10
     */
    @Range(max = 10)
    public String name;

    /**
     * 使用默认值255
     */
    @Range
    public String address;

    public User() {
    }

    public User(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

public class RangeTestDemo {

    public static void checkRange(User user) throws IllegalAccessException {
        Field[] declaredFields = user.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            // 如果标注了Range注解
            if (field.isAnnotationPresent(Range.class)) {
                // 获取注解
                Range range = field.getAnnotation(Range.class);
                // 获取field值
                Object value = field.get(user);
                if (value instanceof String) {
                    String str = (String) value;
                    if (str.length() < range.min() || str.length() > range.max()) {
                        throw new IllegalArgumentException("Invalid field:{" + field.getName() + "},value:{" + str + "},Limit range [" + range.min() + "," + range.max() + "]");
                    }

                }

            }
        }
    }

    public static void main(String[] args) throws IllegalAccessException {
        User user = new User("张大彪啊啊啊啊啊啊啊asdasdasdasda", "地址在哪呢在哪在哪在哪在哪在哪在哪在哪在哪在哪在哪在哪在哪");
        RangeTestDemo.checkRange(user);
    }

}

执行结果:
在这里插入图片描述

拓展应用

  1. 实践-仿照@EnableEurekaServer实现自动装配
  2. 大批量数据导出优化 中通过自定义注解+AOP 实现并发锁

全文结束


参考链接:
廖雪峰的官方网站

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hongmin.shm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值