1. 程式人生 > >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編譯註解自動生成程式碼

在開始之前,我們首先申明一個非常重要的問題:我們並不討論那些在執行時(Runtime)通過反射機制執行處理的註解,而是討論在編譯時(Compile time)處理的註解。註解處理器是一個在javac中的,用來編譯時掃描和處理的註解的工具。可以為特定的註解,註冊自己的註解處

Java編譯註解應用-生成格式化原始檔

引言 有許多開源框架在編譯時通過註解資訊生成新的原始檔,已到達簡化樣板程式碼的書寫,比如說典型的Builder模式,或者實現框架的功能的橋接程式碼。因為我使用編譯時註解只是想要簡化樣板程式碼,下面我就以Builder模式作為示例。 Builder模式

Java語言使用註解處理器生成程式碼 —— 第一部分:註解型別

原文作者:deors 原文地址:https://deors.wordpress.com/2011/09/26/annotation-types/ 譯文作者:Jianan - [email protected] 版本資訊:本文基於

關於java編譯註解你需要知道的二三事。解除你的顧慮!

做Android開發。大家肯定會關心你的app的效能問題。不知道從何時開始。網上有流傳一句。不要使用註解。用註解會影響效能。這不能說錯。但是也不能說對。這裡普及一下關於註解的一些你需要知道的知識 網上常說的註解。基本是執行時註解。而所說的註解會影響效能。則

kotlin結合dagger2使用為什麼在編譯無法自動生成DaggerxxxComponent類

算是一個小坑,卡了我大半天的時間解決方法很簡單,只要將gradle裡面依賴的apt改成kapt就行了,比如dagger2裡面的dagger-compiler,databinding裡面的compiler

通過編譯註解生成程式碼實現自己的ButterKnife

背景概述 註解的處理除了可以在執行時通過反射機制處理外,還可以在編譯期進行處理。 Java5中提供了apt工具來進行編譯期的註解處理。apt是命令列工具,與之配套的是一套描述“程式在編譯時刻的靜態結構”的API:Mirror API(com.sun.mirr

利用編譯註解生成Java原始碼

我們在編寫註解的時候,需要指定@Retention,有三個可選值,表示註解會被保留到那個階段。 RetentionPolicy.SOURCE       這種型別的Annotations只在原始碼級別保留,編譯時就會被忽略,因此一般用來為編譯器提供額外資訊,以便於檢測錯誤,

java註解編譯註解RetentionPolicy.CLASS 基本用法

1 前言 我們知道,在日常開發中我們常用的兩種註解是執行時註解和編譯時註解,執行時註解是通過反射來實現註解處理器的,對效能稍微有一點損耗,而編譯時註解是在程式編譯期間生成相應的代理類,替我們完成某些功能。今天我們來講解一下編譯時註解以及寫一個小例子,以便加深對編譯時註解的理解。

mybatis generator自動生成程式碼生成了insert 而沒有其他的

mybatis框架提供了非常好用的逆向工程外掛,但是在使用過程中會有很多問題。 我在使用中就遇到了只生成insert和insertSeletive方法,而不生成其他根據primary key查詢更新刪除的方法。 解決方案: 1.檢查資料庫中的表是否有主鍵,如果沒有主鍵是不會生成類似selectByPri

VS:編譯dll自動生成版本

步驟 1. 在解決方案中--》現有項xxx.rc檔案--》雙擊xxx.rc檔案開啟資源檢視--》選中xxx.rc有右鍵“新增資源”--》選擇Version                 

SSM+Maven整合在Eclipse中使用Mybatis逆向工程自動生成程式碼

場景 MybatisGenerator 官方文件 http://www.mybatis.org/generator/configreference/xmlconfig.html 實現 專案搭建好完整的包,包括bean、dao、service、test、utils、mapper

Mybatis generator 自動生成程式碼 ,insert返回主鍵值

mvn mybatis-generator:generate 如果要讓generator自動新增該功能,可以如下配置: <!-- tableName:用於自動生成程式碼的資料庫表;domainObjectName:對應於資料庫表的javaBean類名;不需要生成Ex

使用java語言中的註解生成器生成程式碼

Code Generation using Annotation Processors in the Java language – part 1: Annotation Types 註解型別 這篇帖子我會開始關於使用java語言中註解處理器來程式碼生

Mybatis generator 自動生成程式碼 ,insert如何返回主鍵值

mvn mybatis-generator:generate 如果要讓generator自動新增該功能,可以如下配置: <!-- tableName:用於自動生成程式碼的資料庫表;domainObjectName:對應於資料庫表的javaBean類名;不需要生成Ex

mybatis使用generator自動生成程式碼的型別轉換

使用mybatis的generator自動生成程式碼,但是oracle資料庫中number(6,2)總是自動轉成BigDecimal,我想要轉成的是float型別 這樣就寫了一個型別轉換器,需要繼承JavaTypeResolver介面 然後在mybaties配置檔案gene

android apt編譯時期自動生成程式碼

最近新了一個架構,之前用dagger2時候,每當新增新activity還要修改或者新建component來完成dagger的注入。用了apt以後,在activity上標註一個註解就可以了。 本文章用最簡單的方法最直白的話 來搭建一個簡單的apt編譯時期生成程式

mybatis generator自動生成程式碼將blob屬性變成String型別,當出現亂碼的處理方法

1.blob屬性變成String型別,需要在配置檔案中的table元素中增加columnOverride元素,詳情如下例子: <table tableName="paas_conf_metadata"> <generatedKey column="ID"

eclipse、myeclipse寫類自動生成註釋

圖片 version inf pre nbsp tags bubuko 內容 types 在類的上邊/**+enter自動生成註釋。   設置方法:Window--Prefences--Java--Code Style--Code Templates--Comments-

pycharm中每次創建py文件自動生成代碼頭,以及出現SyntaxError:Non-ASCII 。。。問題

char ror utf 生成 CI 出現 每次 class font 我們在pycharm中執行py文件的時候,可能會出現以下錯誤 這是因為你沒有制定編碼格式,這時候你需要在文件最開始制定編碼格式,代碼如下 #!/user/bin/env python

mybatis-generator-maven-plugin外掛自動生成程式碼的配置方法

1. 第一步,在pom檔案中引入如下外掛   //專案示例程式碼: <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-g