美文网首页
编写java注解和注解处理器

编写java注解和注解处理器

作者: 程序员吉森 | 来源:发表于2019-12-20 16:48 被阅读0次

    注解是java语言中一种重要的特性。本文介绍如何编写注解、如果使用注解以及如何编写注解处理器。

    注解在java开发中一直具有举足轻重的地位。在Spring Boot大行其道的今天,注解更显重要,框架提供的各种注解让我们开发更加方便。注解就像生活中的标签纸一样,可以为我们标注的类、方法、属性等补充额外的信息,也可以用于声明类、方法、属性的额外特性。除了使用框架提供的注解,有时候我们也可以编写自定义注解。

    如何编写注解

    首先,让我们以spring提供的RestController注解为例,看一下注解是由哪些部分组成的。

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Controller
    @ResponseBody
    public @interface RestController {
    
        @AliasFor(annotation = Controller.class)
        String value() default "";
    
    }
    

    注解的声明

    在java中,注解的声明与类的声明类似,除了使用@interface关键字代替类的class关键字,注意不要与接口的声明(interface,不带@)混淆。

    注解的属性

    注解中可以包含多个属性。与类的属性不同,注解中的属性需要在属性名后加一对小括号。这有点像无参数的方法声明,事实上在注解处理器中调用注解的属性时也与调用无参方法相同,这一点我们在下文中将进一步介绍。

    注解的属性可以使用default关键字赋默认值。注意如果一个属性没有默认值,那么在使用注解时必须为这个属性赋值。

    注解的元信息

    注解需要使用在类、方法还是属性上?注解是在源文件中生效,还是在class文件中生效,还是可以在运行时调用?这些都属于注解的元信息。注解通过一系列元注解(就是标注在注解上的注解)来说明这些元信息。常用的元注解包括@Target、@Retention、@Documented、@Inherited以及@Repeatable(jdk8之后新增)。

    @Target通过指定一个ElementType枚举类型的数组来表明注解的作用范围。常用的范围包括TYPE(类和注解)、FIELD(属性)、METHOD(方法)、PARAMETER(方法的参数)、CONSTRUCTOR(构造器)和LOCAL_VARIABLE(局部变量 )等 。

    @Retention通过指定一个RetentionPolicy类型的枚举值来表明注解的保留策略,它有三个枚举值:SOURCE(源代码)、CLASS(class文件)和RUNTIME(运行时)。SOURCE表明注解只在源代码中保留,在编译时会被编译器丢弃。CLASS表明注解可以被编译到class文件中,但不会被加载到jvm中。RUNTIME表明注解可以被加载到jvm中,供运行时使用。

    @Documented表明该注解(指被@Documented标注的注解)会进入javadoc文档中,即被该注解标注的类、方法、属性等的javadoc中会显示出这个注解的情况。

    @Inherited表明该注解(指被@Inherited标注的注解)标注的类、方法、属性的会传递到它的子类中。但是该注解不会在子类的javadoc文档中显示。

    @Repeatable表明该注解(指被@Repeatable标注的注解)可以被重复使用,@Repeatable注解内要传入能容纳当前注解的容器类,例如

    @Repeatable(RestControllers.class)
    
    // RestControllers为能容纳@RestController注解的容器
    public @interface RestControllers{
             RestController[] value();
    }
    

    通常情况下,我们应该为注解添加@Target、@Retention和@Documented元注解。

    如何使用注解

    我们可以在@Retention元注解定义的注解作用范围内使用注解。例如,如果注解被 @Retention(RetentionType.TYPE)标注,那么该注解可以在类上使用。这里我们还是以@RestController注解为例来说明如何使用注解:

    @RestController(value = "userController")
    public class UserController{}
    

    我们可以通过属性名=属性值的方式为注解的属性进行赋值,没有默认值的属性必须要赋值。需要注意,如果要为名为value的注解属性赋值,且不需要为其他属性赋值时,value可以省略。即上面的代码可以简化为:

    @RestController("userController")
    public class UserController{}
    

    如果我们要为数组类型的属性赋值,而且要赋的值为单元素的数组,那么数组的大括号可以省略,如:

    @Target(ElementType.TYPE)
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Test{
            String[] value();
    }
    
    // 完整的写法
    @Test(value={"abc"})
    public class MyTest{}
    
    // 上面的代码可以简化为这种形式
    @Test("abc")
    public class MyTest{}
    

    如何编写注解处理器

    注解本身的作用有限,通常需要配合独立的处理代码来使用。注解的处理代码可以存在于单独的类中,也可以存在于其他类的某段逻辑代码当中。注解处理器通常通过java的反射机制来实现。这里以将实体类对象的集合导出为EXCEL为例:

    首先我们先编写一个@Excel注解:

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Excel {
          // 字段在EXCEL列中的名称
        String value() default "";
          // 字段值的映射,如性别映射可以写为"0-女,1-男"
        String map() default "";
    
    }
    

    假设有一个名为Person的实体类如下:

    public class Person {
        @Excel("id")
        private Integer id;
        /**
         * 姓名
         */
        @Excel("姓名")
        private String name;
        /**
         * 年龄
         */
        @Excel("年龄")
        private Integer age;
        /**
         * 性别
         */
        @Excel(value="性别", map="0-女,1-男")
        private Integer gender;
    }
    

    我们用@Excel注解标注了各个属性的中文名称,对于性别属性,我们还给出了相应的映射值。我们希望模拟Excel输出,将对象输出为以下格式:

    id  姓名  年龄  性别
    1   张三    25      男
    2   李四    30      女
    

    下面我们来编写处理类:

    public class ExcelHandler {
    
        // 缓存字段的映射
        Map<String, Map<String, String>> cache = new HashMap<>();
    
        public <T> void handle(List<T> list, Class<T> clazz) throws IllegalAccessException {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                // 遍历字段,如果字段上存在注解,就进行相应处理
                if (field.isAnnotationPresent(Excel.class)) {
                    // 获取注解对象
                    Excel annotation = field.getAnnotation(Excel.class);
                    // 打印出对象的value属性,即中文名称
                    System.out.print(annotation.value() + " ");
                    // 如果注解有map属性,将map属性的字符串解析为HashMap,存到缓存中,供下面解析字段值使用
                    if (!"".equals(annotation.map())){
                        cache.put(field.getName(), convertToMap(annotation.map()));
                    }
                }
            }
            System.out.println();
            for (T t : list) {
                for (Field field : fields) {
                    // 设置可以获取对象中属性的值
                    field.setAccessible(true);
                    Object value = field.get(t);
                    if (field.isAnnotationPresent(Excel.class)) {
                        Excel annotation = field.getAnnotation(Excel.class);
                        String map = annotation.map();
                        if ("".equals(map)) {
                            System.out.print(value);
                        } else {
                            System.out.print(cache.get(field.getName()).get(String.valueOf(value)));
                        }
                    } else {
                        System.out.print(value);
                    }
                    System.out.print(" ");
                }
                System.out.println();
            }
        }
    
        /**
         * 将映射属性转化为HashMap
         * @param fieldMap 注解中的映射属性,如"0-女,1-男"
         * @return 转化为HashMap
         */
        private Map<String, String> convertToMap(String fieldMap) {
            Map<String, String> map = new HashMap<>();
            String[] arr = fieldMap.split(",");
            for (String s : arr) {
                String[] arr1 = s.split("-");
                map.put(arr1[0], arr1[1]);
            }
            return map;
        }
    
        public static void main(String[] args) throws IllegalAccessException {
            Person zhangsan = new Person();
            zhangsan.setGender(1);
            zhangsan.setId(1);
            zhangsan.setAge(25);
            zhangsan.setName("张三");
    
            Person lisi = new Person();
            lisi.setGender(1);
            lisi.setId(1);
            lisi.setAge(25);
            lisi.setName("张三");
    
            List<Person> personList = new ArrayList<>();
            personList.add(zhangsan);
            personList.add(lisi);
    
            new ExcelHandler().handle(personList, Person.class);
    
        }
    }
    

    这里主要的思路就是通过反射机制,根据class对象动态获取类的属性,通过属性可以获取到属性上的注解及其属性,然后我们可以对获取到的注解及属性进行相应的处理。

    相关文章

      网友评论

          本文标题:编写java注解和注解处理器

          本文链接:https://www.haomeiwen.com/subject/isybnctx.html