关于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 个毛病
三、总结:
源码级注解的运用非常普遍,比方:举行代码搜检、生成新类、生成文件。本文完成了基础的代码搜检,用于搜检类中的元素是不是依据请求举行首字母大写或许小写。也可以依据需要,考证是不是悉数大写,或许悉数小写。愿望人人经由过程本案例可以相识源码级注解的编写及运用。