学习总结录 学习总结录
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
  • Java基础

    • Java-枚举
    • Java-反射
    • Java-泛型
    • Java-异常
    • Java-注解
      • 注解简介
        • 注解的形式
        • 什么是注解
        • 注解的作用
        • 注解的代价
      • 内置注解
        • @Override
        • @Deprecated
        • @SuppressWarnnings
        • @SafeVarargs
        • @FunctionalInterface
      • 元注解
        • @Retention
        • @Documented
        • @Target
        • @Inherited
        • @Repeatable
      • 自定义注解
        • 注解的定义
        • 注解属性
        • 注解处理器
        • 使用注解
      • 参考
  • Java集合

  • MySQL

  • Redis

  • JVM

  • 多线程

  • 计算机网络

  • Spring

  • Kafka

  • Elasticsearch

  • Python

  • 面试专题

  • 知识库
  • Java基础
旭日
2023-03-31
目录

Java-注解

# 注解简介

image-20221208105541709

# 注解的形式

Java 中,注解是以 @ 字符开始的修饰符。如下:

@Override
void mySuperMethod() { ... }

注解可以包含命名或未命名的属性,并且这些属性有值。

@Author(
   name = "Benjamin Franklin",
   date = "3/27/2003"
)
class MyClass() { ... }

# 什么是注解

从本质上来说,注解是一种标签,其实质上可以视为一种特殊的注释,如果没有解析它的代码,它并不比普通注释强。

解析一个注解有两种形式:

  • 编译期直接的扫描 - 编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。这种情况只适用于 JDK 内置的注解类。
  • 运行期的反射 - 如果要自定义注解,Java 编译器无法识别并处理这个注解,它只能根据该注解的作用范围来选择是否编译进字节码文件。如果要处理注解,必须利用反射技术,识别该注解以及它所携带的信息,然后做相应的处理。

# 注解的作用

  • 编译器信息 - 编译器可以使用注解来检测错误或抑制警告。
  • 编译时和部署时的处理 - 程序可以处理注解信息以生成代码,XML 文件等。
  • 运行时处理 - 可以在运行时检查某些注解并处理。

# 注解的代价

  • 显然,它是一种侵入式编程,那么,自然就存在着增加程序耦合度的问题。
  • 自定义注解的处理需要在运行时,通过反射技术来获取属性。如果注解所修饰的元素是类的非 public 成员,也可以通过反射获取。这就违背了面向对象的封装性。
  • 注解所产生的问题,相对而言,更难以 debug 或定位。

# 内置注解

JDK 中内置了以下注解:

  • @Override
  • @Deprecated
  • @SuppressWarnnings
  • @SafeVarargs(JDK7 引入)
  • @FunctionalInterface(JDK8 引入)

# @Override

@Override 用于表明被修饰方法覆写了父类的方法。

public class OverrideAnnotationDemo {

    static class Person {
        public String getName() {
            return "getName";
        }
    }


    static class Man extends Person {
        @Override
        public String getName() {
            return "override getName";
        }

        /**
         *  放开下面的注释,编译时会告警
         */
       /*
        @Override
        public String getName2() {
            return "override getName2";
        }
        */
    }

    public static void main(String[] args) {
        Person per = new Man();
        System.out.println(per.getName());
    }
}

# @Deprecated

@Deprecated 用于标明被修饰的类或类成员、类方法已经废弃、过时,不建议使用。

# @SuppressWarnnings

@SuppressWarnnings用于关闭对类、方法、成员编译时产生的特定警告。

# @SafeVarargs

@SafeVarargs 的作用是:告诉编译器,在可变长参数中的泛型是类型安全的。可变长参数是使用数组存储的,而数组和泛型不能很好的混合使用。

@SafeVarargs 注解使用范围:

  • @SafeVarargs 注解可以用于构造方法。
  • @SafeVarargs 注解可以用于 static 或 final 方法。
public class SafeVarargsAnnotationDemo {
    /**
     * 此方法实际上并不安全,不使用此注解,编译时会告警
     */
    @SafeVarargs
    static void wrongMethod(List<String>... stringLists) {
        Object[] array = stringLists;
        List<Integer> tmpList = Arrays.asList(42);
        array[0] = tmpList; // 语法错误,但是编译不告警
        String s = stringLists[0].get(0); // 运行时报 ClassCastException
    }

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");

        List<String> list2 = new ArrayList<>();
        list.add("1");
        list.add("2");

        wrongMethod(list, list2);
    }
}

# @FunctionalInterface

@FunctionalInterface用于指示被修饰的接口是函数式接口。

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。

# 元注解

image-20221208113527624

Java 中提供了以下元注解类型:

  • @Retention
  • @Target
  • @Documented
  • @Inherited(JDK8 引入)
  • @Repeatable(JDK8 引入)

