Annotation(注解)是 Java 提供的一种对元程序中元素关联信息和元数据(metadata)的途径和方法。Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation对象,然后通过该 Annotation 对象来获取注解中的元数据信息。注解发生在编译阶段,它是把parse和enter阶段生成的AST语法树,经过AbstractProcessor类处理生成修改过的语法树,再交给下游进行处理。Lombook就是用这种方式实现的,注解暂时不支持继承。
一、简单实现
/***定义注解*/
(ElementType.FIELD)
(RetentionPolicy.RUNTIME)
public @interface FruitProvider {
/**
* 供应商编号
*/
public int id() default -1;
/*** 供应商名称*/
public String name() default "";
/**
* 供应商地址
*/
public String address() default "";
}
/*注解使用*/
public class Apple {
(id = 1, name = "陕西红富士集团", address = "陕西省西安市延安路")
private String appleProvider;
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}
}
/*注解处理器*/
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz) {
String strFruitProvicer = "供应商信息:";
Field[] fields = clazz.getDeclaredFields();//通过反射获取处理注解
for (Field field : fields) {
if (field.isAnnotationPresent(FruitProvider.class)) {
FruitProvider fruitProvider = (FruitProvider) field.getAnnotation(FruitProvider.class); //注解信息的处理地方
strFruitProvicer = " 供应商编号:" + fruitProvider.id() + " 供应商名称:"
+ fruitProvider.name() + " 供应商地址:" + fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
public static void main(String []args){
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
二、JDK内置的支持
2.1、内置的三种标准注解
- @SuppressWarnings("unchecked"):unchecked执行了未检查的转换时的警告,例如当使用集合时没有用泛型来指定集合保存的类型;Unused没有被访问过的元素,去除警告;Fallthrough当Switch程序块直接通往下一种情况二没有Break时的警告;Path在类路径,源文件路径等中有不存在的路径时的警告;Serial当在可序列化的类上缺少serialVersionUID定义时的警告;Finally任何finally子句不能正常完成时的警告;All关于上边所有情况的警告:
- @Override:当前的方法覆盖超类中的方法;
- @Deprecated:当前的方法已过时,编译器会发出警告;
- @SafeVarargs:参数安全,阻止编译过程中发现警告
2.2、四种元数据
- @Targer:表示此注解可以用在什么地方,用ElementType.做为前缀
- CONSTRUCTOR构造器声明
- FIELD域声明,
- LOCAL_VARIABLE局部变量声明
- METHOD方法声明
- PACKAGE包声明
- PARAMETER参数声明
- TYPE类、接口或enum声明
- ANNOTATION注解
- @Retention:表示需要在什么级别保存该注解信息,用RetentionPolicy.做为前缀
- SOURCE:注解只在源码阶段生效,会在编译成class中不显示
- CLASS:注解在class文件中可用,但不会加载到VM中
- RUNTIME:VM将在运行期也保留注解,因此可以通过反射机制读取注解的信息
- @Documented:将此注解包含在javaDoc中
- @Inherited:允许子类继承父类中的注解如果一个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该class 的子类。
- @Repeatable:1.8的新特性,同一注意会以数组形式运行,类似过滤器链
2.3、属性
在注解中只可定义属性不可定义方法,且属性为基础值,比如public @interface Trace:
- int id() default 1:在类上可以用@Trace(1)或@Trace(id=1)
- 如果无参数:在类上可以用@Trace
三、高级使用
3.1、Spring扫描实现
注解一般都需要一个解析器,上面是一种实现,这也是一种实现,一般需要借助spring等启动类来达到运行的目的;
ElementType.METHOD)(
(RetentionPolicy.RUNTIME)
public @interface UseCase {
public int id() default -1;//由于default不能为null,所以一般用””或负数来表示特殊值
public String description() default "no description";
} ///:~
public class UseCaseTracker {
public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
for(Method m : cl.getDeclaredMethods()) {
UseCase uc = m.getAnnotation(UseCase.class);//返回指定类型的注解对象
if(uc != null) {
System.out.println("FoundCase:" + uc.id() + " " + uc.description());
useCases.remove(new Integer(uc.id()));
}
}
for(int i : useCases) {
System.out.println("Warning: Missing use case-" + i);
}
}
public static void main(String[] args) {
List<Integer> useCases = new ArrayList<Integer>();
Collections.addAll(useCases, 47, 48, 49, 50);
trackUseCases(useCases, PasswordUtils.class);
}
}
3.2、编译时自动启动
其原理就是JVM在编译过程中会扫描所有.jar包META-INF/services/javax.annotation.processiong.Processor文件,在这个文件中指定注解类就可以了。以lombook为例。其文件结构如下:
gradle:
incremental.annotation.processors
MANIFEST.MF
services:
javax.annotation.processing.Processor
lombok.core.LombokApp
lombok.core.PostCompilerTransformation
lombok.core.runtimeDependencies.RuntimeDependencyInfo
lombok.eclipse.EclipseAnnotationHandler
lombok.eclipse.EclipseASTVisitor
lombok.eclipse.handlers.EclipseSingularsRecipes$EclipseSingularizer
lombok.installer.IdeLocationProvider
lombok.javac.handlers.JavacSingularsRecipes$JavacSingularizer
lombok.javac.JavacAnnotationHandler
lombok.javac.JavacASTVisitor
org.mapstruct.ap.spi.AstModifyingAnnotationProcessor
javax.annotation.processing.Processor 内容如下,指定了注解类,其核心源码如下:lombok.launch.AnnotationProcessorHider$AnnotationProcessor。
//创建一个类加载器,它会加载.SCL.lombok文件(其实就是.class文件),存放在lombok.javac路径下。这个类加载器的作用是为了避免污染项目的命名空间
private static AbstractProcessor createWrappedInstance() {
ClassLoader cl = Main.getShadowClassLoader();
try {
Class<?> mc = cl.loadClass("lombok.core.AnnotationProcessor");
return (AbstractProcessor)mc.getDeclaredConstructor().newInstance();
} catch (Throwable var2) {
if (var2 instanceof Error) {
throw (Error)var2;
} else if (var2 instanceof RuntimeException) {
throw (RuntimeException)var2;
} else {
throw new RuntimeException(var2);
}
}
}
3.3、改写AST语法树
执行avac -processor com.jsr269.DataAnnoProcessor Test.java,注意这需要用-processor指定要使用的的注解,或在maven-compiler-plugin中配置compilerArgument参数指定值为-proc:none。
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>
ElementType.TYPE)(
(RetentionPolicy.SOURCE)
public @interface Data {
}
"com.Data") //注解类的全限定名(
(SourceVersion.RELEASE_8)//最高支持1.8的JDK编译出来的类
public class DataAnnoProcessor extends AbstractProcessor {
/*语法树元素的基类*/
private JavacTrees javacTrees;
/*创建方法树的方法*/
private TreeMaker treeMaker;
/*提供了访问标识符的方法,比如this或super这样的引用*/
private Names names;
/*完成初始化工作*/
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Context context = (Context) ((JavacProcessingEnvironment)processingEnv).getContext();
javacTrees = JavacTrees.instance(processingEnv);
treeMaker = TreeMaker.instance(context);
names = Names.instance(context);
}
/*做AST语法树的修改*/
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//取得所有被Data注释的类的集合
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Data.class);
for(Element element : set){
//得到当前类的AST树
JCTree tree = javacTrees.getTree(element);
//这个相当于事件,当遍历AST过程会触发相应的方法
tree.accept(new TreeTranslator(){
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
//
jcClassDecl.defs = jcClassDecl.defs.prepend(genMethod());
super.visitClassDef(jcClassDecl);
}
});
}
return false;
}
//生成新方法
private JCTree.JCMethodDecl genMethod(){
return null;
}
}