Java编译时注解自动生成代码
原文:http://blog.csdn.net/robertcpp/article/details/51628656
经过测试即便生成文件也该是运行时才能生成。
在开始之前,我们首先申明一个非常重要的问题:我们并不讨论那些在运行时(Runtime)通过反射机制运行处理的注解,而是讨论在编译时(Compile time)处理的注解。注解处理器是一个在javac中的,用来编译时扫描和处理的注解的工具。可以为特定的注解,注册自己的注解处理器。
一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。可以生成Java代码,这些生成的Java代码是在生成的.java文件中,所以不能修改已经存在的Java类,例如向已有的类中添加方法。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被javac编译。
虚处理器AbstractProcessor
我们首先看一下处理器的API。每一个处理器都是继承于AbstractProcessor,如下所示:
- public class MyProcessor extends AbstractProcessor {
- @Override
- public synchronized void init(ProcessingEnvironment env){ }
- @Override
- public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
- @Override
- public Set<String> getSupportedAnnotationTypes() { }
- @Override
- public SourceVersion getSupportedSourceVersion() { }
- }
- init(ProcessingEnvironment env): 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements,Types和Filer。
- process(Set<? extends TypeElement> annotations, RoundEnvironment env): 这相当于每个处理器的主函数main()。 在这里写扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让查询出包含特定注解的被注解元素。
- getSupportedAnnotationTypes(): 这里必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,在这里定义你的注解处理器注册到哪些注解上。
- getSupportedSourceVersion(): 用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。然而,如果有足够的理由只支持Java 6的话,也可以返回SourceVersion.RELEASE_6。推荐使用前者。
举一个简单例子
自动生成一个bean的结构文件
- 把
- public class Student {
- public String stu_name;
- public String stu_id;
- public int stu_age;
- }
- 转换为
- {class:"com.robert.processor.Student",
- fields:
- {
- stu_name:"java.lang.String",
- stu_id:"java.lang.String",
- stu_age:"java.lang.Integer"
- }
- }
首先声明注解
- package com.robert.processor;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- @Target({ ElementType.FIELD, ElementType.TYPE })
- @Retention(RetentionPolicy.CLASS)
- public @interface Serialize {
- }
将注解加到Student类上
@Serialize
public class Student
定义自己的解析器
- package com.robert.processor;
- import java.io.File;
- import java.io.FileWriter;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import javax.annotation.processing.AbstractProcessor;
- import javax.annotation.processing.ProcessingEnvironment;
- import javax.annotation.processing.RoundEnvironment;
- import javax.lang.model.element.Element;
- import javax.lang.model.element.ElementKind;
- import javax.lang.model.element.TypeElement;
- import javax.lang.model.element.VariableElement;
- import javax.lang.model.util.ElementFilter;
- import javax.lang.model.util.Elements;
- public class MyProcessor extends AbstractProcessor {
- // 元素操作的辅助类
- Elements elementUtils;
- @Override
- public synchronized void init(ProcessingEnvironment processingEnv) {
- super.init(processingEnv);
- elementUtils = processingEnv.getElementUtils();
- }
- @Override
- public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
- // 获得被该注解声明的元素
- Set<? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Serialize.class);
- TypeElement classElement = null;// 声明类元素
- List<VariableElement> fields = null;// 声明一个存放成员变量的列表
- // 存放二者
- Map<String, List<VariableElement>> maps = new HashMap<String, List<VariableElement>>();
- // 遍历
- for (Element ele : elememts) {
- // 判断该元素是否为类
- if (ele.getKind() == ElementKind.CLASS) {
- classElement = (TypeElement) ele;
- maps.put(classElement.getQualifiedName().toString(), fields = new ArrayList<VariableElement>());
- } else if (ele.getKind() == ElementKind.FIELD) // 判断该元素是否为成员变量
- {
- VariableElement varELe = (VariableElement) ele;
- // 获取该元素封装类型
- TypeElement enclosingElement = (TypeElement) varELe.getEnclosingElement();
- // 拿到key
- String key = enclosingElement.getQualifiedName().toString();
- fields = maps.get(key);
- if (fields == null) {
- maps.put(key, fields = new ArrayList<VariableElement>());
- }
- fields.add(varELe);
- }
- }
- for (String key : maps.keySet()) {
- if (maps.get(key).size() == 0) {
- TypeElement typeElement = elementUtils.getTypeElement(key);
- List<? extends Element> allMembers = elementUtils.getAllMembers(typeElement);
- if (allMembers.size() > 0) {
- maps.get(key).addAll(ElementFilter.fieldsIn(allMembers));
- }
- }
- }
- generateFile(maps);
- return true;
- }
- private void generateFile(Map<String, List<VariableElement>> maps) {
- File dir = new File(MyProcessor.class.getResource("/").getPath());
- if (!dir.exists())
- dir.mkdirs();
- // 遍历map
- for (String key : maps.keySet()) {
- // 创建文件
- File file = new File(dir, key.replaceAll("\\.", "_") + ".txt");
- try {
- /**
- * 编写文件内容
- */
- FileWriter fw = new FileWriter(file);
- fw.append("{").append("class:").append("\"" + key + "\"").append(",\n ");
- fw.append("fields:\n {\n");
- List<VariableElement> fields = maps.get(key);
- for (int i = 0; i < fields.size(); i++) {
- VariableElement field = fields.get(i);
- fw.append(" ").append(field.getSimpleName()).append(":")
- .append("\"" + field.asType().toString() + "\"");
- if (i < fields.size() - 1) {
- fw.append(",");
- fw.append("\n");
- }
- }
- fw.append("\n }\n");
- fw.append("}");
- fw.flush();
- fw.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- @Override
- public Set<String> getSupportedAnnotationTypes() {
- Set<String> set = super.getSupportedAnnotationTypes();
- if (set == null) {
- set = new HashSet<>();
- }
- set.add("com.robert.processor.Serialize");
- return set;
- }
- }
我们经常使用的ButterKnife这个框架就很好的使用了AbstractProcessor
Butter Knife 是 Android 视图字段和方法绑定,使用注解处理来生成样板代码。后面做详细说明。
posted on 2017-07-14 17:21 Silentdoer 阅读(1304) 评论(0) 编辑 收藏 举报