# @Retention

@Retention指明了注解的保留级别,源码如下:

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

RetentionPolicy 是一个枚举类型,它定义了被 @Retention 修饰的注解所支持的保留级别:

  • RetentionPolicy.SOURCE - 标记的注解仅在源文件中有效,编译器会忽略。
  • RetentionPolicy.CLASS - 标记的注解在 class 文件中有效,JVM 会忽略。
  • RetentionPolicy.RUNTIME - 标记的注解在运行时有效。

@Retention 示例:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    public String name() default "fieldName";
    public String setFuncName() default "setField";
    public String getFuncName() default "getField";
    public boolean defaultDBValue() default false;
}

# @Documented

@Documented表示无论何时使用指定的注解,都应使用 Javadoc(默认情况下,注释不包含在 Javadoc 中)。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
    public String name() default "fieldName";
    public String setFuncName() default "setField";
    public String getFuncName() default "getField";
    public boolean defaultDBValue() default false;
}

# @Target

@Target指定注解可以修饰的元素类型,源码如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

ElementType 是一个枚举类型,它定义了被 @Target 修饰的注解可以应用的范围:

  • ElementType.ANNOTATION_TYPE - 标记的注解可以应用于注解类型。
  • ElementType.CONSTRUCTOR - 标记的注解可以应用于构造函数。
  • ElementType.FIELD - 标记的注解可以应用于字段或属性。
  • ElementType.LOCAL_VARIABLE - 标记的注解可以应用于局部变量。
  • ElementType.METHOD - 标记的注解可以应用于方法。
  • ElementType.PACKAGE - 标记的注解可以应用于包声明。
  • ElementType.PARAMETER - 标记的注解可以应用于方法的参数。
  • ElementType.TYPE - 标记的注解可以应用于类的任何元素。
@Target(ElementType.TYPE)
public @interface Table {
    /**
     * 数据表名称注解,默认值为类名称
     * @return
     */
    public String tableName() default "className";
}

@Target(ElementType.FIELD)
public @interface NoDBColumn {}

# @Inherited

@Inherited表示注解类型可以被继承(默认情况下不是这样)。表示自动继承注解类型。 如果注解类型声明中存在 @Inherited 元注解,则注解所修饰类的所有子类都将会继承此注解。

@Inherited
public @interface Greeting {
    public enum FontColor{ BULE,RED,GREEN};
    String name();
    FontColor fontColor() default FontColor.GREEN;
}

# @Repeatable

@Repeatable表示注解可以重复使用

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Schedules {
	Scheduled[] value();
}

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
  // ...
}
public class TaskRunner {

    @Scheduled("0 0/15 * * * ?")
    @Scheduled("0 0 12 * ?")
    public void task1() {}
}

# 自定义注解

使用 @interface 自定义注解时,自动继承了 java.lang.annotation.Annotation 接口,由编译程序自动完成其他细节。

# 注解的定义

注解的语法格式如下:

public @interface 注解名 {定义体}
@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValid {}
  • @Documented 表示 @RegexValid 应该使用 javadoc。
  • @Target({ElementType.FIELD, ElementType.PARAMETER}) 表示 @RegexValid 可以在类成员或方法参数上修饰。
  • @Retention(RetentionPolicy.RUNTIME) 表示 @RegexValid 在运行时有效。

# 注解属性

注解属性的语法形式如下:

访问级别修饰符] [数据类型] 名称() default 默认值;

例如,我们要定义在注解中定义一个名为 value 的字符串属性,其默认值为空字符串,访问级别为默认级别,那么应该定义如下:

String value() default "";

在注解中,我们定义属性时,属性名后面需要加 ()。

@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValid {
    enum Policy {
        // @formatter:off
        EMPTY(null),
        DATE("^(?:(?!0000)[0-9]{4}([-/.]?)(?:(?:0?[1-9]|1[0-2])\\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\\1"
            + "(?:29|30)|(?:0?[13578]|1[02])\\1(?:31))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|"
            + "(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2\\2(?:29))$"),
        MAIL("^[A-Za-z0-9](([_\\.\\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\\.\\-]?[a-zA-Z0-9]+)*)\\.([A-Za-z]{2,})$");
        // @formatter:on

        private String policy;

        Policy(String policy) {
            this.policy = policy;
        }

        public String getPolicy() {
            return policy;
        }
    }

    String value() default "";
    Policy policy() default Policy.EMPTY;
}

在上面的示例代码中,我们定义了两个注解属性:String 类型的 value 属性和 Policy 枚举类型的 policy 属性。Policy 枚举中定义了几个默认的正则表达式,这是为了直接使用这几个常用表达式去正则校验。

# 注解处理器

java.lang.annotation.Annotation 是一个接口,程序可以通过反射来获取指定程序元素的注解对象,然后通过注解对象来获取注解里面的元数据。

