日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

结合提供者模式解析Jenkins源码国际化的实现

發(fā)布時(shí)間:2024/8/26 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 结合提供者模式解析Jenkins源码国际化的实现 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

關(guān)鍵字:提供者模式,設(shè)計(jì)模式,github,gerrit,源碼學(xué)習(xí),jenkins,國(guó)際化,maven高級(jí),maven插件

本篇文章的源碼展示部分由于長(zhǎng)度問(wèn)題不會(huì)全部粘貼展示,或許只是直接提及,需要了解的朋友請(qǐng)fork in github,文中會(huì)給出源碼地址。

源碼的研究策略

從這篇文章開(kāi)始,陸續(xù)要展開(kāi)一些源碼分析的內(nèi)容,既然確立了這個(gè)目標(biāo),就要尋找研究源碼的策略,經(jīng)過(guò)各方面的取經(jīng)和自己的總結(jié),接下來(lái)我將采取的策略為:

  • 源碼內(nèi)容:
    • 從最早的release版本開(kāi)始,任何偉大而復(fù)雜的工程可能都源自于“helloworld”,從最初的版本(如果你能找到的話(huà))開(kāi)始看,可能會(huì)降低很多難度,隨著工程的不斷升級(jí),根據(jù)release歷史,可以跟蹤到每次大的升級(jí)更新內(nèi)容。
  • 源碼庫(kù):
    • 采用github。作為世界最大的源碼庫(kù),github使用非常方便,并且我也在上面有很多自己的repo。可以直接fork官方源碼然后加入自己的調(diào)試研究過(guò)程,可以記錄下每一次的更新與變化,我想這也是github除了保存自己的代碼以外最為重要的功能之一。
  • 程序入口:
    • 本地工程運(yùn)行,按圖索驥,編譯調(diào)試,理解設(shè)計(jì)模式的一些常見(jiàn)命名方式。
  • 分析架構(gòu):
    • 結(jié)合官方手冊(cè)(注意要與當(dāng)前源碼release版本相一致)get started, API,使用UML,分析核心功能模塊的實(shí)現(xiàn)。
  • 造輪子:
    • 修改源碼工程,添加自己的注釋,增加自己的代碼,以支持模擬業(yè)務(wù)場(chǎng)景。同時(shí)要保存自己的github提交歷史,這也是學(xué)習(xí)過(guò)程的記錄。
  • 在以下文章分析過(guò)程中,我會(huì)通過(guò)這種格式來(lái)記錄每一個(gè)我突發(fā)奇想的可以用來(lái)實(shí)驗(yàn)Jenkins源碼的業(yè)務(wù)需求,這些需求會(huì)在未來(lái)繼續(xù)研究源碼的文章中進(jìn)行實(shí)現(xiàn)。

    搭建源碼開(kāi)發(fā)環(huán)境

    一、github本地配置修改

    由于本地存在其他git庫(kù)的配置并且他們集成了gerrit,所以如果我想在本地配置一套github的開(kāi)發(fā)環(huán)境,必須要做些改變。如果你的機(jī)器是純凈的,大可不必有此顧慮,直接配置成全局變量即可。

    git配置文件

    git的默認(rèn)配置是在用戶(hù)home目錄下的.gitconfig文件,這個(gè)文件我是不可以修改的,否則會(huì)影響現(xiàn)有庫(kù)的使用。而在每個(gè)git工程中還有.git目錄,這下面的config就是該項(xiàng)目的本地Git配置,相當(dāng)于復(fù)寫(xiě)home目錄下的.gitconfig文件,home目錄下的對(duì)應(yīng)的是global配置,項(xiàng)目本地的對(duì)應(yīng)的是local配置。

    gerrit

    • 代碼審核服務(wù)器,一種免費(fèi)、開(kāi)放源代碼的代碼審查軟件,使用網(wǎng)頁(yè)界面。
    • 同一個(gè)團(tuán)隊(duì)的軟件程序員,可以相互審閱彼此修改后的程序代碼,決定是否能夠提交,退回或者繼續(xù)修改。
    • 通過(guò)鉤子hooks/commit-msg程序,將每次推送的提交附屬上唯一的Change-Id,從而轉(zhuǎn)化為一個(gè)一個(gè)的代碼審核任務(wù)。
    • 代碼審核工作流,完全在網(wǎng)頁(yè)上面操作,其中涉及到comments,Code-Review,Verified,submit,merge等操作。
    • gerrit同時(shí)也是一個(gè)git的版本庫(kù),一般用于維護(hù)項(xiàng)目的主干分支,各開(kāi)發(fā)者可以將本地庫(kù)與其進(jìn)行pull,merge等操作。

    本地git配置文件修改

    1.刪除hooks

    目標(biāo)確定為git工程下的.git目錄,首先刪除其中的hooks文件夾(hooks默認(rèn)為空,如果安裝了gerrit,每次clone時(shí)會(huì)同步下載hooks/commit-msg鉤子程序),要知道gerrit的集成主要就靠這個(gè)鉤子,這個(gè)鉤子的作用就是每次在你提交代碼時(shí),默認(rèn)附屬上一串Change-Id,這樣一來(lái)就將你的每一次提交建立了一個(gè)主鍵,通過(guò)這個(gè)主鍵去review,merge等

    2.配置用戶(hù)名和郵件

    在git工程下直接配置上git config user. name 和user.email即可使用當(dāng)前配置而不是用戶(hù)目錄下的.gitconfig的默認(rèn)配置。請(qǐng)參考Setting your username in Git

    3.git提供ssh和http兩種交互方式

    這里采用http的方式,它可以繞過(guò)防火墻和網(wǎng)絡(luò)代理,很方便,但是每次與遠(yuǎn)端庫(kù)交互的時(shí)候都要驗(yàn)證賬戶(hù)和密碼。請(qǐng)參考remote url method

    • ssh

    ssh的方式要在遠(yuǎn)端庫(kù)中配置上本地的id_rsa.pub,從而實(shí)現(xiàn)免密認(rèn)證。

    • http

    http的話(huà),直接使用credential.helper store來(lái)存儲(chǔ)用戶(hù)名密碼,可避免日后必須始終輸入賬號(hào)密碼的麻煩。具體操作如下:

    evsward@lwbsPC:~/work/github/mainbase$ git config credential.helper store evsward@lwbsPC:~/work/github/mainbase$ git push origin master Username for 'https://github.com': evsward Password for 'https://evsward@github.com': Everything up-to-date evsward@lwbsPC:~/work/github/mainbase$ git push origin master Everything up-to-date

    http修改存儲(chǔ)密碼的方式以上方式會(huì)在根目錄下建立一個(gè).git-credentials的文件明文存儲(chǔ)密碼。雖然可以指定該文件的訪(fǎng)問(wèn)權(quán)限,我仍然覺(jué)得很不安全,所以采用另外一種方式——存儲(chǔ)于緩存。請(qǐng)參考Caching your GitHub password in Git,延長(zhǎng)默認(rèn)緩存時(shí)間從15分鐘改為1小時(shí)。如下方式執(zhí)行以后,會(huì)在用戶(hù)根目錄下生成一個(gè)文件夾.git-credential-cache,里面存儲(chǔ)一個(gè)socket的設(shè)備文件,用于緩存用戶(hù)名密碼,通常手段無(wú)法讀取這個(gè)文件,采取緩存用戶(hù)名密碼的方式比起上一種直接存儲(chǔ)的方式要安全一些。(注意:當(dāng)你的系統(tǒng)仍需連接其他git庫(kù)的時(shí)候,參數(shù)不要使用global,全部設(shè)置為local即默認(rèn))另外,同一個(gè)github下的不同項(xiàng)目只要存儲(chǔ)過(guò)一次賬號(hào)密碼以后,任何項(xiàng)目在其本地執(zhí)行

    git config credential.helper 'cache --timeout=3600'

    不必初始化存入密碼,即可立即免密使用,因?yàn)橥粋€(gè)github賬戶(hù)下的項(xiàng)目訪(fǎng)問(wèn)時(shí)的賬戶(hù)密碼是相同的,默認(rèn)都是從用戶(hù)根目錄下的.git-credential-cache去讀取,因此,同一個(gè)github賬戶(hù)初始化過(guò)程只需要一次即可。當(dāng)然了,超過(guò)了我們?cè)O(shè)定的緩存時(shí)限1個(gè)小時(shí),就需要重新輸入了。下面是具體操作方式:

    evsward@lwbsPC:~/work/github/mainbase$ git config credential.helper 'cache --timeout=3600' evsward@lwbsPC:~/work/github/mainbase$ git push origin master Username for 'https://github.com': evsward Password for 'https://evsward@github.com': Everything up-to-date evsward@lwbsPC:~/work/github/mainbase$ git push origin master Everything up-to-date

    git修改歷史提交記錄

    一般來(lái)說(shuō)是直接reset + commitId,然后git push -f <remote> <branch>到遠(yuǎn)程庫(kù)直接刪除commitId以后的所有提交歷史,請(qǐng)參考git如何修改已提交的commit

    二、Jenkins項(xiàng)目源碼

    1.首先f(wàn)ork Jenkins源碼到自己的賬戶(hù),并下載到本地。
    2.同步更新,Configuring a remote for a fork -> Syncing a fork

    Jenkins 業(yè)務(wù)構(gòu)想之一:監(jiān)控Jenkins 源代碼,如果有任何更新,則fetch到本地,然后同步推送至我的github庫(kù)。

    3.開(kāi)始檢查jenkins 的release版本,找到第一個(gè)發(fā)布在github上的release版本1.312,可惜的是這個(gè)歷史版本因?yàn)樘爬现涣粝铝藌ip的下載方式,直接下載下來(lái),jenkins-1.312.zip。
    4.github網(wǎng)頁(yè)端新建一個(gè)repo起名為jenkins-1.312,將這個(gè)空項(xiàng)目clone到本地,然后導(dǎo)入前面下載的jenkins-1.312.zip解壓出來(lái)的文件。
    5.注意新clone下來(lái)的github項(xiàng)目一定要先刪除hooks,配置好user. name,email以及credential,然后push到github遠(yuǎn)端。
    6.eclipse通過(guò)檢測(cè)pom文件將jenkins1.312以maven項(xiàng)目導(dǎo)入。

    三、Maven構(gòu)建源碼工程

    本文就細(xì)細(xì)地將研究過(guò)程中遇到的所有可記錄的知識(shí)點(diǎn)都寫(xiě)下來(lái)。

    配置Maven

    1.去Maven下載一個(gè)zip包,我下載的是Maven3.5.2
    2.解壓縮,打開(kāi)conf/setting.xml,修改localRepository到你預(yù)設(shè)的本地Maven資源庫(kù)。
    3.修改mirror,添加阿里云maven庫(kù)

    <mirrors><mirror><id>nexus-aliyun</id><mirrorOf>*</mirrorOf><name>Nexus aliyun</name><url>http://maven.aliyun.com/nexus/content/groups/public</url></mirror></mirrors>

    4.在eclipse中配置上剛剛下載并修改好的maven地址,同時(shí)別忘記更改user-setting。
    5.linux下配置maven環(huán)境變量(Windows的配置這里不再贅述),在用戶(hù)根目錄下打開(kāi).profile,增加export MAVEN_HOME=/home/CORPUSERS/evsward/work/apache-maven-3.5.2,并將$MAVEN_HOME/bin添加到PATH中去。
    6.terminal下輸入mvn -v測(cè)試。

    開(kāi)始構(gòu)建

    eclipse中直接使用clean project來(lái)觸發(fā)maven重構(gòu)工程,但是發(fā)生錯(cuò)誤,我們剛配置的阿里云的maven庫(kù)似乎連接不上,我按圖索驥,使用瀏覽器對(duì)該url路徑進(jìn)行了檢查,確定了這個(gè)文件確實(shí)是存在于阿里云上面的。

    下面我在terminal中,定位到項(xiàng)目路徑下,使用命令去測(cè)試mvn install(安裝artifacts,compile是編譯工程代碼,package是為現(xiàn)有工程打包并上傳到maven庫(kù)),錯(cuò)誤仍舊是那樣。所以目前的問(wèn)題是瀏覽器可以訪(fǎng)問(wèn),但是terminal和eclipse無(wú)法訪(fǎng)問(wèn)。

    我又嘗試了在terminal中直接wget,仍然是好使的,我將vpn配發(fā)的proxy路徑配置到$MAVEN_HOME/conf/setting.xml中以后,開(kāi)始工作了!

    <proxies><proxy><id>A</id><active>true</active><protocol>http</protocol><username>evsward</username><password>xxxxxxx</password><host>proxy.xxxx.net</host><port>8080</port></proxy><proxy><id>B</id><active>true</active><protocol>https</protocol><username>evsward</username><password>xxxxxxx</password><host>proxy.xxxxxx.net</host><port>8080</port></proxy></proxies>

    阿里云的Maven庫(kù)還是非常全的!

    我們先terminal本地install一下,最終Maven安裝artifacts結(jié)果如下:

    [INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary: [INFO] [INFO] Hudson main module ................................. SUCCESS [04:48 min] [INFO] Hudson remoting layer .............................. SUCCESS [01:58 min] [INFO] Hudson CLI ......................................... SUCCESS [02:31 min] [INFO] Hudson core ........................................ FAILURE [05:33 min] [INFO] Hudson Maven PluginManager interceptor ............. SKIPPED [INFO] Hudson Maven CLI agent ............................. SKIPPED [INFO] Maven Integration plugin ........................... SKIPPED [INFO] Hudson war ......................................... SKIPPED [INFO] Test harness for Hudson and plugins ................ SKIPPED [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 22:49 min [INFO] Finished at: 2017-11-22T17:12:58+08:00 [INFO] Final Memory: 30M/164M [INFO] ------------------------------------------------------------------------

    總共耗時(shí)近23分鐘,只有一項(xiàng)Hudson core編譯失敗了,其他均成功了。

    Jenkins 業(yè)務(wù)構(gòu)想之二:每次的源碼更新,本地要自動(dòng)執(zhí)行mvn install去編譯,這樣就為我們真正的開(kāi)發(fā)節(jié)省了很多時(shí)間。

    現(xiàn)在去查看一下我們的repo目錄:

    evsward@lwbsPC:~/work/maven-repo$ ls ant commons-digester geronimo-spec net antlr commons-discovery httpunit org aopalliance commons-el javanettasks oro args4j commons-fileupload javax plexus asm commons-httpclient jaxen qdox avalon-framework commons-io jdom slide backport-util-concurrent commons-jelly jfree stax ch commons-lang jline velocity classworlds commons-logging jtidy xalan com commons-pool junit xerces commons-beanutils commons-validator log4j xml-apis commons-cli de logkit xom commons-codec dom4j mx4j xpp3 commons-collections doxia nekohtmlevsward@lwbsPC:~/work/maven-repo$ du -sh 115M

    可以看到,原來(lái)空空如也的本地repo已經(jīng)被填入了115M的不同的依賴(lài)包,這些都是從之前我們配置的mirror——阿里云下載過(guò)來(lái)的。

    下面我們轉(zhuǎn)戰(zhàn)到IDE,刷新一下項(xiàng)目,工程在Maven的幫助下自動(dòng)進(jìn)入安裝階段。

    • 失敗一次

    可惜最終還是沒(méi)有build成功,報(bào)錯(cuò)信息顯示有些依賴(lài)包在阿里云上面無(wú)法找到,看來(lái)阿里云還是不夠全啊。

    • 失敗二次

    于是我將conf/setting.xml中的mirror內(nèi)容注釋掉了,重新運(yùn)行mvn package從maven中央庫(kù)下載,build又開(kāi)始工作了!(之前加的mirror不是當(dāng)時(shí)無(wú)法download的根源問(wèn)題,根源問(wèn)題已解決,是proxy的問(wèn)題)

    • 失敗N次

    失敗已經(jīng)持續(xù)了10個(gè)小時(shí),轉(zhuǎn)去翻官方文檔。

    重新出發(fā)

    由于沒(méi)有依據(jù)官方文檔,自己在摸索中構(gòu)建導(dǎo)致了很多問(wèn)題,無(wú)法順利構(gòu)建成功,這一次依據(jù)官方文檔,Build Jenkins,我來(lái)嘗試follow一下。第一個(gè)改變就是我們丟棄了jenkins-1.312版本,直接使用jenkin最新版本,這是因?yàn)樽钚掳姹镜奈臋n和代碼都是非常齊全,適合我們分析與研究。嫌麻煩的同學(xué)不用擔(dān)心,我會(huì)將所有的構(gòu)建步驟貼在下面。
    1.構(gòu)建準(zhǔn)備

    使用jdk7+,maven3

    2.環(huán)境變量配置

    alias jdk7='export JAVA_HOME=/home/CORPUSERS/evsward/work/java/jdk1.7.0_80_x64 ; export PATH=$JAVA_HOME/bin:$PATH'

    3.maven 基礎(chǔ)構(gòu)建

    $ cd jenkins $ mvn -Plight-test install

    4.找到作者的github

    If you want simply to have the jenkins.war as fast as possible (without test execution), run:mvn clean install -pl war -am -DskipTestsThe WAR file will be in war/target/jenkins.war (you can play with it) You can deactivate test-harness execution with -Dskip-test-harness

    最終,terminal build成功!

    [INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary: [INFO] [INFO] Jenkins main module ................................ SUCCESS [ 0.888 s] [INFO] Jenkins cli ........................................ SUCCESS [ 12.555 s] [INFO] Jenkins core ....................................... SUCCESS [01:31 min] [INFO] Jenkins war ........................................ SUCCESS [01:09 min] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 02:54 min [INFO] Finished at: 2017-11-23T13:13:35+08:00 [INFO] Final Memory: 86M/488M [INFO] ------------------------------------------------------------------------

    在目錄war/target/jenkins.war中已經(jīng)在本地成功生成了jenkins.war包。但是環(huán)境依然有問(wèn)題,有很多紅叉在項(xiàng)目里面。

    localizer

    打開(kāi)整個(gè)jenkins工程,感覺(jué)亂七八糟頭有點(diǎn)大,不知道從何開(kāi)始研究,本地跑起來(lái)剛剛生成的war包沒(méi)問(wèn)題,但是調(diào)試起來(lái)還有很多障礙,我們就先從這些紅叉叉開(kāi)始研究吧。localizer也是由kohsuke(Hudson&Jenkins的作者)寫(xiě)的一個(gè)屬性文件本地化工具。先來(lái)介紹一下它的功能,它可以將屬性文件*.properties按照國(guó)際化語(yǔ)言設(shè)定規(guī)則轉(zhuǎn)成一個(gè)常量類(lèi)文件,可以直接在其他類(lèi)中調(diào)用。我們把jenkins.war包解壓縮,找到cli-2.92-SNAPSHOT.war,繼續(xù)解壓縮,進(jìn)入到cli-2.92-SNAPSHOT/hudson/cli/client目錄下,可以發(fā)現(xiàn):

    Messages_bg.properties Messages_es.properties Messages.properties Messages.class Messages_fr.properties Messages_pt_BR.properties Messages_da.properties Messages_it.properties Messages_zh_TW.properties Messages_de.properties Messages_ja.properties

    這里面除了我們編寫(xiě)的各個(gè)國(guó)家地區(qū)的語(yǔ)言屬性文件,還有一個(gè)Message.class并不是我們寫(xiě)的,而是Maven生成的,這非常方便,因?yàn)閷傩晕募鳛殪o態(tài)文件,并不是類(lèi)需要?jiǎng)討B(tài)編譯,所以常量類(lèi)文件可以完全被屬性文件取代,同時(shí)又能擁有常量類(lèi)文件的調(diào)用便攜性。下面我們來(lái)分析一下這個(gè)工具。

    1.首先去kohsuke的github庫(kù)中下載該項(xiàng)目
    每次下載都要執(zhí)行以下操作(這僅針對(duì)于我的環(huán)境):

    evsward@lwbsPC:~/work/github/localizer/.git$ rm -rf hooks evsward@lwbsPC:~/work/github/localizer/.git$ vi config evsward@lwbsPC:~/work/github/localizer/.git$ cd.. evsward@lwbsPC:~/work/github/localizer$ git config credential.helper 'cache --timeout=3600' evsward@lwbsPC:~/work/github/localizer$ git config user.name "evsward" evsward@lwbsPC:~/work/github/localizer$ git config user.email "xxxxx@xxx.com"

    2.導(dǎo)入項(xiàng)目到eclipse中去
    查看核心類(lèi)ResourceBundleHolder類(lèi),可以看到上面的holder.format方法:

    /*** Formats a resource specified by the given key by using the default locale*/public String format(String key, Object... args) {return MessageFormat.format(get(LocaleProvider.getLocale()).getString(key), args);}

    其中g(shù)et(LocaleProvider.getLocale())方法在類(lèi)的上方也給出了。

    Jenkins業(yè)務(wù)構(gòu)想之三:開(kāi)發(fā)一個(gè)處理國(guó)際語(yǔ)言本地化的工具系統(tǒng)

    每種語(yǔ)言都有一個(gè)文件后綴,例如漢語(yǔ)是zh,這里是全世界關(guān)于這個(gè)語(yǔ)言后綴的列表。取其中ISO 639-1的值。

    源碼相關(guān)知識(shí),對(duì)象的軟引用SoftReference,弱引用WeakReference。弱引用HashMap:WeakHashMap。強(qiáng)引用也是類(lèi)加載器引用a classloader refernce

    3.介紹一種緩存的使用方法

    private static final Map<Class<?>, WeakReference<ResourceBundleHolder>> cache =new WeakHashMap<Class<?>, WeakReference<ResourceBundleHolder>> ();public synchronized static ResourceBundleHolder get(Class<?> clazz) {WeakReference<ResourceBundleHolder> entry = cache.get(clazz);if (entry != null) {ResourceBundleHolder rbh = entry.get();if (rbh != null) return rbh;}ResourceBundleHolder rbh = new ResourceBundleHolder(clazz);cache.put(clazz, new WeakReference<ResourceBundleHolder>(rbh));return rbh;}

    分析一波:

    • 這個(gè)緩存cache的類(lèi)型是WeakHashMap,即弱引用的哈希Map,并且它的key為Class類(lèi),值為弱引用的ResourceBundleHolder對(duì)象。這種緩存的定義就決定了它在垃圾回收器需要的時(shí)候可以隨時(shí)自動(dòng)清除針對(duì)此對(duì)象的所有弱引用。
    • 我們來(lái)看下面的get(Class)方法,首先它是上了鎖的,這是必須的,因?yàn)橐苊饩彺嫱瑫r(shí)被多線(xiàn)程操作造成內(nèi)部數(shù)據(jù)混亂的結(jié)果。然后,它也是static的,所以這個(gè)方法是屬于類(lèi)的而不是對(duì)象的,比起對(duì)象屬性,它的作用域更加廣,也保證了緩存的唯一性。
    • 另外,這個(gè)緩存在系統(tǒng)資源緊張的時(shí)候會(huì)被隨時(shí)清理,就會(huì)出現(xiàn)你去按key查找卻查不到值的情況。看進(jìn)去方法體,先獲得key為某Class的值,判斷其是否為空,不為空則取出,為空則說(shuō)明要么是該key從來(lái)沒(méi)被存入過(guò),要么是被垃圾回收器清理掉了,無(wú)論哪種情況,我們?cè)俅嫒胍槐榧纯伞W⒁庖栽揅lass為key的值,就是ResourceBundleHolder的弱引用對(duì)象。(所以構(gòu)造器ResourceBundleHolder(Class o)雖然被丟棄,但內(nèi)部仍要使用。
    /*** @param owner* The name of the generated resource bundle class.* @deprecated* Use {@link #get(Class)}*/public ResourceBundleHolder(Class<?> owner) {this.owner = owner;}

    4.本地化文件的集合容器

    private transient final Map<Locale,ResourceBundle> bundles = new ConcurrentHashMap<Locale,ResourceBundle>();

    java 的transient關(guān)鍵字為我們提供了便利,你只需要實(shí)現(xiàn)Serilizable接口,將不需要序列化的屬性前添加關(guān)鍵字transient,序列化對(duì)象的時(shí)候,這個(gè)屬性就不會(huì)序列化到指定的目的地中。

    ConcurrentHashMap 是支持并發(fā)的HashMap。Locale和ResourceBundle都是Jdk中的關(guān)于國(guó)際化的類(lèi)。

  • ResourceBundle,資源包包含特定于語(yǔ)言環(huán)境的對(duì)象。當(dāng)程序需要一個(gè)特定于語(yǔ)言環(huán)境的資源時(shí)(如 String),程序可以從適合當(dāng)前用戶(hù)語(yǔ)言環(huán)境的資源包中加載它。
  • Locale,Locale 對(duì)象表示了特定的地理、政治和文化地區(qū)。上面提到的眾多國(guó)家地區(qū)語(yǔ)言都可以從這個(gè)類(lèi)中取到。
  • 下面是ResourceBundleHolder最核心的方法get(Locale locale),

    public ResourceBundle get(Locale locale) {ResourceBundle rb = bundles.get(locale);// 根據(jù)地區(qū)情況獲取其資源包if (rb != null)return rb;synchronized (this) {// 如果未獲取到locale對(duì)應(yīng)的資源包,則為其上鎖并創(chuàng)建資源包rb = bundles.get(locale);if (rb != null)return rb;// 進(jìn)鎖以后再查一遍。還是沒(méi)有則繼續(xù)Locale next = getBaseLocale(locale);// 擴(kuò)大一級(jí)搜索范圍String s = locale.toString();// 這個(gè)owner就是Message類(lèi),拼串以后就是例如Message_zh.properties,getResource查找?guī)в薪o定名稱(chēng)的資源。URL res = owner.getResource(owner.getSimpleName() + (s.length() > 0 ? '_' + s : "") + ".properties");if (res != null) {// 找到對(duì)應(yīng)屬性文件try {URLConnection uc = res.openConnection();uc.setUseCaches(false);InputStream is = uc.getInputStream();// ResourceBundleImpl是自己實(shí)現(xiàn)的一個(gè)可根據(jù) InputStream// 創(chuàng)建屬性資源包。構(gòu)造器是繼承實(shí)現(xiàn)ResourceBundleImpl bundle = new ResourceBundleImpl(is);is.close();rb = bundle;if (next != null)// 多線(xiàn)程操作,當(dāng)涉及事務(wù)操作的時(shí)候要先做檢查// ResourceBundle類(lèi)可以根據(jù)parent屬性找到相近的屬性文件,而不是查不到就直接返回null.bundle.setParent(get(next));bundles.put(locale, bundle);} catch (IOException e) {MissingResourceException x = new MissingResourceException("Unable to load resource " + res,owner.getName(), null);x.initCause(e);throw x;}} else {if (next != null)bundles.put(locale, rb = get(next));elsethrow new MissingResourceException("No resource was found for " + owner.getName(), owner.getName(),null);}}return rb;}

    總結(jié)一下ResourceBundleHolder類(lèi),就是它是一個(gè)序列化的本地化資源數(shù)據(jù)的緩存,緩存中存儲(chǔ)了多個(gè)key為類(lèi),值為該類(lèi)為owner的ResourceBundleHolder類(lèi)的鍵值對(duì)。而每個(gè)ResourceBundleHolder對(duì)象會(huì)維護(hù)一個(gè)不序列化且外部不可修改的成員屬性二級(jí)緩存Map,該Map會(huì)存儲(chǔ)每次查詢(xún)過(guò)的本地化文件數(shù)據(jù),如果沒(méi)有則會(huì)新插入數(shù)據(jù)。在插入新數(shù)據(jù)時(shí),要根據(jù)本地化參數(shù)Locale去查找相近的屬性文件,然后將該文件存入資源包作為前面說(shuō)的那個(gè)Map的值。

    5.Message類(lèi)
    按圖索驥,下面來(lái)分析上面提到的那個(gè)由Maven自動(dòng)生成的Message類(lèi),我們將它反編譯看一下:

    package hudson.cli.client;import org.jvnet.localizer.Localizable; import org.jvnet.localizer.ResourceBundleHolder; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse;@Restricted({ NoExternalUse.class }) public class Messages {private static final ResourceBundleHolder holder = ResourceBundleHolder.get(Messages.class);public Messages() {}public static String CLI_Usage() {return holder.format("CLI.Usage", new Object[0]);}public static Localizable _CLI_Usage() {return new Localizable(holder, "CLI.Usage", new Object[0]);}public static String CLI_NoURL() {return holder.format("CLI.NoURL", new Object[0]);}public static Localizable _CLI_NoURL() {return new Localizable(holder, "CLI.NoURL", new Object[0]);}public static String CLI_NoSuchFileExists(Object arg0) {return holder.format("CLI.NoSuchFileExists", new Object[] { arg0 });}public static Localizable _CLI_NoSuchFileExists(Object arg0) {return new Localizable(holder, "CLI.NoSuchFileExists", new Object[] { arg0 });}public static String CLI_VersionMismatch() {return holder.format("CLI.VersionMismatch", new Object[0]);}public static Localizable _CLI_VersionMismatch() {return new Localizable(holder, "CLI.VersionMismatch", new Object[0]);} }

    這個(gè)類(lèi)是直接使用我們剛剛分析過(guò)的ResourceBundleHolder類(lèi),其中還調(diào)用到了Localizable類(lèi),下面來(lái)分析一下Localizable類(lèi),然后再繞回來(lái)繼續(xù)分析它。

    6.Localizable類(lèi)
    Localizable類(lèi)就是一個(gè)針對(duì)本地資源包國(guó)家地區(qū)數(shù)據(jù)的一個(gè)封裝類(lèi),該類(lèi)有一個(gè)ResourceBundleHolder的私有成員對(duì)象。然后比較重要的就是它的toString(Locale locale)方法:

    public String toString(Locale locale) {try {return MessageFormat.format(holder.get(locale).getString(key),(Object[])args);} catch (MissingResourceException e) {throw new RuntimeException("Failed to localize key="+key+",args="+ asList(args),e);}}

    這里面調(diào)用到了我們分析ResourceBundleHolder類(lèi)中的核心方法get(Locale),也即通過(guò)地區(qū)條件Locale查找對(duì)應(yīng)的屬性文件。這里有個(gè)"key",代表的是屬性文件內(nèi)部的數(shù)據(jù)的key(屬性文件內(nèi)部數(shù)據(jù)結(jié)構(gòu)也是key-value)。

    7.最終目的
    最終目的就是在資源包中找到屬性文件,然后在該文件中找到key為"CLI.VersionMismatch"的值,用參數(shù)args內(nèi)容通過(guò)MessageFormat.format替換掉值里面的占位符。

    綜上分析,Message類(lèi)的

    return new Localizable(holder, "CLI.VersionMismatch", new Object[0]);

    返回的即是包含上面toString需要的三個(gè)參數(shù)的Localizable對(duì)象,當(dāng)在Message類(lèi)的更外部調(diào)用的時(shí)候,會(huì)讓這個(gè)對(duì)象toString輸出,而

    public String toString() {return toString(LocaleProvider.getLocale());}

    所以就調(diào)用回了toString(Locale locale)方法,最終實(shí)現(xiàn)了我們剛才說(shuō)的最終目的。

    而LocaleProvider.getLocale()就是一個(gè)緩存存儲(chǔ)的就是當(dāng)前本地化數(shù)據(jù),如語(yǔ)言、國(guó)家地區(qū)等。

    public static final LocaleProvider DEFAULT = new LocaleProvider() {public Locale get() {return Locale.getDefault();}};

    一步步跟進(jìn)去到j(luò)dk的Locale類(lèi)中,找到對(duì)應(yīng)方法:

    private static Locale initDefault() {String language, region, script, country, variant;language = AccessController.doPrivileged(new GetPropertyAction("user.language", "en"));// for compatibility, check for old user.region propertyregion = AccessController.doPrivileged(new GetPropertyAction("user.region"));if (region != null) {// region can be of form country, country_variant, or _variantint i = region.indexOf('_');if (i >= 0) {country = region.substring(0, i);variant = region.substring(i + 1);} else {country = region;variant = "";}script = "";} else {script = AccessController.doPrivileged(new GetPropertyAction("user.script", ""));country = AccessController.doPrivileged(new GetPropertyAction("user.country", ""));variant = AccessController.doPrivileged(new GetPropertyAction("user.variant", ""));}return getInstance(language, script, country, variant, null);}

    以上的方法通過(guò)本地方法(native method)獲取機(jī)器的語(yǔ)言,國(guó)家地區(qū)等信息。

    提供者模式

    首先展示一下上面localizer的類(lèi)圖,localizer就使用到了提供者模式,因?yàn)槲覀兛吹搅薒ocaleProvider,我們通過(guò)它的類(lèi)圖來(lái)研究和學(xué)習(xí)提供者模式。

    以上LocaleProvider的DEFAULT屬性為L(zhǎng)ocaleProvider本身的匿名內(nèi)部類(lèi),這里可以再次重申

    繼承了抽象類(lèi)的類(lèi)必須實(shí)現(xiàn)其抽象方法。

    提供者模式并非一個(gè)全新的主意,它主要從流行的策略模式發(fā)展而來(lái)。快速瀏覽下策略模式是個(gè)不錯(cuò)的想法。

    提供者模式是由.net2.0提出的,雖然語(yǔ)言與java不同,但是設(shè)計(jì)模式是跨語(yǔ)言的。有了提供者模式,很多時(shí)候可以用它來(lái)代替策略模式,他們的角色也是非常類(lèi)似的。

    • 角色
      • provider類(lèi),用于統(tǒng)籌管理具體provider對(duì)象,是一個(gè)抽象類(lèi)
      • XXXProvider類(lèi),繼承自Provider,是具體的provide類(lèi),有自己的方法實(shí)現(xiàn)

    通過(guò)與策略模式對(duì)比,我們可以發(fā)現(xiàn)LocaleProvider很神奇,有點(diǎn)與策略模式相類(lèi)似但又不太一樣,下面具體分析,

  • LocaleProvider的setProvider(LocaleProvider p)方法與策略模式中的Context類(lèi)的setStrategy(Strategy strategy)基本思想是一致的。
  • 同時(shí),LocaleProvider又可以成為策略模式中的Strategy類(lèi),
  • 他們本身都是抽象類(lèi)
  • 都定義了一個(gè)沖抽象方法,策略模式中的是abstract void algorithm(),LocaleProvider定義的是abstract Locale get()
  • 他們都有自己的實(shí)現(xiàn)類(lèi),策略模式繼承與Strategy類(lèi)的有BSTAdapter和RedBlackBSTAdapter,LocaleProvider雖然沒(méi)有直接的子類(lèi),但是它內(nèi)部定義的DEFAULT是一個(gè)繼承了LocaleProvider本身的匿名內(nèi)部類(lèi),它實(shí)現(xiàn)了那個(gè)抽象方法Locale get(),且返回的是Locale.getDefault()。(其實(shí)setProvider方法也是注入了一個(gè)繼承于LocaleProvider的實(shí)現(xiàn)了Locale get方法的類(lèi),可以是匿名內(nèi)部類(lèi),會(huì)更加方便。)
  • 所以結(jié)論是什么?LocaleProvider類(lèi)將策略模式中的Context類(lèi)和Strategy類(lèi)合并了起來(lái)。最終所有的模式其實(shí)都匯聚到這一個(gè)類(lèi)中,然而這并非不符合“單一指責(zé)原則”,因?yàn)長(zhǎng)ocaleProvider類(lèi)的職責(zé)自始至終都是一個(gè),那就是決定Locale對(duì)象。

    以后如果遇到這種情況,我們也可以使用這種模式創(chuàng)建我們的Provider,決定(服務(wù)?)某個(gè)類(lèi)的對(duì)象。

    Message.java

    Message.java是整個(gè)localizer包的出口。它的功能是:

  • 允許用戶(hù)創(chuàng)建一系列按照某名字KeyName附加國(guó)家地區(qū)的兩位字母的國(guó)際化文件(例如KeyName_zh, KeyName_en)。
  • 創(chuàng)建一個(gè)名字為KeyName的類(lèi),包含一個(gè)ResourceBundleHolder的成員對(duì)象,該對(duì)象是傳入了KeyName類(lèi)獲得的資源包持有器。
  • KeyName類(lèi)創(chuàng)建了一系列方便客戶(hù)端調(diào)用屬性文件中的數(shù)據(jù)的方法,一般是以屬性文件中內(nèi)容的key為方法名,傳入的是替換屬性文件中該key的值的占位符。
  • 方法實(shí)現(xiàn)分為兩種:
  • holder.format("屬性文件中key", new Object[] { arg0 });
  • new Localizable(holder, "屬性文件中key", new Object[] { arg0 });
  • 以上兩種方式的內(nèi)部實(shí)現(xiàn)分別為

    MessageFormat.format(get(LocaleProvider.getLocale()).getString(key),args);

  • MessageFormat.format(holder.get(locale).getString(key),(Object[])args);

    這兩種實(shí)現(xiàn)基本是一致,只是調(diào)用方式稍有不同,他們均可以實(shí)現(xiàn)按照語(yǔ)言本地化的方式調(diào)用字符串,并用參數(shù)替換占位符,格式化該字符串的功能。

    但是我們發(fā)現(xiàn)有意思的是,如果你是第一次下載下來(lái)localizer的源碼,會(huì)發(fā)現(xiàn)并不存在這個(gè)Message.java的文件。經(jīng)過(guò)分析才知道,該類(lèi)文件是通過(guò)Maven的插件自動(dòng)創(chuàng)建的。

    maven-localizer-plugin

    這個(gè)Maven插件也屬于localizer包的一部分,它的功能就一個(gè):自動(dòng)創(chuàng)建上面提到的那個(gè)Message.java類(lèi)文件。

    首先先來(lái)思考,這個(gè)Message.java的類(lèi)文件(也就是上面提及的KeyName類(lèi))如此重要,是外部調(diào)用的接口,為什么要自動(dòng)生成?

    原因很簡(jiǎn)單,因?yàn)樘闊N覀兌x屬性文件的時(shí)候,基本已經(jīng)把所有的數(shù)據(jù)按照key-value的形式寫(xiě)入,同時(shí)又創(chuàng)建了多個(gè)相同結(jié)構(gòu),不同翻譯版本的value的地區(qū)語(yǔ)言屬性文件。Message類(lèi)文件需要按照屬性文件內(nèi)部的key來(lái)生成對(duì)應(yīng)的方法,這個(gè)過(guò)程就是復(fù)制粘貼還容易出錯(cuò)的工作量很大的枯燥的工程,因此,通過(guò)插件去讀取這些屬性文件然后自動(dòng)生成是比較好的選擇。

    下面針對(duì)Maven如何創(chuàng)建一個(gè)插件來(lái)另開(kāi)一個(gè)章節(jié)仔細(xì)介紹。

    Maven插件

    Maven本身只是提供了一個(gè)執(zhí)行環(huán)境,所有的具體操作包括打包、單元測(cè)試、代碼檢查、版本規(guī)則等等都是通過(guò)Maven插件完成的。為了讓Maven完成不同的任務(wù),我們要為它配置各種插件。

    archetype:generate:從archetype創(chuàng)建一個(gè)Maven項(xiàng)目

    首先Archetype也是Maven的一個(gè)插件。

    創(chuàng)建一個(gè)新的Maven工程,我們需要用到Maven的archetype,archetype是一個(gè)模板工具包,定義了一類(lèi)項(xiàng)目的基本架構(gòu)。

    • Maven通過(guò)不同的archetype為程序員提供了創(chuàng)建Maven項(xiàng)目的模板,同時(shí)也可以根據(jù)已有的Maven項(xiàng)目生成自己團(tuán)隊(duì)使用的參數(shù)化模板。
    • 通過(guò)archetype,開(kāi)發(fā)人員可以很方便的復(fù)用一類(lèi)項(xiàng)目的最佳實(shí)現(xiàn)架構(gòu)到自己的項(xiàng)目中去。
    • 在一個(gè)Maven項(xiàng)目中,開(kāi)發(fā)人員可以通過(guò)archetype提供的范例快速入門(mén)并了解該項(xiàng)目的結(jié)構(gòu)與特點(diǎn)。

    Maven的Archetype包含:

    • maven-archetype-plugin: Archetype插件。通過(guò)該插件,開(kāi)發(fā)者可以在Maven中使用Archetype。它主要有兩個(gè)goal:
      • archetype:generate:從archetype 中創(chuàng)建一個(gè)Maven項(xiàng)目。
      • archetype:create-from-project:從已有的項(xiàng)目中生成archetype。
    • archetype-packaging:用于描述Archetype的生命周期與構(gòu)建項(xiàng)目軟件包。
    • archetype-models:用于描述類(lèi)和引用。
    • archetype-common:核心類(lèi)
    • archetype-test:用于測(cè)試Maven archetype的內(nèi)部組件。

    下面利用Archetype具體創(chuàng)建一個(gè)Maven項(xiàng)目,這里使用命令行的方式,IDE只是集成了這些功能,最終仍舊是轉(zhuǎn)化成命令行的方式,所以理解了命令行操作,IDE的操作也就直接掌握了。

    • 執(zhí)行mvn archetype:generate,終端會(huì)顯示開(kāi)始下載很多archetype,最終穩(wěn)定在一個(gè)讓你輸入一個(gè)編號(hào)的界面。這個(gè)編號(hào)有個(gè)默認(rèn)的1082,對(duì)應(yīng)的是maven archetype quickstart。如果直接回車(chē)則默認(rèn)選擇該quickstart的archetype為你構(gòu)建一個(gè)Maven項(xiàng)目。回車(chē)以后會(huì)讓你選擇一個(gè)quickstart的版本,默認(rèn)是最近穩(wěn)定版。繼續(xù)回車(chē)會(huì)讓你默認(rèn)輸入
    Define value for property 'groupId': Define value for property 'artifactId': Define value for property 'version' 1.0-SNAPSHOT: : Define value for property 'package' : :
    • 按照上面傻瓜式的輸入,就創(chuàng)建了一個(gè)完整的Maven工程,我們將其導(dǎo)入eclipse,然后觀(guān)察它的目錄結(jié)構(gòu)。可以發(fā)現(xiàn)src/main/java和src/test/java已經(jīng)成為了source folder,其中也包含例子程序,并且該項(xiàng)目也引用了jdk,mavne默認(rèn)加了一個(gè)junit的依賴(lài)。

    • pom.xml
      最后主要內(nèi)容為查看該項(xiàng)目的pom文件。

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.evsward</groupId><artifactId>testforOne</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><name>testforOne</name><url>http://maven.apache.org</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency></dependencies> </project>

    非常簡(jiǎn)潔,只有一個(gè)junit的依賴(lài),其他的都是常見(jiàn)的屬性信息字段。那么問(wèn)題是剛剛講過(guò)的Maven的其他強(qiáng)大的功能所依賴(lài)的那些插件在哪里定義的呢?
    實(shí)際上,我們項(xiàng)目中的pom文件是繼承于一個(gè)Super Pom,我們?cè)谠擁?xiàng)目目錄下的終端里輸入

    mvn help:effective-pom

    就會(huì)展示一個(gè)完整的包含其super pom內(nèi)容的pom文件,完整的pom文件太長(zhǎng)了,就不展示在這里了,核心思想就是我們項(xiàng)目中的pom文件是繼承一個(gè)super pom的,所以項(xiàng)目?jī)?nèi)的pom可以?xún)H關(guān)注于本業(yè)務(wù)的依賴(lài)定義即可,Maven默認(rèn)的功能插件支持在super pom中都會(huì)默認(rèn)幫你配置好。

    archetype:create-from-project:從已有的項(xiàng)目中生成archetype

    在上面通過(guò)archetype生成了Maven工程以后,我們對(duì)其進(jìn)行一個(gè)針對(duì)我們組內(nèi)開(kāi)發(fā)需求,加入依賴(lài)包,創(chuàng)建示例程序等,抽象出來(lái)一個(gè)我們自己的maven項(xiàng)目構(gòu)建模板。然后在項(xiàng)目根目錄終端在中輸入:

    mvn archetype:create-from-project

    執(zhí)行完以上命令以后,就可以在target/generated-sources/archetype目錄下生成一個(gè)archetype目錄,進(jìn)去這個(gè)目錄,然后mvn install就可以將該archetype安裝到本地倉(cāng)庫(kù),如果要共享到組內(nèi),則可以使用mvn deploy安裝到nexus等公共倉(cāng)庫(kù)。非常方便。

    創(chuàng)建一個(gè)自己的maven插件

    學(xué)習(xí)了以上maven archetype的知識(shí),我們要通過(guò)archetype創(chuàng)建一個(gè)自定義的maven插件開(kāi)發(fā)工程,archetype選擇maven-archetype-mojo。然后按照上面講過(guò)的內(nèi)容將該Maven工程創(chuàng)建成功。然后我們來(lái)觀(guān)察這個(gè)項(xiàng)目的結(jié)構(gòu)和內(nèi)容,

    • pom.xml文件中的packaging字段的值為maven-plugin,這與我們其他的maven項(xiàng)目不同,其他的項(xiàng)目可能是jar,war,hpi(Jenkins插件安裝包)等。
    • 示例程序中,我們發(fā)現(xiàn)了一個(gè)Mojo結(jié)尾的類(lèi),這里我們可以轉(zhuǎn)到 maven-localizer-plugin,可以看到GeneratorMojo,它繼承自org.apache.maven.plugin.AbstractMojo。它的類(lèi)注解有兩個(gè)新東西:
  • @goal generate 每個(gè)maven插件都對(duì)應(yīng)著一個(gè)goal,這個(gè)goal會(huì)在使用該插件的項(xiàng)目的pom中定義,我們?nèi)enkins-CLI的pom文件中查找。
  • <plugin><groupId>org.jvnet.localizer</groupId><artifactId>maven-localizer-plugin</artifactId><version>1.9</version><executions><execution><goals><goal>generate</goal></goals><configuration><fileMask>Messages.properties</fileMask><outputDirectory>target/generated-sources/localizer</outputDirectory></configuration></execution></executions></plugin>

    可以發(fā)現(xiàn)goal字段的generate對(duì)應(yīng)的就是GeneratorMojo的注解@goal generate,這是為查找插件使用的。

  • @phase generate-sources
    這個(gè)注解定義了插件在Maven的哪一個(gè)生命周期中運(yùn)行。Maven構(gòu)建的生命周期,以下通過(guò)一個(gè)表格來(lái)展示。
  • 生命周期階段描述
    validate檢查工程配置是否正確,完成構(gòu)建過(guò)程的所有必要信息是否能夠獲取到。
    initialize初始化構(gòu)建狀態(tài),例如設(shè)置屬性。
    generate-sources生成編譯階段需要包含的任何源碼文件。
    process-sources處理源代碼,例如,過(guò)濾任何值(filter any value)。
    generate-resources生成工程包中需要包含的資源文件。
    process-resources拷貝和處理資源文件到目的目錄中,為打包階段做準(zhǔn)備。
    compile編譯工程源碼。
    process-classes處理編譯生成的文件,例如 Java Class 字節(jié)碼的加強(qiáng)和優(yōu)化。
    generate-test-sources生成編譯階段需要包含的任何測(cè)試源代碼。
    process-test-sources處理測(cè)試源代碼,例如,過(guò)濾任何值(filter any values)。
    test-compile編譯測(cè)試源代碼到測(cè)試目的目錄。
    process-test-classes處理測(cè)試代碼文件編譯后生成的文件。
    test使用適當(dāng)?shù)膯卧獪y(cè)試框架(例如JUnit)運(yùn)行測(cè)試。
    prepare-package在真正打包之前,為準(zhǔn)備打包執(zhí)行任何必要的操作。
    package獲取編譯后的代碼,并按照可發(fā)布的格式進(jìn)行打包,例如 JAR、WAR 或者 EAR 文件。
    pre-integration-test在集成測(cè)試執(zhí)行之前,執(zhí)行所需的操作。例如,設(shè)置所需的環(huán)境變量。
    integration-test處理和部署必須的工程包到集成測(cè)試能夠運(yùn)行的環(huán)境中。
    post-integration-test在集成測(cè)試被執(zhí)行后執(zhí)行必要的操作。例如,清理環(huán)境。
    verify運(yùn)行檢查操作來(lái)驗(yàn)證工程包是有效的,并滿(mǎn)足質(zhì)量要求。
    install安裝工程包到本地倉(cāng)庫(kù)中,該倉(cāng)庫(kù)可以作為本地其他工程的依賴(lài)。
    deploy拷貝最終的工程包到遠(yuǎn)程倉(cāng)庫(kù)中,以共享給其他開(kāi)發(fā)人員和工程。

    所以該注解定義了maven-localizer-plugin插件的執(zhí)行時(shí)間是在generate-sources階段,也就是在生成工程包中需要包含的資源文件的階段,會(huì)將Message.java生成。

    • MavenProject屬性
    /*** The maven project.** @parameter expression="${project}"* @required* @readonly*/protected MavenProject project;

    GeneratorMojo類(lèi)包含一個(gè)MavenProject的對(duì)象屬性,該屬性并未賦值,它可以在插件運(yùn)行時(shí)通過(guò)@parameter expression="${project}"將maven項(xiàng)目注入(Maven自己的IoC容器Plexus)到該屬性對(duì)象中去。在使用MavenProject類(lèi)時(shí),要在pom中加入依賴(lài)

    <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-project</artifactId> <version>2.2.1</version> </dependency>

    即可使用該類(lèi)。

    • execute方法
      繼續(xù)研究GeneratorMojo類(lèi),它實(shí)現(xiàn)了AbstractMojo類(lèi)以后,就會(huì)默認(rèn)必須實(shí)現(xiàn)一個(gè)execute方法。這個(gè)方法就是該插件功能的核心實(shí)現(xiàn)。回到我們的Maven插件開(kāi)發(fā)項(xiàng)目中去,簡(jiǎn)單編寫(xiě)execute的內(nèi)容,最終我們的測(cè)試Mojo類(lèi)的完整內(nèi)容如下:
    package com.evsward.test_maven_plugin;+import org.apache.maven.model.Build;../*** * @author Evsward* @goal evswardtest* @phase pre-integration-test*/ public class EvswardTestMojo extends AbstractMojo {/*** @parameter expression="${project}"* @readonly*/private MavenProject project;public void execute() throws MojoExecutionException, MojoFailureException {Build build = project.getBuild();getLog().info("\n=========test here=================\n");getLog().info("build: " + build.getDefaultGoal() + build.getDirectory() + build.getFinalName());getLog().info("=======================");}}

    然后對(duì)整個(gè)Maven項(xiàng)目執(zhí)行mvn clean install
    build success以后執(zhí)行mvn com.evsward:test-maven-plugin:0.0.1-SNAPSHOT:evswardtest
    輸出內(nèi)容如下:

    [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building test-maven-plugin Maven Plugin 0.0.1-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- test-maven-plugin:0.0.1-SNAPSHOT:evswardtest (default-cli) @ test-maven-plugin --- [INFO] =========test here=================[INFO] build: nullE:\Evsward\git\test-maven-plugin\targettest-maven-plugin-0.0.1-SNAPSHOT [INFO] ======================= [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 0.368 s [INFO] Finished at: 2017-11-25T13:04:49+08:00 [INFO] Final Memory: 8M/245M [INFO] ------------------------------------------------------------------------
    • 改善插件goal的命令

      mvn com.evsward:test-maven-plugin:0.0.1-SNAPSHOT:evswardtest

    這個(gè)命令實(shí)在是太長(zhǎng)很麻煩,不像我們之前執(zhí)行的mvn install等,因此我們要針對(duì)我們的命令進(jìn)行改善,這就需要使用別名的方式代替冗長(zhǎng)的命令,有兩點(diǎn)要求:

    • 插件工程的命名規(guī)則必須是xxx-maven-plugin或者maven-xxx-plugin,我們的工程是test-maven-plugin,已經(jīng)滿(mǎn)足了這個(gè)命名規(guī)則。(經(jīng)過(guò)嘗試這一條并不應(yīng)驗(yàn))
    • Maven默認(rèn)搜索插件只會(huì)在org.apache.maven.plugins和org.codehaus.mojo兩個(gè)groupId下搜索,我們要讓它也來(lái)搜索我們自己的groupId,就要在Maven的setting.xml中加入
    <pluginGroups><!-- pluginGroup| Specifies a further group identifier to use for plugin lookup.<pluginGroup>com.your.plugins</pluginGroup>--><pluginGroup>com.evsward</pluginGroup> </pluginGroups>

    所以最終命令執(zhí)行

    mvn test-maven-plugin:evswardtest

    即可。

    GeneratorMojo的execute方法

    GeneratorMojo除了注入project屬性以外,還通過(guò)@parameter注入了outputDirectory,fileMask,outputEncoding,keyPattern,generatorClass,strictTypes,accessModifierAnnotations,他們分別都是maven build過(guò)程中的一些屬性?xún)?nèi)容。

    // packaging 方式為pom的跳過(guò) String pkg = project.getPackaging(); if(pkg!=null && pkg.equals("pom"))return;

    execute方法首先要確認(rèn)packaging方式,如果是pom方式則不處理。
    下面則是一系列java io相關(guān)的文件寫(xiě)入工作,文件過(guò)濾器FileFilter可以搜索屬性文件或結(jié)尾包含"_xx"的文件,將他們通過(guò)一系列處理最終調(diào)用ClassGenerator的build方法完成寫(xiě)入工作。

    TODO: java io 方面具體的深入研究請(qǐng)關(guān)注我即將發(fā)布的文章。

    下面是maven-localizer-plugin插件中涉及類(lèi)生成工作的類(lèi)圖。

    本文總結(jié)

    通過(guò)本文的研究,我們深入學(xué)習(xí)了:

    • Maven的配置使用,模板架構(gòu),工程創(chuàng)建,插件開(kāi)發(fā),部署等高級(jí)使用方法。這部分源碼地址在test-maven-plugin
    • Jenkins源碼中所有涉及屬性文件的操作工具localizer以及其開(kāi)發(fā)的maven-localizer-plugin插件,并完全研究了localizer的源碼
    • 通過(guò)研究localizer源碼,我們復(fù)習(xí)了設(shè)計(jì)模式中的策略模式,同時(shí)也學(xué)習(xí)了新型的提供者模式。
    • 最后也是本文的初衷,涉及Jenkins源碼部分,我們僅是完成了對(duì)其國(guó)際化工具的實(shí)現(xiàn),這對(duì)于整套源碼來(lái)講只是冰山一角,之后會(huì)隨著越加深入而展開(kāi)更多的Jenkins源碼研究課題。

    轉(zhuǎn)載于:https://www.cnblogs.com/Evsward/p/localizer.html

    總結(jié)

    以上是生活随笔為你收集整理的结合提供者模式解析Jenkins源码国际化的实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。