zoukankan      html  css  js  c++  java
  • Java注解的使用

    Java注解的使用

    参考

    廖雪峰java教程

    使用注解

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

    注解的作用

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

    Java的注解可以分为三类:

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

    • @Override:让编译器检查该方法是否正确地实现了覆写;
    • @SuppressWarnings:告诉编译器忽略此处代码产生的警告。

    这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。

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

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

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

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

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

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

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

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

    小结

    注解(Annotation)是Java语言用于工具处理的标注:

    注解可以配置参数,没有指定配置的参数使用默认值;

    如果参数名称是value,且只有一个参数,那么可以省略参数名称。

    定义注解

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

    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Logger {
    
        String value() default "";
    
        String level() default "error";
    
        int type() default 0;
    }
    

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

    元注解

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

    @Target

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

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

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

    @Retention

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

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

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

    @Repeatable

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

    @Repeatable(Loggers.class)
    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Logger {
    
        String value() default "";
    
        String level() default "error";
    
        int type() default 0;
    }
    
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE,ElementType.METHOD})
    public @interface Loggers {
        Logger[] value();
    }
    

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

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

    @Inherited——注解继承,作用于类上

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

    @Inherited
    @Repeatable(Loggers.class)
    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Logger {
    
        String value() default "";
    
        String level() default "error";
    
        int type() default 0;
    }
    

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

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

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

    public class Student extends Person {
    }
    

    如何定义Annotation

    我们总结一下定义Annotation的步骤:

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

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

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

    小结

    Java使用@interface定义注解:

    可定义多个参数和默认值,核心参数使用value名称;

    必须设置@Target来指定Annotation可以应用的范围;

    应当设置@Retention(RetentionPolicy.RUNTIME)便于运行期读取该Annotation。

    处理注解

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

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

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

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

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

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

    判断某个注解是否存在于Class、Field、Method或Constructor:

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

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

    示例:

    public class AnnotationTest {
    
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException {
            UserController userController = new UserController();
            boolean present = userController.getClass().isAnnotationPresent(Logger.class);
            if (present) {
                Logger logger = userController.getClass().getAnnotation(Logger.class);
                System.out.println(" logger value: " + logger.value());
                System.out.println(" logger level: " + logger.level());
            }
    
            Method loadUsers = UserController.class.getMethod("loadUsers");
            Logger loadUsersAnnotation = loadUsers.getAnnotation(Logger.class);
            if (loadUsersAnnotation != null) {
                System.out.println("loadUsers logger value: " + loadUsersAnnotation.value());
                System.out.println("loadUsers logger level: " + loadUsersAnnotation.level());
            }
    
            Method getUser = UserController.class.getMethod("getUser", String.class);
            Annotation[][] annotations = getUser.getParameterAnnotations();
            for (Annotation[] annotationArr : annotations) {
                for (Annotation annotation : annotationArr) {
                    if (annotation instanceof NotEmpty) {
                        NotEmpty r = (NotEmpty) annotation;
                        System.out.println("NotEmpty");
                    }
                    if (annotation instanceof RequestParam) {
                        RequestParam r = (RequestParam) annotation;
                        System.out.println("RequestParam");
                    }
                }
            }
    
            User user = new User();
            //user.setName("牛掰");
            user.setName("");
            boolean checkEmpty = AnnotationTest.checkEmpty(user);
            if (checkEmpty) {
                System.out.println("一切正常!");
            }
    
        }
    
        public static boolean checkEmpty(User user) throws IllegalAccessException {
            Field[] declaredFields = user.getClass().getDeclaredFields();
            if (declaredFields != null) {
                // 遍历所有Field:
                for (Field field : declaredFields) {
                    field.setAccessible(true);
                    // 获取Field定义的@Range:
                    NotEmpty range = field.getAnnotation(NotEmpty.class);
                    // 如果@Range存在:
                    if (range != null) {
                        // 获取Field的值:
                        Object value = field.get(user);
                        // 如果值是String:
                        if (value == null) {
                            if (value instanceof String) {
                                String s = (String) value;
                                if (StringUtils.isEmpty(s)) {
                                    throw new IllegalArgumentException("Invalid field: " + field.getName());
                                }
                            }
                        }else {
                            throw new IllegalArgumentException("Invalid field: " + field.getName());
                        }
                    }
                }
            }
            return true;
        }
    }
    

    输出:

     logger value: UserControllerLog
     logger level: debug
    loadUsers logger value: UserControllerLog.loadUsers
    loadUsers logger level: info
    NotEmpty
    RequestParam
    Exception in thread "main" java.lang.IllegalArgumentException: Invalid field: name
    	at com.self.annotation.AnnotationTest.checkEmpty(AnnotationTest.java:89)
    	at com.self.annotation.AnnotationTest.main(AnnotationTest.java:61)
  • 相关阅读:
    Effective Java 19 Use interfaces only to define types
    Effective Java 18 Prefer interfaces to abstract classes
    Effective Java 17 Design and document for inheritance or else prohibit it
    Effective Java 16 Favor composition over inheritance
    Effective Java 15 Minimize mutability
    Effective Java 14 In public classes, use accessor methods, not public fields
    Effective Java 13 Minimize the accessibility of classes and members
    Effective Java 12 Consider implementing Comparable
    sencha touch SortableList 的使用
    sencha touch dataview 中添加 button 等复杂布局并添加监听事件
  • 原文地址:https://www.cnblogs.com/castamere/p/13578868.html
Copyright © 2011-2022 走看看