public interface Annotation {
    boolean equals(Object obj);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}

除此之外,Java 中支持注解处理器接口 java.lang.reflect.AnnotatedElement ,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:

  • Class - 类定义
  • Constructor - 构造器定义
  • Field - 累的成员变量定义
  • Method - 类的方法定义
  • Package - 类的包定义

AnnotatedElement 接口是所有程序元素(Class、Method 和 Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement 对象之后,程序就可以调用该对象的如下四个个方法来访问注解信息:

  • getAnnotation - 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回 null。
  • getAnnotations - 返回该程序元素上存在的所有注解。
  • isAnnotationPresent - 判断该程序元素上是否包含指定类型的注解,存在则返回 true,否则返回 false。
  • getDeclaredAnnotations - 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
import java.lang.reflect.Field;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexValidUtil {
    public static boolean check(Object obj) throws Exception {
        boolean result = true;
        StringBuilder sb = new StringBuilder();
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 判断成员是否被 @RegexValid 注解所修饰
            if (field.isAnnotationPresent(RegexValid.class)) {
                RegexValid valid = field.getAnnotation(RegexValid.class);

                // 如果 value 为空字符串,说明没有注入自定义正则表达式,改用 policy 属性
                String value = valid.value();
                if ("".equals(value)) {
                    RegexValid.Policy policy = valid.policy();
                    value = policy.getPolicy();
                }

                // 通过设置 setAccessible(true) 来访问私有成员
                field.setAccessible(true);
                Object fieldObj = null;
                try {
                    fieldObj = field.get(obj);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                if (fieldObj == null) {
                    sb.append("\n")
                        .append(String.format("%s 类中的 %s 字段不能为空!", obj.getClass().getName(), field.getName()));
                    result = false;
                } else {
                    if (fieldObj instanceof String) {
                        String text = (String) fieldObj;
                        Pattern p = Pattern.compile(value);
                        Matcher m = p.matcher(text);
                        result = m.matches();
                        if (!result) {
                            sb.append("\n").append(String.format("%s 不是合法的 %s !", text, field.getName()));
                        }
                    } else {
                        sb.append("\n").append(
                            String.format("%s 类中的 %s 字段不是字符串类型,不能使用此注解校验!", obj.getClass().getName(), field.getName()));
                        result = false;
                    }
                }
            }
        }

        if (sb.length() > 0) {
            throw new Exception(sb.toString());
        }
        return result;
    }
}
  1. 通过 getDeclaredFields 反射方法获取传入对象的所有成员。
  2. 遍历成员,使用 isAnnotationPresent 判断成员是否被指定注解所修饰,如果不是,直接跳过。
  3. 如果成员被注解所修饰,通过 RegexValid valid = field.getAnnotation(RegexValid.class); 这样的形式获取,注解实例化对象,然后,就可以使用 valid.value() 或 valid.policy() 这样的形式获取注解中设定的属性值。
  4. 根据属性值,进行逻辑处理。

# 使用注解

public class RegexValidDemo {
    static class User {
        private String name;
        @RegexValid(policy = RegexValid.Policy.DATE)
        private String date;
        @RegexValid(policy = RegexValid.Policy.MAIL)
        private String mail;
        @RegexValid("^((\\+)?86\\s*)?((13[0-9])|(15([0-3]|[5-9]))|(18[0,2,5-9]))\\d{8}$")
        private String phone;

        public User(String name, String date, String mail, String phone) {
            this.name = name;
            this.date = date;
            this.mail = mail;
            this.phone = phone;
        }

        @Override
        public String toString() {
            return "User{" + "name='" + name + '\'' + ", date='" + date + '\'' + ", mail='" + mail + '\'' + ", phone='"
                + phone + '\'' + '}';
        }
    }

    static void printDate(@RegexValid(policy = RegexValid.Policy.DATE) String date){
        System.out.println(date);
    }

    public static void main(String[] args) throws Exception {
        User user = new User("Tom", "1990-01-31", "xxx@163.com", "18612341234");
        User user2 = new User("Jack", "2019-02-29", "sadhgs", "183xxxxxxxx");
        if (RegexValidUtil.check(user)) {
            System.out.println(user + "正则校验通过");
        }
        if (RegexValidUtil.check(user2)) {
            System.out.println(user2 + "正则校验通过");
        }
    }
}

# 参考

深入理解Java注解 (opens new window)

#Java
上次更新: 2024/06/29, 15:13:44
Java-异常
Java集合-ArrayList

← Java-异常 Java集合-ArrayList→

最近更新
01
基础概念
10-31
02
Pytorch
10-30
03
Numpy
10-30
更多文章>
Theme by Vdoing | Copyright © 2021-2024 旭日 | 蜀ICP备2021000788号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式