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

Java编译时注解自动生成代码[通俗易懂]在开始之前,我们首先申明一个非常重要的问题:我们并不讨论那些在运行时(Runtime)通过反射机制运行处理的注解,而是讨论在编译时(Compiletime)处理的注解。注解处理器是一个在javac中的,用来编译时扫描和处理的注解的工具。可以为特定的注解,注册自己的注解处理器。一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。可以生成Java代码,这些生成的Java代码是在生成的.java文件中,所以不能修改已经存在的Java类,例如向已有的类中

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

在开始之前,我们首先申明一个非常重要的问题:我们并不讨论那些在运行时(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 视图字段和方法绑定,使用注解处理来生成样板代码。后面做详细说明。


欢迎扫描二维码,关注个人公众号

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


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 八大排序算法

    八大排序算法概述排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。我们这里说说八大排序就是内部排序。当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分…

    2022年4月26日
    35
  • 菜单权限表sql语句「建议收藏」

    菜单权限表sql语句「建议收藏」selectm.*     fromt_menum     wherem.state=’1′      and((menu_type=1andexists        (select1          fromv_user_menub          wherem.menu_id…

    2025年6月13日
    0
  • ajax 跨域请求api_java跨域请求的三种方法

    ajax 跨域请求api_java跨域请求的三种方法让ajax请求访问servlet,可以让ajax请求跨域,通过直接本应用中的资源servlet,ajax可以不跨域访问本应用中的servlet,让java程序发送Get,Post请求,来访问2号服务器中的的servlet。ProxyServlet发送get请求,访问b站点TargerServlet,请求响应回来数据在进行响应。首先把架包导入创建lib目录,复制进去选中右键Addaslibrary。(2)ajax跨域解决方案之代理机制的代码实现。引入架包httpclient架包。…

    2022年8月24日
    4
  • cuda安装步骤_cuda和cudnn是什么

    cuda安装步骤_cuda和cudnn是什么cuda9.0+cudnn7.0安装教程 1、下载cuda9.0下载链接:https://developer.nvidia.com/cuda-toolkit-archive 2、安装cuda安装cuda时,第一次会让设置临时解压目录,第二次会让设置安装目录;临时解压路径,建议默认即可,也可以自定义。安装结束后,临时解压文件夹会自动删除;安装目录,建议默认…

    2022年4月19日
    95
  • 出现UnboundLocalError: local variable ‘a’ referenced before assignment异常的情况与解决方法

    出现UnboundLocalError: local variable ‘a’ referenced before assignment异常的情况与解决方法出现UnboundLocalError:localvariable‘a’referencedbeforeassignment异常的情况与解决方法字面意思:局部变量赋值前被引用原因:局部变量与全局变量同名例:a=1deffunc():a+=1print(a)func()解决方法:1.使局部变量与全局变量不同名a=1deffunc…

    2022年6月15日
    155
  • BIOS + EC

    BIOS + ECBIOS+EC1参考1参考1、BIOS与EC之间关系2、BIOS和EC3、蓝天DOS下刷BIOS、EC小教程(以775TM为例)

    2022年7月20日
    22

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号