Java编译时注解自动生成代码[通俗易懂]

2022-09-01 15:30:43 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

在开始之前,我们首先申明一个非常重要的问题:我们并不讨论那些在运行时(Runtime)通过反射机制运行处理的注解,而是讨论在编译时(Compile time)处理的注解。注解处理器是一个在javac中的,用来编译时扫描和处理的注解的工具。可以为特定的注解,注册自己的注解处理器。

一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。可以生成Java代码,这些生成的Java代码是在生成的.java文件中,所以不能修改已经存在的Java类,例如向已有的类中添加方法。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被javac编译。

虚处理器AbstractProcessor

我们首先看一下处理器的API。每一个处理器都是继承于AbstractProcessor,如下所示:

代码语言:javascript复制
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的结构文件

代码语言:javascript复制
把
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"
 }  
} 

首先声明注解

代码语言:javascript复制
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

定义自己的解析器

代码语言:javascript复制
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 视图字段和方法绑定,使用注解处理来生成样板代码。后面做详细说明。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/140841.html原文链接:https://javaforall.cn

如果您是在找激活码,但输入激活码后激活失败,最新激活码地址:https://javaforall.cn/127239.html

0 人点赞