Java 的 annotation 自 JDK1.5就擁有了暑塑,主要作用就是給代碼打標(biāo)注诚撵,這個(gè)系列文章將從頭到尾進(jìn)行一個(gè)梳理捧韵,當(dāng)然不僅僅是 Annotation丝蹭,也包括他的解析;一些比較常見的做法犁享,百度谷歌一搜一大把的就再贅述余素。
還是直奔主題,我們以一個(gè)目標(biāo)進(jìn)行炊昆,比如我們需要做個(gè)一個(gè)文檔生成器桨吊,使用Annotation來對 API 進(jìn)行標(biāo)注, 然后結(jié)合 Maven 生成文檔凤巨;或者在編譯的時(shí)候生成文檔视乐,先結(jié)合 Maven 來使用。
構(gòu)建一些 Annotation
首先敢茁,我們需要準(zhǔn)備一些 Annotation佑淀,這個(gè) Annotation 將會給我們的其他項(xiàng)目使用,建議單獨(dú)是一個(gè)項(xiàng)目彰檬,按照思路伸刃,某些類比如 UserController需要標(biāo)注, 以確定這個(gè)類是我們需要掃描的逢倍,然后這個(gè)類的若干方法也需要標(biāo)注捧颅,方法里面包括參數(shù),目前先整這基礎(chǔ)的3個(gè)
较雕,先看類標(biāo)注
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)//對類接口進(jìn)行注解
@Retention(RetentionPolicy.RUNTIME)//在運(yùn)行時(shí)也保留該注解
@Documented//可以被 javadoc 文檔化
public @interface DocAction {
String value() default"";//功能描述
String[] author() default "";//作者隘道,可能有多個(gè)
String name() default "";//名稱
String date() default "";//開始日期
String update() default "";//結(jié)束日期
}
Target
Target標(biāo)識注解到什么地方,這里的ElementType
是一個(gè)枚舉類郎笆,源碼如下
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
//類、接口(包括注解類型)忘晤,或者枚舉
TYPE,
/** Field declaration (includes enum constants) */
//字段宛蚓,包括枚舉
FIELD,
/** Method declaration */
//方法
METHOD,
/** Parameter declaration */
//參數(shù)
PARAMETER,
/** Constructor declaration */
//構(gòu)造方法
CONSTRUCTOR,
/** Local variable declaration */
//本地變量
LOCAL_VARIABLE,
/** Annotation type declaration */
//注解
ANNOTATION_TYPE,
/** Package declaration */
//包
PACKAGE
}
上面就是所有的 target 注解地方,也順便看下@Target
的源碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
他的 target 就是ANNOTATION_TYPE
,也就是只能在 Annotation
上使用设塔,而里面 value()
方法返回值是一個(gè)數(shù)組凄吏,所以,target
里面可以是多個(gè)地方,比如@Target({ElementType.FIELD, ElementType.METHOD})
;
Retention
Retention是保存期痕钢,也就是該 Annotation 是存活時(shí)間图柏,他有3個(gè)值,如下源碼解釋
public enum RetentionPolicy {
/**
* 編譯的時(shí)候就沒了任连,但是在預(yù)編譯的時(shí)候還是存在的
*/
SOURCE,
/**
* 注解被保存在 class 文件中蚤吹,但是在 VM 運(yùn)行時(shí)候并不能獲得
*/
CLASS,
/**
* 和上個(gè) CLASS 的區(qū)別是在運(yùn)行時(shí)候也能獲得
*/
RUNTIME
}
接下來繼續(xù)把 DocMethod
和 DocParam
的注解也寫出來
DocMethod
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DocMethod {
String value() default "";//方法描述
String[] author() default "";//作者
String[] version() default "1.0.0";//版本,可以存在多個(gè)版本
String url() default "";//接口
Method method() default Method.GET;//請求方式
String date() default "";//日期
String update() default "";//更新日期
Type returnType();//返回類型
DocParam[] params();
public enum Method{
GET,POST,CALL
}
}
DocParam
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value={ElementType.METHOD,ElementType.PARAMETER})//方法和參數(shù)中都可以使用
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DocParam {
String value();//參數(shù)描述
Type type() default Type.String;//參數(shù)類型
public enum Type{
String,Long,Integer,Date,FLOAT,DOUBLE,OBJECT
}
}
到這里基本的注解都已經(jīng)完成,實(shí)際情況根據(jù)自己項(xiàng)目的需要自行定制随抠。
Maven 插件
如何寫一個(gè)插件裁着,搜索一下都有,不再贅述拱她,按照流程做個(gè)
1二驰、插件POM
按照普通 maven 項(xiàng)目骨架構(gòu)建一個(gè) maven 項(xiàng)目,項(xiàng)目的名稱位doc-maven-plugin
,按照 XXX-maven-plugin的格式來,這樣在運(yùn)行 maven 時(shí)候比較方便秉沼,修改 package 形式為maven-plugin
桶雀,具體如下
<groupId>cn.ts</groupId>
<artifactId>doc-maven-plugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
然后需要依賴如下的包
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.4</version>
<scope>provided</scope>
</dependency>
為了插件編寫的方便,下面連個(gè)依賴也一并加入
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.0.8</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-project</artifactId>
<version>2.2.1</version>
</dependency>
如果上面的 annotation 是單獨(dú)一個(gè)項(xiàng)目唬复,也需要引入矗积,否則解析 Annotation 找不到
2、寫個(gè) Mojo
Mojo
必須繼承AbstractMojo
盅抚,具體情況如下代碼詳述
@Mojo(name = "gen",
requiresProject=true,
requiresDependencyResolution=ResolutionScope.COMPILE_PLUS_RUNTIME,
requiresDependencyCollection=ResolutionScope.COMPILE_PLUS_RUNTIME)
public class DocMojo extends AbstractMojo {
@Override
public void execute() throws MojoExecutionException, MojoFailureException {}
}
說明下@Mojo
:name表示 MVN doc:XXX
這個(gè) XXX漠魏,這里就是mvn doc:gen
,requiresProject需要項(xiàng)目支持,我們的注解是注解到項(xiàng)目上的妄均,需要掃描項(xiàng)目代碼柱锹;requiresDependencyResolution表示需要項(xiàng)目在編譯運(yùn)行時(shí)的 CLASSPATH,方便我們獲取項(xiàng)目的 CLASS丰包,從而獲得 CLASS 上面的注解禁熏。
項(xiàng)目參數(shù)和配置參數(shù)
@Parameter(defaultValue = "${project}",required = true,readonly=true)
private MavenProject project;
@Parameter(property="port",defaultValue="3306")
private String port;
第一個(gè)獲取項(xiàng)目對應(yīng) POM 構(gòu)造,第二個(gè)是自己定義的一個(gè)參數(shù)邑彪,比如我們需要獲取數(shù)據(jù)庫連接參數(shù)等瞧毙,注解的含義一目了然。
核心解析方法
execute
是整個(gè) Mojo 的核心方法寄症,我們是業(yè)務(wù)實(shí)現(xiàn)也在這里宙彪,這里把偽代碼寫出
//根據(jù)配置參數(shù),構(gòu)造數(shù)據(jù)庫連接
Connection c=getConnByConfig(url,driver,user,pswd)
//從配置參數(shù)中獲得項(xiàng)目中的 CLASS 路徑
String targetPath=project.getBuild().getOutputDirectory();
//獲取路徑下所有類文件
List<File>classFile=scanClassFile(targetPath);
//根據(jù)類文件獲得類
List<Class<?>> classes=classForClassFile(classFile);
//解析類
//分析類的 Annotation
if(c==null)continue;
DocAction docAction = c.getAnnotation(DocAction.class);
if(docAction==null)continue;//沒有標(biāo)注文檔輸出的過濾掉
Method[] methods = c.getDeclaredMethods();
for(Method method:methods){
DocMethod docMethod = method.getAnnotation(DocMethod.class);
if(docMethod==null)continue;
DocParam[] params = docMethod.params();
for(DocParam param:params){
String name = param.value();
Type type=param.type();
//TODO 收集需要的Annotation
//信息進(jìn)行存儲到數(shù)據(jù)庫有巧,根據(jù)類名+方法名稱作為主鍵释漆,不存在插入,存在就更新
}
}
使用
插件寫好了篮迎,我們需要在項(xiàng)目中使用男图,建立一個(gè)maven項(xiàng)目示姿,依賴annotation,插件依賴上面的插件,同時(shí)插件里面依賴當(dāng)前項(xiàng)目逊笆。如下所示
<plugin>
<groupId>cn.ts</groupId>
<artifactId>doc-maven-plugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<configuration>
<port>3309</port><!-- 自定義參數(shù) -->
</configuration>
<dependencies><!-- 依賴本身項(xiàng)目撬碟,為了獲取 classpath -->
<dependency>
<groupId>cn.ts</groupId>
<artifactId>myrpc</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
對項(xiàng)目使用注解
import cn.ts.annotation.DocAction;
import cn.ts.annotation.DocMethod;
import cn.ts.annotation.DocMethod.Method;
import cn.ts.annotation.DocParam;
import cn.ts.annotation.DocParam.Type;
@DocAction(value="Test",author={"老唐"},date="2017-01-10",name="用戶信息")
public class UserAnnotation {
@DocMethod(value="測試用戶信息",
author={"Tangshun"},
method=Method.GET,
url="user",
version="1.0.0",
params={
@DocParam(value="用戶姓名",type=Type.String),
@DocParam(value="用戶年齡",type=Type.Integer)
}, returnType = Type.NONE)
public void test( String name,Integer age){}
}
執(zhí)行命令
mvn doc:gen
激蹲,在 eclipse 環(huán)境配置如下

