Java速通12:注解

106 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情

一.注解分类

1.1、源码注解:只在源码中存在,编译时不存在。
1.2、编译时注解:注解在源码和.class编译时都存在。
1.3、运行时注解:在运行阶段存在起作用。

二.元注解:给注解进行注解

2.1、@Target: 作用域,即注解可在什么地方使用

  • package:包声明
  • type:类、接口声明
  • constructor:构造方法声明
  • method:方法声明
  • field:字段声明
  • parameter:参数声明
  • local_variable:局部变量声明

2.2、@Retention:生命周期,即注解在什么时候还有用

  • source:只在源码显示,编译时会丢弃。用@Retention(RetentionPolicy.SOURCE)修饰的注解,表示注解的信息会被编译器抛弃,不会留在class文件中,注解的信息只会留在源文件中。如@Override@Test
  • class:编译时会记录到class中,运行时忽略。用@Retention(RetentionPolicy.CLASS)修饰的注解,表示注解的信息在程序编译时被保留在class文件(字节码文件)中,但不会被虚拟机读取在运行的时候。
  • runtime:运行时存在,可以通过反射读取。用@Retention(RetentionPolicy.RUNTIME)修饰的注解,表示注解的信息被保留在class文件(字节码文件)中当程序编译时,会被虚拟机保留在运行时,所以可以用反射的方式读取。如@Controller

2.3、Inherited:标识注解(允许子类继承父类的 被该注解标注的注解)

2.4、Documented:被该注解标注的注解 将被包含在Javadoc中(文档注释)

三.内置注解

3.1、@Override:重写。
3.2、@Deprecated:用于修饰已经过时的方法,即不推荐使用。
3.3、@SuppressWarings("deprecation"):用于通知java编译器忽略特定的编译警告(镇压警告)。

四.自定义注解

4.1、注解格式

  1. 注解里的每个方法,实际是声明注解中的一个参数。方法的返回值类型就是注解的参数类型:类型只能是基本数据类型StringClassAnnotationEnumeration
  2. default声明参数默认值。
  3. 注解类可以没有成员,没有成员的注解成为标识注解。
  4. 若注解只有一个参数,一般命名为value,调用时value可省略。
// 注解格式:public @interface 注解名 {定义内容}
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
    // 参数类型 + 参数名() + [default]
    String name();
    int age() default 0;
    String[] school() default "";
}

4.2、调用

public class Test {
    // 参数顺序无要求,有默认值的参数可不写
    // @MyAnnotation(name = "aa", school = "bb", age = 10)
    @MyAnnotation(name = "aa")
    public void test() {
        System.out.println("注解");
    }
}

4.3、反射获取 方法上 指定注解,并执行被注解标注的方法

@Test
public void test2() throws ClassNotFoundException {
    Class clazz = Class.forName("com.liu.demo.Test");
    Method[] methods = clazz.getMethods();
    if (methods != null) {
        for (Method method : methods) {
            // 判断方法是否有 MyAnnotation注解
            boolean isAnno = method.isAnnotationPresent(MyAnnotation.Class);
            if (isAnno) {
                // 执行被MyAnnotation 标注的方法
                method.invoke(
                    clazz.getConstructor(null).newInstance(null)
                    , null);
            }
        }
    }
}

4.4、反射获取 类上 指定注解

@Test
public void test2() throws ClassNotFoundException {
    Class clazz = Class.forName("com.liu.demo.Test");
	Annotation anno = clazz.getAnnotation(MyAnnotation.Class);
    if (anno != null) {
        System.out.println("该方法含有 MyAnnotation注解");
    }
}

五.SpringBoot上自定义注解

举例: 自定义手机号验证规则:

  1. 注解的定义
  2. 验证规则(约束验证器)
  3. 注解使用

1.注解的定义:

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
// 与约束注解关联的验证器
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
    // 当这个注解验证错误,返回给用户的信息
    String message() default "手机号校验错误";

    // 约束注解在验证时所属的组别
    Class<?>[] groups() default {};

    // 约束注解的有效负载/严重程度
    Class<? extends Payload>[] payload() default {};
}

2.验证规则(约束验证器)

// 约束注解对应的验证器
public class PhoneValidator
        implements ConstraintValidator<Phone, String>{

    // 自定义校验逻辑方法
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {

        // 对value进行空值处理: 如果为null,就替换成"""
        String phone = Optional.ofNullable(value).orElse("");

        // 手机号验证规则:158后面随便
        String check = "158\\d{8}";
        Pattern regex = Pattern.compile(check);
        Matcher matcher = regex.matcher(phone);

        return matcher.matches();
    }
}

3.注解使用

@Data           
public class User{
     // 使用刚刚定义的注解
    @Phone(message = "手机号要以158开头")
    private String phone;
}

总结

注解在框架中十分常见, 如果结合AOP一起使用可以完成很多有意思的功能。如:注解+AOP实现防止接口重复提交的情况、注解+AOP实现统计接口耗时、注解+AOP进行全局日志处理等。

学会如何使用注解,并尝试自己去写一个注解并使用起来。