使用Gradle構(gòu)建Java Web應(yīng)用
本文是發(fā)布在java.net上的一篇摘自于<Gradle in Action>一書(shū)中的節(jié)選,介紹了使用Gradle構(gòu)建Java Web應(yīng)用的過(guò)程。剛剛接觸Gradle,看到了這篇小文,隨手譯了出來(lái):-) (2014.01.23最后更新)
當(dāng)今世界,一派繁忙。在職業(yè)生涯和私人生活中,我們中間的許多人要同時(shí)管理多個(gè)項(xiàng)目。你可能常常發(fā)現(xiàn)自己處于不知所措及失控的狀態(tài)。保持規(guī)整并專(zhuān)注于價(jià)值的關(guān)鍵是一個(gè)維護(hù)良好的工作清單。當(dāng)然,你可能總是把你的任務(wù)寫(xiě)在一張紙上,但是你也許不可能在你所處的任何地方都可方便地獲得這些工作條目?對(duì)互聯(lián)網(wǎng)的訪問(wèn)幾乎是無(wú)處不在的,無(wú)論是通過(guò)你的移動(dòng)電話(huà),還是公共的網(wǎng)絡(luò)接入點(diǎn)。在<Gradle in Action>一書(shū)中,如圖1所示的說(shuō)明性示例是一個(gè)很有吸引力的可視化Web應(yīng)用。
圖1 To Do應(yīng)用可以通過(guò)互聯(lián)網(wǎng)進(jìn)行訪問(wèn),并使用它去管理數(shù)據(jù)存儲(chǔ)中的工作條目
Gradle插件表現(xiàn)的如同一個(gè)使能器,它會(huì)自動(dòng)地執(zhí)行這些任務(wù)。一個(gè)插件通過(guò)引入特定領(lǐng)域的規(guī)范以及對(duì)缺省值敏感的任務(wù)去對(duì)工程進(jìn)行擴(kuò)展。隨Gradle發(fā)布的插件之一就是Java插件。該Java插件絕不僅僅是提供了源碼編譯和打包這樣的基礎(chǔ)功能。它為工程建立了一整套標(biāo)準(zhǔn)的目錄布局,它會(huì)確保以正確的順序去執(zhí)行任務(wù),這樣,這些任務(wù)在Java工程環(huán)境中才是有意義的。現(xiàn)在是時(shí)候?yàn)槲覀兊膽?yīng)用去創(chuàng)建一個(gè)構(gòu)建腳本并去使用這個(gè)Java插件了。
構(gòu)建Java應(yīng)用
一開(kāi)始,每個(gè)Gradle工程都會(huì)創(chuàng)建一個(gè)名為build.gradle的構(gòu)建腳本。為了創(chuàng)建該腳本,并告訴該工程使用Java插件,應(yīng)該像這樣去做:
apply plugin: 'java'
為了構(gòu)建你的Java代碼,一行代碼就夠了。但Gradle怎么知道去哪兒找你的源文件呢?該Java插件引入的規(guī)范之一就是源代碼的路徑。默認(rèn)地,該插件會(huì)到目錄src/main/java中搜尋產(chǎn)品的源代碼。
構(gòu)建Web應(yīng)用
通過(guò)War插件,Gradle也提供了構(gòu)建Web應(yīng)用的擴(kuò)展支持。War插件擴(kuò)展了Java插件,它加入了針對(duì)Web應(yīng)用程序開(kāi)發(fā)的規(guī)范,并支持歸集WAR文件。讓我們也在這個(gè)工程中用用War插件:
apply plugin: 'war'
Web應(yīng)用源文件的默認(rèn)路徑是src/main/webapp。假設(shè)你已經(jīng)明確了該應(yīng)用所必要的Java類(lèi)。那么要使產(chǎn)品的全部源代碼和Web資源文件處于正確路徑下,該工程的目錄布局應(yīng)該像下面這樣:
.
├── build.gradle
└── src
└── main
├── java
│ └── com
│ └── manning
│ └── gia
│ └── todo
│ ├── model
│ │ └── ToDoItem.java
│ ├── repository
│ │ ├── InMemoryToDoRepository.java
│ │ └── ToDoRepository.java
│ └── web
│ └── ToDoServlet.java
└── webapp #A
├── WEB-INF
│ └── web.xml #B
├── css #C
│ ├── base.css
│ └── bg.png
└── jsp #D
├── index.jsp
└── todo-list.jsp
#A Web源文件默認(rèn)目錄
#B Web應(yīng)用描述符文件
#C 存儲(chǔ)描述如何展現(xiàn)HTML元素的樣式單文件的目錄
#D 存放JSP形式的動(dòng)態(tài)腳本化視圖組件
聲明外部依賴(lài)
在實(shí)現(xiàn)這個(gè)Web應(yīng)用的過(guò)程,我們使用的一些類(lèi),例如javax.servlet.HttpServlet,并非Java標(biāo)準(zhǔn)版(Java SE)的一部分。在構(gòu)建工程之前,我們需要確保已經(jīng)聲明了這些外部依賴(lài)。在Java系統(tǒng)中,依賴(lài)類(lèi)庫(kù)是以JAR文件的形式去發(fā)布和使用的。許多類(lèi)庫(kù)可以從倉(cāng)庫(kù),如一個(gè)文件系統(tǒng)或中央服務(wù)器,中獲得。為了使用依賴(lài),Gradle要求你至少定義一個(gè)倉(cāng)庫(kù)。出于一些考慮,我們將使用公共的可通過(guò)互聯(lián)網(wǎng)進(jìn)行訪問(wèn)的Maven Central倉(cāng)庫(kù)。
repositories {
mavenCentral() #A
}
#A 通過(guò)http://repo1.maven.org/maven2訪問(wèn)Maven2中央倉(cāng)庫(kù)的簡(jiǎn)短標(biāo)記
在Gradle中,依賴(lài)是通過(guò)配置項(xiàng)來(lái)進(jìn)行分組的。我們將來(lái)Servlet依賴(lài)使用的配置項(xiàng)是providedCompile。該配置項(xiàng)用于那些在編譯時(shí)而非運(yùn)行時(shí)所需的依賴(lài)。像JSTL這樣的運(yùn)行時(shí)依賴(lài),在編譯時(shí)不會(huì)被用到,但在運(yùn)行時(shí)則會(huì)被用到。它們都會(huì)成為WAR文件的一部分。下面的配置語(yǔ)句塊聲明了我們應(yīng)用所需的外部類(lèi)庫(kù):
dependencies {
providedCompile 'javax.servlet:servlet-api:2.5'
runtime 'javax.servlet:jstl:1.1.2'
}
構(gòu)建工程
我們已經(jīng)準(zhǔn)備好構(gòu)建這個(gè)工程了。另到工程中的一個(gè)Java插件任務(wù)名為build。該任務(wù)將編譯源代碼,運(yùn)行測(cè)試程序并歸集WAR文件--所有的這些任務(wù)都將以正確的順序被執(zhí)行。執(zhí)行命令gradle build之后,你可能會(huì)得到形如下面的輸出:
$ gradle build
:compileJava #A
:processResources UP-TO-DATE
:classes
:war #B
:assemble
:compileTestJava UP-TO-DATE #C
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test #D
:check
:build
#A 編譯產(chǎn)品的Java源代碼
#B War插件提供的任務(wù),用于歸集WAR文件
#C 編譯Java測(cè)試源代碼
#D 運(yùn)行單元測(cè)試
上述輸出的每一行都代表執(zhí)行了一個(gè)由Java或War插件提供的任務(wù)。你可能會(huì)注意到,有一些任務(wù)被標(biāo)記為UP-TO-DATE。它的意思是指該任務(wù)被跳過(guò)去了。Gradle的增量構(gòu)建支持策略會(huì)自動(dòng)識(shí)別不需要執(zhí)行的工作。特別是在大型商業(yè)項(xiàng)目中,該特性會(huì)極大地節(jié)省時(shí)間。
在該工程的根節(jié)目錄中,你將會(huì)發(fā)現(xiàn)一個(gè)名為build的子目錄,它包含有執(zhí)行構(gòu)建之后的全部輸出,包括類(lèi)文件,測(cè)試報(bào)告,歸集的WAR文件,以及像manifest這樣的在打包時(shí)需要的臨時(shí)文件。如下就是執(zhí)行構(gòu)建工作之后的工程目錄結(jié)構(gòu):
.
├── build
│ ├── classes
│ │ └── main #A
│ │ └── com
│ │ └── manning
│ │ └── gia
│ │ └── todo
│ │ ├── model
│ │ │ └── ToDoItem.class
│ │ ├── repository
│ │ │ ├── InMemoryToDoRepository.class
│ │ │ └── ToDoRepository.class
│ │ └── web
│ │ ├── ToDoServlet$ToDoListStats.class
│ │ └── ToDoServlet.class
│ ├── dependency-cache
│ ├── libs
│ │ └── todo-webapp.war #B
│ ├── reports
│ │ └── tests
│ │ ├── base-style.css
│ │ ├── css3-pie-1.0beta3.htc
│ │ ├── index.html
│ │ ├── report.js
│ │ └── style.css
│ ├── test-results
│ │ └── binary
│ │ └── test
│ │ └── results.bin
│ └── tmp
│ └── war
│ └── MANIFEST.MF #C
├── build.gradle
└── src
#A 包含Java類(lèi)文件的默認(rèn)目錄
#B 歸集的WAR文件
#C 用于WAR的臨時(shí)manifest文件
你已經(jīng)知道如何從一個(gè)基于標(biāo)準(zhǔn)目錄結(jié)構(gòu)的Web工程去構(gòu)建WAR文件。現(xiàn)在是時(shí)候?qū)⑺际鸬揭粋€(gè)Servlet容器中去了。在下一節(jié)中,我們將在本地開(kāi)發(fā)機(jī)器中啟動(dòng)Jetty去運(yùn)行這個(gè)Web應(yīng)用。
運(yùn)行應(yīng)用
在本地機(jī)器中運(yùn)行一個(gè)Web應(yīng)用應(yīng)該很容易,能夠?qū)嵺`快速應(yīng)用開(kāi)發(fā)(RAD),并能夠提供快速的啟動(dòng)時(shí)間。最棒地是,它不要求你部署一個(gè)Web容器運(yùn)行時(shí)環(huán)境。Jetty一個(gè)流行的輕量級(jí)開(kāi)源Web容器,它支持前面提到的所有特性。在這個(gè)Web應(yīng)用中加入一個(gè)HTTP模塊,它就變成了一個(gè)嵌入式實(shí)現(xiàn)。Gradle的Jetty插件擴(kuò)展了War插件,它提供的任務(wù)可以將一個(gè)Web應(yīng)用部署到嵌入式容器中,并能夠啟動(dòng)該應(yīng)用。在你的構(gòu)建腳本中,可以像如下那樣使用這個(gè)插件:
apply plugin: 'jetty'
這個(gè)將被我們用于啟動(dòng)Web應(yīng)用的任務(wù)名為jettyRun。它甚至可以在無(wú)需創(chuàng)建WAR文件的情況下啟動(dòng)一個(gè)Jetty容器。執(zhí)行上述命令后會(huì)得到如下形式的輸出:
$ gradle jettyRun
:compileJava
:processResources UP-TO-DATE
:classes
> Building > :jettyRun > Running at http://localhost:8080/todo-webapp-jetty
在上述輸出的最后一行中,該插件告訴了你Jetty即將偵聽(tīng)的請(qǐng)求地址。打開(kāi)一個(gè)你喜歡的瀏覽器,并輸入上述地址。最后,我們會(huì)看到這個(gè)To Do Web應(yīng)用的行為。圖2展示在一個(gè)瀏覽器中查看到該應(yīng)用界面的截屏。
圖2 To Do應(yīng)用的Web界面及其行為
在你通過(guò)組合鍵CTRL+C去停止這個(gè)應(yīng)用之前,Gradle會(huì)讓它一直運(yùn)行。Jetty如何知道使用哪個(gè)端口和上下文環(huán)境去運(yùn)行這個(gè)Web應(yīng)用?再說(shuō)一遍,這就是規(guī)范。Jetty運(yùn)行Web應(yīng)用所使用的默認(rèn)端口就是8080。
總結(jié)
只需要較少的努力,你就可以使用Gradle去構(gòu)建并運(yùn)行一個(gè)Java Web應(yīng)用。只要你嚴(yán)格遵循標(biāo)準(zhǔn)目錄結(jié)構(gòu),那么你的構(gòu)建腳本僅需要兩行代碼。