Annotation 使用案例

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ù)把 DocMethodDocParam 的注解也寫出來

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末湿镀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子伐憾,更是在濱河造成了極大的恐慌勉痴,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件树肃,死亡現(xiàn)場離奇詭異蒸矛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)胸嘴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門雏掠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人劣像,你說我怎么就攤上這事乡话。” “怎么了耳奕?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵绑青,是天一觀的道長。 經(jīng)常有香客問我屋群,道長闸婴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任芍躏,我火速辦了婚禮邪乍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘对竣。我一直安慰自己庇楞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布柏肪。 她就那樣靜靜地躺著姐刁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪烦味。 梳的紋絲不亂的頭發(fā)上聂使,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機(jī)與錄音谬俄,去河邊找鬼柏靶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛溃论,可吹牛的內(nèi)容都是我干的屎蜓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼钥勋,長吁一口氣:“原來是場噩夢啊……” “哼炬转!你這毒婦竟也來了辆苔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤扼劈,失蹤者是張志新(化名)和其女友劉穎驻啤,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荐吵,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骑冗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了先煎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贼涩。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖薯蝎,靈堂內(nèi)的尸體忽然破棺而出遥倦,到底是詐尸還是另有隱情,我是刑警寧澤良风,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布谊迄,位于F島的核電站,受9級特大地震影響烟央,放射性物質(zhì)發(fā)生泄漏统诺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一疑俭、第九天 我趴在偏房一處隱蔽的房頂上張望粮呢。 院中可真熱鬧,春花似錦钞艇、人聲如沸啄寡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挺物。三九已至,卻和暖如春飘弧,著一層夾襖步出監(jiān)牢的瞬間识藤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工次伶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痴昧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓冠王,卻偏偏與公主長得像赶撰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一種元程序中的元素關(guān)聯(lián)任何信息和...
    九尾喵的薛定諤閱讀 3,168評論 0 2
  • 前面寫了Android 開發(fā):由模塊化到組件化(一),很多小伙伴來問怎么沒有Demo啊?之所以沒有立刻放demo的...
    涅槃1992閱讀 8,034評論 4 37
  • Maven作為一個(gè)優(yōu)秀的項(xiàng)目管理工具豪娜,其插件機(jī)制為其功能擴(kuò)展提供了非常大的便利性餐胀。Maven本身提供了很多的插件。...
    Soclever閱讀 6,335評論 0 9
  • 一瘤载、概念 ??Annotation(注解)就是Java提供了一種源程序中的元素關(guān)聯(lián)任何信息和任何元數(shù)據(jù)(metad...
    從菜鳥到老菜鳥閱讀 2,108評論 0 2
  • Annotation是特殊的interface骂澄,從java5開始引入。 因?yàn)锳nnotation的實(shí)現(xiàn)(proce...
    DjangoW閱讀 979評論 0 1