在咱们编写代码时,必定看到过这样的代码:java
class Student { private String name; @Override public String toString(String str) {//编译错误! return "Student name = " + name; } }
其中的@Override,就是一个“注解”,@Override通常出如今重写equals()或者toString()方法的上边,意思是告诉编译器:下边的代码是重写父类方法的。这时编译器会按照“重写”的语法严格检查下面的方法,若是不符合重写语法,将会编译错误。数组
"注解"做为一种“标记”,被写在源码中,不会改变程序的执行流程。它一般由“注解解析工具”来解析,而“注解解析器”能够随Java编译器启动,也能够独立启动,来解析注解,并以此能够作一些事情。ide
注解只在源码中,编译成class文件后就不存在了。工具
注解在源码和.class文件中都存在(如:JDK内置系统注解)测试
在运行阶段还起做用,甚至会影响运行逻辑的注解(如:JUnit的@Test)this
注解的做用很是普遍,注解能够被用在类、属性、构造方法、成员方法、局部变量等位置,用于对这些元素进行说明。由“注解解析工具”解析后,能够生成文档、进行代码分析、编译检查等。
本例将会实现一个用做"编译检查“的注解,以及一个"注解解析器"。"注解解析器"将会随着javac编译器一同启动来对使用了注解的类进行编译,并检查类名、字段名、方法名是否以大写、小写字符开头,若是违反了规则,编译时将会报错。编码
“注解”本质上是一个“类”,咱们能够根据本身的须要定义本身的注解。
定义注解的语法很简单:命令行
public @interface CheckWord{ ... }
"注解”编译后会生成.class文件。但这是一个很是简单的注解,它能够被用在任何位置,并且编译器遇到这种注解也不作任何事情。例如:code
@CheckWord public class Student { @CheckWord public Student() { } @CheckWord private String name; @CheckWord public void study() { } }
下面咱们先使用“元注解”来规定这个注解能够被用在哪里。对象
“元注解”也是一种“注解”,它是已经实现好的。必须用在“注解”的定义上,它能够规定注解能够用在哪里,以及能够存在于源码中,或者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会编译错误,由于咱们规定了它只能用在"类","字段","方法"上。
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() { } }
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。
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 个错误
源码级注解的应用很是普遍,例如:进行代码检查、生成新类、生成文件。本文实现了基本的代码检查,用于检查类中的元素是否按照要求进行首字母大写或者小写。也能够根据须要,验证是否所有大写,或者所有小写。但愿你们经过本案例可以了解源码级注解的编写及使用。