java注解之注解处理器
摘要
Java5中提供了apt工具来进行编译期的注解处理。apt是命令行工具,与之配套的是一套描述“程序在编译时刻的静态结构”的API:Mirror API(com.sun.mirror.*)。通过Mirror API可以获取到被注解的Java类型元素的信息,从而提供自定义的处理逻辑。具体的处理工具交给apt来处理。编写注解处理器的核心是两个类:注解处理器(com.sun.mirror.apt.AnnotationProcessor)、注解处理器工厂(com.sun.mirror.apt.AnnotationProcessorFactory)。apt工具在完成注解处理后,会自动调用javac来编译处理完成后的源代码。然而,apt工具是oracle提供的私有实现(在JDK开发包的类库中是不存在的)。
在JDK6中,将注解处理器这一功能进行了规范化,形成了java.annotation.processing的API包,Mirror API则进行封装,形成javax.lang.model包。注解处理器的开发进行了简化,不再单独使用apt工具,而将此功能集成到了javac命令中。
在JSK1.8之后移除了APT,使用插入式注解处理API(JSR 269)提供一套标准API来处理Annotations(JSR 175),实际上JSR 269不仅仅用来处理Annotation,我觉得更强大的功能是它建立了Java 语言本身的一个模型,它把method, package, constructor, type, variable, enum, annotation等Java语言元素映射为Types和Elements(两者有什么区别?), 从而将Java语言的语义映射成为对象, 我们可以在javax.lang.model包下面可以看到这些类. 所以我们可以利用JSR 269提供的API来构建一个功能丰富的元编程(metaprogramming)环境. JSR 269用Annotation Processor在编译期间而不是运行期间处理Annotation, Annotation Processor相当于编译器的一个插件,所以称为插入式注解处理.如果Annotation Processor处理Annotation时(执行process方法)产生了新的Java代码,编译器会再调用一次Annotation Processor,如果第二次处理还有新代码产生,就会接着调用Annotation Processor,直到没有新代码产生为止.每执行一次process()方法被称为一个”round”,这样整个Annotation
processing过程可以看作是一个round的序列.
注解处理器可以在注解生命周期全程使用
定义一个注解(maven工程)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)//CLASS,RUNTIME都可以public @interface CreateFile {
}
使用此注解
@CreateFile
public class main{
}创建注解处理器
@SupportedAnnotationTypes(value = “com.ycj.customannotation.annotation.CreateFile”)
@SupportedSourceVersion(value = SourceVersion.RELEASE_11)
public class MyProcessor extends AbstractProcessor {private Types types;
private Elements elements;
private Filer filer;
private Messager messager;
/**
* 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,
* init()会被注解处理工具调用,以processingEnv作为参数
* ProcessingEnvironment提供了一些实用得工具
* Types
* Elements
* Filer
* Messager
* @param processingEnv 提供给processor 用来访问工具框架得环境
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
types=processingEnv.getTypeUtils();
elements= processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
/**
* 在这个方法里面你必须指定那些注解应该被注解处理器注册。注意,它的返回值是一个String集合,
* 包含了你的注解处理器想要处理的注解类型的全称。
* @return
*/
/* @Override
public Set<String> getSupportedAnnotationTypes() {
LinkedHashSet<String> annotations = new LinkedHashSet<String>();
//annotations.add("com.ycj.customannotation.annotation.CreateFile");
annotations.add(CreateFile.class.getCanonicalName());//等同上面
return annotations;
}
*//**
* 指定注解可以使用的版本
* @return
*//*
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}*/
/*
注意:可以使用注解@SupportedSourceVersion(value=SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({
// Set of full qullified annotation type names
“com.ycj.jcf.collection.annotation.CreateFile”
})
来代替getSupportedAnnotationTypes()和getSupportedSourceVersion()
*/
/**
* 注解处理器扫描,解析,处理注解的核心方法,你可以在这个方法里面编写实现扫描,处理注解,生成java文件。
* @param annotations 请求处理的注解类型
* @param roundEnv 你可以查询被特定注解标注的元素
* @return 如果返回false,则这些注解未声明并且可能要求后续Processor处理它们。
* 如果返回true,则这些注解已声明并且不要求后续Processor处理它们。
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(Diagnostic.Kind.NOTE,"日志开始-----------------------");
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(CreateFile.class);
for (Element element : elementsAnnotatedWith) {
if(element.getKind()== ElementKind.CLASS){
TypeElement typeElement= (TypeElement) element;
PackageElement packageElement = elements.getPackageOf(element);
String packagePath = packageElement.getQualifiedName().toString();
String className =typeElement.getSimpleName().toString();
try {
JavaFileObject sourceFile = filer.createSourceFile(packagePath + "." + className + "_ViewBinding", typeElement);
Writer writer = sourceFile.openWriter();
writer.write("package "+packagePath+";\n");
writer.write("import "+packagePath+"."+className+";\n");
writer.write("public class "+className+"_ViewBinding"+"{\n");
writer.write("\n");
writer.append(" public "+className+" targe;\n");
writer.write("\n");
writer.append("}");
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
messager.printMessage(Diagnostic.Kind.NOTE,"日志结束-----------------------");
return true;
}
}
- 注册注解处理器
在javax.annotation.processing.Processor中填入注解处理器的全限定名com.ycj.customannotation.processor.MyProcessor
这个文件中一行代表一个注解处理器 - maven插件配置
因为注解处理器必须先编译了才能使用,但是直接编译整个工程会默认启动注解处理器,这时就会报错,所以在编译时设置不执行注解处理器 - idea配置
- 编译工程注解处理器启动
注解处理器编译完成之后 就会启动 ,生成的文件在generated下
如果对上一部不很理解可以先不编译main类,也就是不先创建,在编译完整个工程后再创建main类使用@CreateFile ,在单独编译recompile
同样可以看到生成的文件
日志信息在这里
扩展
可以看到上面注册注解处理器的方式比较繁琐,
Google开发了一个AutoService,用来生成 META-INF/services/javax.annotation.processing.Processor 文件的,你只需要在你定义的注解处理器上添加 @AutoService(Processor.class) 就可以了.@AutoService(Processor.class)
@SupportedAnnotationTypes(value = “com.ycj.customannotation.annotation.CreateFile”)
@SupportedSourceVersion(value = SourceVersion.RELEASE_11)
public class MyProcessor extends AbstractProcessor {}
不过我并没有成功,可能需要在android studio上进行,毕竟这个谷歌开发给安卓用的,那位大神在idea上成功了,请留言一下,十分感谢!
处理器的属性方法介绍
Messager:主要起一个日志的作用 ,为注解处理器提供了一种报告错误消息,警告信息和其他消息的方式。它不是注解处理器开发者的日志工具,是用来给那些使用了你的注解处理器的第三方开发者显示信息的。Kind.ERROR,就是错误信息的级别。
- Elements:一个用来处理Elemnet的工具类
Element代表程序中的元素,比如包、类、属性、方法。 - Types:一个用来处理TypeMirror的工具类
- Filer:可以用来创建文件。
还没有评论,来说两句吧...