zoukankan      html  css  js  c++  java
  • 关于JAVA中源码级注解的编写及使用

    一、注解简介:

    1.1.什么是“注解”:

    ​ 在我们编写代码时,一定看到过这样的代码:

    class Student {
        private String name;
    
        @Override
        public String toString(String str) {//编译错误!
            return "Student name = " + name;
        }
    }
    

    ​ 其中的@Override,就是一个“注解”,@Override一般出现在重写equals()或者toString()方法的上边,意思是告诉编译器:下边的代码是重写父类方法的。这时编译器会按照“重写”的语法严格检查下面的方法,如果不符合重写语法,将会编译错误。

    ​ "注解"作为一种“标记”,被写在源码中,不会改变程序的执行流程。它通常由“注解解析工具”来解析,而“注解解析器”可以随Java编译器启动,也可以独立启动,来解析注解,并以此可以做一些事情。

    1.2.注解的分类:

    源码注解:

    ​ 注解只在源码中,编译成class文件后就不存在了。

    编译时注解:

    ​ 注解在源码和.class文件中都存在(如:JDK内置系统注解)

    运行时注解:

    ​ 在运行阶段还起作用,甚至会影响运行逻辑的注解(如:JUnit的@Test)

    1.3.注解的作用

    ​ 注解的作用非常广泛,注解可以被用在类、属性、构造方法、成员方法、局部变量等位置,用于对这些元素进行说明。由“注解解析工具”解析后,可以生成文档、进行代码分析、编译检查等。
    ​ 本例将会实现一个用作"编译检查“的注解,以及一个"注解解析器"。"注解解析器"将会随着javac编译器一同启动来对使用了注解的类进行编译,并检查类名、字段名、方法名是否以大写、小写字符开头,如果违反了规则,编译时将会报错。

    二、自定义注解:

    2.1.定义注解的基本语法

    ​ “注解”本质上是一个“类”,我们可以根据自己的需要定义自己的注解。
    ​ 定义注解的语法很简单:

    public @interface CheckWord{
    	...
    }
    

    ​ "注解”编译后会生成.class文件。但这是一个非常简单的注解,它可以被用在任何位置,而且编译器遇到这种注解也不做任何事情。例如:

    @CheckWord
    public class Student {
        @CheckWord
        public Student() {
        }
    
        @CheckWord
        private String name;
    
        @CheckWord
        public void study() {
        }
    }
    

    下面我们先使用“元注解”来规定这个注解可以被用在哪里。

    2.2.元注解

    ​ “元注解”也是一种“注解”,它是已经实现好的。必须用在“注解”的定义上,它可以规定注解可以用在哪里,以及可以存在于源码中,或者class中,或者运行时。
    常用的“元注解”有两个:
    ​ 1).@Target : 规定注解可以用在哪里。常用的取值被定义在枚举java.lang.annotation.ElementType中:
    ​ ElementType.TYPE:类和接口上
    ​ ElementType.FIELD: 用在成员变量上
    ​ ElementType.METHOD: 用在方法上
    ​ ElementType.PARAMETER: 用在参数上
    ​ ElementType.CONSTRUCTOR: 用在构造方法上
    ​ ElementType.LOCAL_VARIABLE: 用在局部变量上
    ​ 2).@Retention : 规定注解可以存在于哪里。常用的取值被定义在枚举java.lang.annotation.RetentionPolicy中:
    ​ RetentionPolicy.SOURCE: 规定注解只存在于Java源代码中, 编译生成的字节码文件中就不存在了。
    ​ RetentionPolicy.CLASS: 规定注解存在于Java源代码、 编译以后的字节码文件中, 但JVM运行时,不会被加载到内存。
    ​ RetentionPolicy.RUNTIME: 规定注解存在于Java源代码中、 编译以后的字节码文件中、 运行时内存中, 程序可以通过反射获取该注解。
    ​ 例如:修改我们的注解,规定它只能用在"类","字段",“方法”上,并且可以存在于“源码中”:

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface CheckWord {
    
    }
    

    ​ 如果再编译之前的Student类,会发现用在"构造方法"上的@CheckWord会编译错误,因为我们规定了它只能用在"类","字段","方法"上。

    2.3.定义注解的属性:

    1.“注解”中可以定义一些属性,“注解解析器”可以根据“属性”的不同,分别做不同的事情。

    ​ 例如@Target注解中的ElementType.TYPE就是此注解的一个属性,它是一个"枚举"类型。
    ​ 下面让我们来看看怎样定义属性,然后再解析这些属性。
    ​ 注解中定义属性的语法:数据类型 属性名() [deafult 值];
    ​ 1.其中“数据类型”可以是:
    ​ 1).所有基本类型;
    ​ 2).String;
    ​ 3).Class;
    ​ 4).枚举;
    ​ 5).注解;
    ​ 6).以上任一类型的数组
    ​ 2.属性名():属性名可以自由设定,要遵循Java标识符的命名规则;其中的一对()是必须的。
    ​ 3.[default 值]:为此属性设置的默认值。
    2.本例中由于只检查大小写,为了规范取值,所以定义一个"枚举"类型的属性。
    ​ 1).先定义枚举:

    public enum StartsWith {
        UPPERCASE, LOWERCASE
    }
    

    ​ 此枚举定义了两个值:UPPERCASE表示:大写;LOWERCASE表示:小写。
    ​ 2).修改"CheckWord"注解的代码:

    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface CheckWord {
        StartsWith value();
    }
    

    ​ 说明:
    ​ a.StartsWith表示"数据类型",是一个"枚举"类型。
    ​ b.value表示"属性名",在使用此注解时,此属性的可取值只有StartsWith.UPPERCASE和StartsWith.LOWERCASE两个。
    ​ c.此属性没有设置"默认值",在使用此注解时必须要设置此属性的值。如下面的代码:
    ​ 3).修改"Student"类的代码:

    @CheckWord(StartsWith.UPPERCASE)
    public class Student {
        @CheckWord(StartsWith.LOWERCASE)
        private String stuName;
        @CheckWord(StartsWith.LOWERCASE)
        public void show() {
        }
    }
    

    2.4注解解析器:

    ​ 1."注解解析器"通常是随着注解一起定义的,用于解析"注解",并做一些事情。本例的"注解解析器"用于与javac编译器一起启动,编译Student类时,检查各元素的名称是否按要求以指定的大写、小写字母开头。
    ​ 2.自定义"注解解析器"需要继承AbstractProcessor类,并重写process()方法,完整的"注解解析器"代码如下:

    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.RoundEnvironment;
    import javax.annotation.processing.SupportedAnnotationTypes;
    import javax.annotation.processing.SupportedSourceVersion;
    import javax.lang.model.SourceVersion;
    import javax.lang.model.element.Element;
    import javax.lang.model.element.TypeElement;
    import javax.tools.Diagnostic;
    import java.util.Set;
    
    @SupportedAnnotationTypes("CheckWord")
    @SupportedSourceVersion(SourceVersion.RELEASE_8)
    public class MyProcessor extends AbstractProcessor {
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            //获取所有使用了@CheckWord注解的元素
            Set<? extends Element> annoEle = roundEnv.getElementsAnnotatedWith(CheckWord.class);
            // 遍历这些元素
            for (Element e : annoEle) {
                //获取元素名称,可能是:类名、属性名、方法名
                String name = e.getSimpleName().toString();
                //获取这个名字的第一个字母
                char c = name.charAt(0);
                //获取这个元素上的@CheckWord注解对象
                CheckWord anno = e.getAnnotation(CheckWord.class);
                //获取这个注解的value属性的值,它是一个StartsWith枚举类型
                StartsWith sw = anno.value();
                //判断属性值是否设置为:StartsWith.UPPERCASE,但名字的首字母是小写
                if (sw == StartsWith.UPPERCASE && Character.isLowerCase(c)) {
                    //向控制台打印异常信息
                    this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名称:" + name + " 首字母应该大写!");
                    return false;
                }
                //判断属性值是否设置为:StartsWith.LOWERCASE,但名字的首字母是大写
                if (sw == StartsWith.LOWERCASE && Character.isUpperCase(c)) {
                    this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名称:" + name + " 首字母应该小写!");
                    return false;
                }
    
            }
            return true;
        }
    }
    

    ​ 此代码的细节大家可以根据注释一点一点研究。一些类:TypeElement,RoundEnvironment,Element等的一些方法大家可以在API手册中查找。
    ​ 其它说明:
    ​ @SupportedAnnotationTypes("CheckWord") : 表示只处理CheckWord注解。
    ​ @SupportedSourceVersion(SourceVersion.RELEASE_8) : 表示支持JDK1.8。

    2.5.编译和测试:

    1.在编译前,我们看一下完整的代码清单:请确保以下的四个类在同一个目录下
    ​ 1).枚举类:

    public enum StartsWith {
        UPPERCASE, LOWERCASE
    }			
    

    ​ 2).自定义注解类:

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface CheckWord {
        StartsWith value();
    }
    

    ​ 3).使用了CheckWord注解的Student类:

    @CheckWord(StartsWith.UPPERCASE)
    public class Student {
        @CheckWord(StartsWith.LOWERCASE)
        private String StuName;
    
        @CheckWord(StartsWith.LOWERCASE)
        public void show() {
    
        }
    }
    

    ​ 4).注解解析器类:

    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.RoundEnvironment;
    import javax.annotation.processing.SupportedAnnotationTypes;
    import javax.annotation.processing.SupportedSourceVersion;
    import javax.lang.model.SourceVersion;
    import javax.lang.model.element.Element;
    import javax.lang.model.element.ElementKind;
    import javax.lang.model.element.TypeElement;
    import javax.tools.Diagnostic;
    import java.util.Set;
    
    @SupportedAnnotationTypes("CheckWord")
    @SupportedSourceVersion(SourceVersion.RELEASE_8)
    public class MyProcessor extends AbstractProcessor {
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            Set<? extends Element> annoEle = roundEnv.getElementsAnnotatedWith(CheckWord.class);
            for (Element e : annoEle) {
                String name = e.getSimpleName().toString();
                char c = name.charAt(0);
    
                CheckWord anno = e.getAnnotation(CheckWord.class);
                StartsWith sw = anno.value();
                if (sw == StartsWith.UPPERCASE) {
                    if (Character.isLowerCase(c)) {
                        this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名称:" + name + " 首字母应该大写!");
                        return false;
                    }
                }
                if (sw == StartsWith.LOWERCASE) {
                    if (Character.isUpperCase(c)) {
                        this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名称:" + name + " 首字母应该小写!");
                        return false;
                    }
                }
            }
            return true;
        }
    }
    

    2.启动命令行,使用javac依次进行编译:
    ​ javac StartsWith.java
    ​ javac CheckWord.java
    ​ javac MyProcessor.java(如果报错: 编码GBK的不可映射字符,是因为代码中的中文,可以使用javac -encoding UTF-8 MyProcessor.java进行编译)
    ​ 接下来使用MyProcessor解析器编译Student:
    ​ javac -processor MyProcessor Student.java
    ​ 执行命令后,会有错误提示:
    ​ 错误: 名称:StuName 首字母应该小写!
    ​ 1 个错误

    三、总结:

    ​ 源码级注解的应用非常广泛,例如:进行代码检查、生成新类、生成文件。本文实现了基本的代码检查,用于检查类中的元素是否按照要求进行首字母大写或者小写。也可以根据需要,验证是否全部大写,或者全部小写。希望大家通过本案例能够了解源码级注解的编写及使用。

  • 相关阅读:
    STL——(4)deque
    Windows Server服务器日常管理技巧
    各种服务器伪静态配置方法
    DedeCMS V5.7开启memcache缓存的方法配置说明
    windows服务器下iis的性能优化 服务器
    网站出现service unavailable的解决方法
    【ThinkPHP框架学习 】(2) --- 后台管理系统如何用iframe点击左边右边局部刷新
    【ThinkPHP框架学习 】(1) --- thinkphp 3.2.3 验证码验证使用教程分享
    走着走着,已经到了玩不起的年龄 —— 致自己
    PHP常用数组(Array)函数整理
  • 原文地址:https://www.cnblogs.com/hanstrovsky/p/12327460.html
Copyright © 2011-2022 走看看