不出意外麦向,你能獲得需要的東西
上面是基于 Maven 插件的形式祷膳,我們需要把 Annotation 存活時(shí)間設(shè)置為 RunTime,如果設(shè)置為 Source 或者 CLASS 又怎樣解析差牛?
Annotation CLASS解析
首先把注解的存活時(shí)間設(shè)置為 CLASS命锄。這里我構(gòu)建三個(gè) maven 項(xiàng)目來進(jìn)行

先看下annotation 部分

重點(diǎn)是 annotation-process,先看他的 pom 文件偏化,把依賴加入
<dependency>
<groupId>cn.ts</groupId>
<artifactId>x-annotation</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 可選插件 不再自己增加 META-INF/service/插件處理 -->
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc2</version>
</dependency>
接下來脐恩,我們需要編寫自己的 Annotation 處理器,該處理器需要實(shí)現(xiàn)AbstractProcessor
,并且復(fù)寫核心方法process
public class ClassAnnotationProcess extends AbstractProcessor{
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {}
}
參數(shù)說明:
輸入?yún)?shù)RoundEnviroment侦讨,可以讓你查詢出包含特定注解的被注解元素驶冒,比如roundEnv.getElementsAnnotatedWith(FirstAnotation.class)
,獲得注解了FirstAnotation
的元素,元素有
PACKAGE, ENUM, CLASS, ANNOTATION_TYPE, INTERFACE, ENUM_CONSTANT, FIELD, PARAMETER, LOCAL_VARIABLE, EXCEPTION_PARAMETER, METHOD, CONSTRUCTOR, STATIC_INIT, INSTANCE_INIT, TYPE_PARAMETER, OTHER, RESOURCE_VARIABLE;
根據(jù)元素類型枚舉值轉(zhuǎn)換對應(yīng)的元素類型韵卤,進(jìn)一步根據(jù)typeElement.getAnnotation(FirstAnotation.class)
獲得注解的詳細(xì)情況骗污,做進(jìn)一步操作。
還有一些方法也需要override
沈条,如下
/**
* init()方法會被注解處理工具調(diào)用需忿,并輸入ProcessingEnviroment參數(shù)。
* ProcessingEnviroment提供很多有用的工具類Elements, Types 和 Filer
* @param processingEnv 提供給 processor 用來訪問工具框架的環(huán)境
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// Filer是個(gè)接口蜡歹,支持通過注解處理器創(chuàng)建新文件
filer = processingEnv.getFiler();
}
/**
* 這里必須指定屋厘,這個(gè)注解處理器是注冊給哪個(gè)注解的。注意月而,它的返回值是一個(gè)字符串的集合汗洒,包含本處理器想要處理的注解類型的合法全稱
* @return 注解器所支持的注解類型集合,如果沒有這樣的類型父款,則返回一個(gè)空集合
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotataions.add(FirstAnotation.class.getCanonicalName());
return annotataions;
}
/**
* 指定使用的Java版本溢谤,通常這里返回SourceVersion.latestSupported(),默認(rèn)返回SourceVersion.RELEASE_6
* @return 使用的Java版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
寫完 annotation 處理器憨攒,打包世杀,在demo項(xiàng)目依賴中引用
<dependencies>
<dependency>
<groupId>cn.ts</groupId>
<artifactId>x-annotation</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.ts</groupId>
<artifactId>x-annotation-process</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
可能的問題:
在處理類上注解@AutoService(Processor.class) ,可能沒有作用肝集,需要在 src/main/resources/目錄下構(gòu)建 META_INF/service 子目錄玫坛,并且建立文件javax.annotation.processing.Processor,并且在里面輸入插件的具體名稱包晰,如cn.ts.x_annotation_process.ClassAnnotationProcess
版權(quán)印為您的作品印上版權(quán)32805660