舉例:創建自定義navigation注解處理器,分為兩大部分:
一、創建自定義的注解,定義注解類;
二、創建自定義的注解決議器,定義決議規則;
下面,我們來創建自己的注解,在專案根目錄上右鍵新建New->Moudule->Java Library,填寫自定義注解庫的名字libnavannotation,如下:

同樣需要繼續創建注解決議器:在專案根目錄上右鍵新建New->Moudule->Java Library,填寫自定義注解決議器庫的名字libnavcompiler:
通過上述的創建,我們專案的目錄中會多出如下的目錄:

將libnavannotation庫和libnavcompiler庫目錄中的build.gradle中的源兼容性和目標兼容性由版本7改為版本8:
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
改為:
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

我們來添加依賴,只在自定義注解決議器libnavcompiler庫 中添加注解相關的依賴,完整build.gradle檔案內容如下:
plugins {
id 'java-library'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':libnavannotation')
implementation 'com.alibaba:fastjson:1.2.75'
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
}
下面來正式的創建兩個注解類:ActivityDestination注解類和FragmentDestination注解類:

FragmentDestination注解類:
package com.mooc.libnavannotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
public @interface FragmentDestination {
/**當前頁面url**/
String pageUrl();
/**當前頁面是否需要登錄**/
boolean needLogin() default false;
/**當前頁面是否作為默認起始頁面**/
boolean asStarter() default false;
}
ActivityDestination注解類:
package com.mooc.libnavannotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
public @interface ActivityDestination {
String pageUrl();
boolean needLogin() default false;
boolean asStarter() default false;
}
注解處理器Processor基礎概念
在開始之前,我們首先申明一個非常重要的問題:你應該明確注解的區別:
①在運行時(Runtime)通過反射機制運行處理的注解;
②在編譯時(Compile time)處理的注解;
注解處理器(Annotation Processor)是javac的一個工具,它用來在編譯時掃描和處理注解(Annotation),你可以對自定義注解,并注冊相應的注解處理器,到這里,我假設你已經知道什么是注解,并且知道怎么宣告的一個注解(我的第一篇文章已經說明了),如果你不熟悉注解,你可以在這官方檔案中得到更多資訊,注解處理器在Java 5開始就有了,但是從Java 6(2006年12月發布)開始才有可用的API,
一個注解的注解處理器,以Java代碼或者編譯過的位元組碼作為輸入,生成檔案通常是.java檔案作為輸出,這具體的含義什么呢?你可以生成Java代碼!這些生成的Java代碼是在生成的.java檔案中,所以你不能修改已經存在的Java類,例如向已有的類中添加方法,這些生成的Java檔案,會同其他普通的手動撰寫的Java源代碼一樣被javac編譯,
虛處理器AbstractProcessor
我們首先看一下處理器的API,每一個處理器都是繼承于AbstractProcessor,它的原始碼如下所示:
package javax.annotation.processing;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
import java.util.Objects;
import javax.lang.model.element.*;
import javax.lang.model.SourceVersion;
import javax.tools.Diagnostic;
/**
* 一種抽象注釋處理器,設計成大多數具體注釋處理器的方便超類,該類檢查注釋值以計算其子型別支持的選項、注釋型別和源版本,
* 在處理器初始化后,getter方法可能會使用可用的工具發出關于值得注意的條件的警告,
* 只要遵守該方法的通用Processor契約,子類可以自由重寫該類中任何方法的實作和規范,
*
**/
public abstract class AbstractProcessor implements Processor {
/**
* Processing environment providing by the tool framework.
* 由工具框架提供的處理環境
*/
protected ProcessingEnvironment processingEnv;
private boolean initialized = false;
/**
* Constructor for subclasses to call.
*/
protected AbstractProcessor() {}
/**
* If the processor class is annotated with {@link
* SupportedOptions}, return an unmodifiable set with the same set
* of strings as the annotation. If the class is not so
* annotated, an empty set is returned.
*
* @return the options recognized by this processor, or an empty
* set if none
* 如果處理器類使用SupportedOptions注釋,則回傳一個不可修改的集合,該集合具有與注釋相同的字串集,如果類沒有這樣注釋,則回傳一個空集,
* 回傳:此處理器識別的選項,如果沒有,則為空集
*/
public Set<String> getSupportedOptions() {
SupportedOptions so = this.getClass().getAnnotation(SupportedOptions.class);
if (so == null)
return Collections.emptySet();
else
return arrayToSet(so.value());
}
/**
* If the processor class is annotated with {@link
* SupportedAnnotationTypes}, return an unmodifiable set with the
* same set of strings as the annotation. If the class is not so
* annotated, an empty set is returned.
*
* @return the names of the annotation types supported by this
* processor, or an empty set if none
*
* 如果處理器類使用SupportedAnnotationTypes注釋,則回傳一個不可修改的集合,該集合具有與注釋相同的字串集,如果類沒有這樣注釋,則回傳一個空集,
* 回傳:此處理器支持的注釋型別的名稱,如果沒有,則回傳一個空集
*/
public Set<String> getSupportedAnnotationTypes() {
SupportedAnnotationTypes sat = this.getClass().getAnnotation(SupportedAnnotationTypes.class);
if (sat == null) {
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedAnnotationTypes annotation " +
"found on " + this.getClass().getName() +
", returning an empty set.");
return Collections.emptySet();
}
else
return arrayToSet(sat.value());
}
/**
* If the processor class is annotated with {@link
* SupportedSourceVersion}, return the source version in the
* annotation. If the class is not so annotated, {@link
* SourceVersion#RELEASE_6} is returned.
*
* @return the latest source version supported by this processor
*
* 如果處理器類使用SupportedSourceVersion注釋,則在注釋中回傳源版本,如果類沒有這樣注釋,SourceVersion,RELEASE_6回傳,
* 回傳:此處理器支持的最新源版本
*/
public SourceVersion getSupportedSourceVersion() {
SupportedSourceVersion ssv = this.getClass().getAnnotation(SupportedSourceVersion.class);
SourceVersion sv = null;
if (ssv == null) {
sv = SourceVersion.RELEASE_6;
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedSourceVersion annotation " +
"found on " + this.getClass().getName() +
", returning " + sv + ".");
} else
sv = ssv.value();
return sv;
}
/**
* Initializes the processor with the processing environment by
* setting the {@code processingEnv} field to the value of the
* {@code processingEnv} argument. An {@code
* IllegalStateException} will be thrown if this method is called
* more than once on the same object.
*
* @param processingEnv environment to access facilities the tool framework
* provides to the processor
* @throws IllegalStateException if this method is called more than once.
*
* 通過將processingEnv欄位設定為processingEnv引數的值,使用處理環境初始化處理器,如果在同一物件上多次呼叫此方法,將引發IllegalStateException,
* processingEnv—用于訪問工具框架提供給處理器的設施的環境
* Throws:IllegalStateException——如果該方法被多次呼叫,
*/
public synchronized void init(ProcessingEnvironment processingEnv) {
if (initialized)
throw new IllegalStateException("Cannot call init more than once.");
Objects.requireNonNull(processingEnv, "Tool provided null ProcessingEnvironment");
this.processingEnv = processingEnv;
initialized = true;
}
/**
* {@inheritDoc}
*
* 處理來自上一輪的型別元素上的一組注釋型別,并回傳該處理程式是否宣告這些注釋型別,如果回傳true,則宣告注釋型別,后續處理器將不會被要求處理它們;
* 如果回傳false,則不宣告注釋型別,隨后的處理器可能會被要求處理它們,處理器可以總是回傳相同的布林值,也可以根據選擇的條件改變結果,
* 如果處理器支持“*”且根元素沒有注釋,則輸入集將為空,處理器必須優雅地處理一組空的注釋,
*/
public abstract boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv);
/**
* Returns an empty iterable of completions.
*
* @param element {@inheritDoc}
* @param annotation {@inheritDoc}
* @param member {@inheritDoc}
* @param userText {@inheritDoc}
*/
public Iterable<? extends Completion> getCompletions(Element element,
AnnotationMirror annotation,
ExecutableElement member,
String userText) {
return Collections.emptyList();
}
/**
* Returns {@code true} if this object has been {@linkplain #init
* initialized}, {@code false} otherwise.
*
* @return {@code true} if this object has been initialized,
* {@code false} otherwise.
*/
protected synchronized boolean isInitialized() {
return initialized;
}
private static Set<String> arrayToSet(String[] array) {
assert array != null;
Set<String> set = new HashSet<String>(array.length);
for (String s : array)
set.add(s);
return Collections.unmodifiableSet(set);
}
}
我們繼承上面的父類并實作父類的方法:
package com.example;
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,我推薦你使用前者,
在Java 7及以后的版本中,你也可以在自定義注解處理器類上使用注解來代替getSupportedAnnotationTypes()和getSupportedSourceVersion()這兩個方法,像這樣:
@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
// 合法注解全名的集合
})
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
}
因為兼容的原因,特別是針對Android平臺,建議使用多載getSupportedAnnotationTypes()和getSupportedSourceVersion()方法代替@SupportedAnnotationTypes和@SuppozrtedSourceVersion,
接下來的你必須知道的事情是,注解處理器是運行它自己的虛擬機JVM中,是的,你沒有看錯,javac啟動一個完整Java虛擬機來運行注解處理器,這對你意味著什么?你可以使用任何你在其他java應用中使用的的東西,使用guava,如果你愿意,你可以使用依賴注入工具,例如dagger或者其他你想要的類別庫,但是不要忘記,即使是一個很小的處理,你也要像其他Java應用一樣,注意演算法效率,以及設計模式,
創建注解處理器NavProcessor,在下面的目錄中:
package com.mooc.libnavcompiler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.auto.service.AutoService;
import com.mooc.libnavannotation.ActivityDestination;
import com.mooc.libnavannotation.FragmentDestination;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
/**
* APP頁面導航資訊收集注解處理器
* <p>
* AutoService注解:就這么一標記,annotationProcessor project()應用一下,編譯時就能自動執行該類了,
* <p>
* SupportedSourceVersion注解:宣告我們所支持的jdk版本
* <p>
* SupportedAnnotationTypes:宣告該注解處理器想要處理那些注解
*/
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"com.mooc.libnavannotation.FragmentDestination", "com.mooc.libnavannotation.ActivityDestination"})
public class NavProcessor extends AbstractProcessor {
private Messager messager;
private Filer filer;
private static final String OUTPUT_FILE_NAME = "destination.json";
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//日志列印,在java環境下不能使用android.util.log.e()
messager = processingEnv.getMessager();
//檔案處理工具
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//通過處理器環境背景關系roundEnv分別獲取 專案中標記的FragmentDestination.class 和ActivityDestination.class注解,
//此目的就是為了收集專案中哪些類 被注解標記了
Set<? extends Element> fragmentElements = roundEnv.getElementsAnnotatedWith(FragmentDestination.class);
Set<? extends Element> activityElements = roundEnv.getElementsAnnotatedWith(ActivityDestination.class);
if (!fragmentElements.isEmpty() || !activityElements.isEmpty()) {
HashMap<String, JSONObject> destMap = new HashMap<>();
//分別 處理FragmentDestination 和 ActivityDestination 注解型別
//并收集到destMap 這個map中,以此就能記錄下所有的頁面資訊了
handleDestination(fragmentElements, FragmentDestination.class, destMap);
handleDestination(activityElements, ActivityDestination.class, destMap);
//app/src/main/assets
FileOutputStream fos = null;
OutputStreamWriter writer = null;
try {
//filer.createResource()意思是創建源檔案
//我們可以指定為class檔案輸出的地方,
//StandardLocation.CLASS_OUTPUT:java檔案生成class檔案的位置,/app/build/intermediates/javac/debug/classes/目錄下
//StandardLocation.SOURCE_OUTPUT:java檔案的位置,一般在/ppjoke/app/build/generated/source/apt/目錄下
//StandardLocation.CLASS_PATH 和 StandardLocation.SOURCE_PATH用的不多,指的了這個引數,就要指定生成檔案的pkg包名了
FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", OUTPUT_FILE_NAME);
String resourcePath = resource.toUri().getPath();
messager.printMessage(Diagnostic.Kind.NOTE, "resourcePath:" + resourcePath);
//由于我們想要把json檔案生成在app/src/main/assets/目錄下,所以這里可以對字串做一個截取,
//以此便能準確獲取專案在每個電腦上的 /app/src/main/assets/的路徑
String appPath = resourcePath.substring(0, resourcePath.indexOf("app") + 4);
String assetsPath = appPath + "src/main/assets/";
File file = new File(assetsPath);
if (!file.exists()) {
file.mkdirs();
}
//此處就是穩健的寫入了
File outPutFile = new File(file, OUTPUT_FILE_NAME);
if (outPutFile.exists()) {
outPutFile.delete();
}
outPutFile.createNewFile();
//利用fastjson把收集到的所有的頁面資訊 轉換成JSON格式的,并輸出到檔案中
String content = JSON.toJSONString(destMap);
fos = new FileOutputStream(outPutFile);
writer = new OutputStreamWriter(fos, "UTF-8");
writer.write(content);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return true;
}
private void handleDestination(Set<? extends Element> elements, Class<? extends Annotation> annotationClaz, HashMap<String, JSONObject> destMap) {
for (Element element : elements) {
//TypeElement是Element的一種,
//如果我們的注解標記在了類名上,所以可以直接強轉一下,使用它得到全類名
TypeElement typeElement = (TypeElement) element;
//全類名com.mooc.ppjoke.home
String clazName = typeElement.getQualifiedName().toString();
//頁面的id.此處不能重復,使用頁面的類名做hascode即可
int id = Math.abs(clazName.hashCode());
//頁面的pageUrl相當于隱士跳轉意圖中的host://schem/path格式
String pageUrl = null;
//是否需要登錄
boolean needLogin = false;
//是否作為首頁的第一個展示的頁面
boolean asStarter = false;
//標記該頁面是fragment 還是activity型別的
boolean isFragment = false;
Annotation annotation = element.getAnnotation(annotationClaz);
if (annotation instanceof FragmentDestination) {
FragmentDestination dest = (FragmentDestination) annotation;
pageUrl = dest.pageUrl();
asStarter = dest.asStarter();
needLogin = dest.needLogin();
isFragment = true;
} else if (annotation instanceof ActivityDestination) {
ActivityDestination dest = (ActivityDestination) annotation;
pageUrl = dest.pageUrl();
asStarter = dest.asStarter();
needLogin = dest.needLogin();
isFragment = false;
}
if (destMap.containsKey(pageUrl)) {
messager.printMessage(Diagnostic.Kind.ERROR, "不同的頁面不允許使用相同的pageUrl:" + clazName);
} else {
JSONObject object = new JSONObject();
object.put("id", id);
object.put("needLogin", needLogin);
object.put("asStarter", asStarter);
object.put("pageUrl", pageUrl);
object.put("className", clazName);
object.put("isFragment", isFragment);
destMap.put(pageUrl, object);
}
}
}
}
下面兩篇文章其實是一篇文章的不同轉載,閱讀其一即可:
Java注解處理器https://www.race604.com/annotation-processing/ Java注解處理器使用詳解_Rukey7的博客-CSDN博客_注解處理器在這篇文章中,我將闡述怎樣寫一個注解處理器(Annotation Processor),在這篇教程中,首先,我將向您解釋什么是注解器,你可以利用這個強大的工具做什么以及不能做什么;然后,我將一步一步實作一個簡單的注解器,一些基本概念在開始之前,我們首先申明一個非常重要的問題:
https://blog.csdn.net/github_35180164/article/details/52055994#
未完待續...
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/301075.html
標籤:其他
