使用Gradle構建Java Web應用
本文是發布在java.net上的一篇摘自于<Gradle in Action>一書中的節選,介紹了使用Gradle構建Java Web應用的過程。剛剛接觸Gradle,看到了這篇小文,隨手譯了出來:-) (2014.01.23最后更新)當今世界,一派繁忙。在職業生涯和私人生活中,我們中間的許多人要同時管理多個項目。你可能常常發現自己處于不知所措及失控的狀態。保持規整并專注于價值的關鍵是一個維護良好的工作清單。當然,你可能總是把你的任務寫在一張紙上,但是你也許不可能在你所處的任何地方都可方便地獲得這些工作條目?對互聯網的訪問幾乎是無處不在的,無論是通過你的移動電話,還是公共的網絡接入點。在<Gradle in Action>一書中,如圖1所示的說明性示例是一個很有吸引力的可視化Web應用。

圖1 To Do應用可以通過互聯網進行訪問,并使用它去管理數據存儲中的工作條目
Gradle插件表現的如同一個使能器,它會自動地執行這些任務。一個插件通過引入特定領域的規范以及對缺省值敏感的任務去對工程進行擴展。隨Gradle發布的插件之一就是Java插件。該Java插件絕不僅僅是提供了源碼編譯和打包這樣的基礎功能。它為工程建立了一整套標準的目錄布局,它會確保以正確的順序去執行任務,這樣,這些任務在Java工程環境中才是有意義的。現在是時候為我們的應用去創建一個構建腳本并去使用這個Java插件了。
構建Java應用
一開始,每個Gradle工程都會創建一個名為build.gradle的構建腳本。為了創建該腳本,并告訴該工程使用Java插件,應該像這樣去做:
apply plugin: 'java'
為了構建你的Java代碼,一行代碼就夠了。但Gradle怎么知道去哪兒找你的源文件呢?該Java插件引入的規范之一就是源代碼的路徑。默認地,該插件會到目錄src/main/java中搜尋產品的源代碼。構建Web應用
通過War插件,Gradle也提供了構建Web應用的擴展支持。War插件擴展了Java插件,它加入了針對Web應用程序開發的規范,并支持歸集WAR文件。讓我們也在這個工程中用用War插件:
apply plugin: 'war'
Web應用源文件的默認路徑是src/main/webapp。假設你已經明確了該應用所必要的Java類。那么要使產品的全部源代碼和Web資源文件處于正確路徑下,該工程的目錄布局應該像下面這樣:.
├── 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源文件默認目錄
#B Web應用描述符文件
#C 存儲描述如何展現HTML元素的樣式單文件的目錄
#D 存放JSP形式的動態腳本化視圖組件
├── 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源文件默認目錄
#B Web應用描述符文件
#C 存儲描述如何展現HTML元素的樣式單文件的目錄
#D 存放JSP形式的動態腳本化視圖組件
聲明外部依賴
在實現這個Web應用的過程,我們使用的一些類,例如javax.servlet.HttpServlet,并非Java標準版(Java SE)的一部分。在構建工程之前,我們需要確保已經聲明了這些外部依賴。在Java系統中,依賴類庫是以JAR文件的形式去發布和使用的。許多類庫可以從倉庫,如一個文件系統或中央服務器,中獲得。為了使用依賴,Gradle要求你至少定義一個倉庫。出于一些考慮,我們將使用公共的可通過互聯網進行訪問的Maven Central倉庫。
repositories {
mavenCentral() #A
}
#A 通過http://repo1.maven.org/maven2訪問Maven2中央倉庫的簡短標記
在Gradle中,依賴是通過配置項來進行分組的。我們將來Servlet依賴使用的配置項是providedCompile。該配置項用于那些在編譯時而非運行時所需的依賴。像JSTL這樣的運行時依賴,在編譯時不會被用到,但在運行時則會被用到。它們都會成為WAR文件的一部分。下面的配置語句塊聲明了我們應用所需的外部類庫:mavenCentral() #A
}
#A 通過http://repo1.maven.org/maven2訪問Maven2中央倉庫的簡短標記
dependencies {
providedCompile 'javax.servlet:servlet-api:2.5'
runtime 'javax.servlet:jstl:1.1.2'
}
providedCompile 'javax.servlet:servlet-api:2.5'
runtime 'javax.servlet:jstl:1.1.2'
}
構建工程
我們已經準備好構建這個工程了。另到工程中的一個Java插件任務名為build。該任務將編譯源代碼,運行測試程序并歸集WAR文件--所有的這些任務都將以正確的順序被執行。執行命令gradle build之后,你可能會得到形如下面的輸出:
$ 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 編譯產品的Java源代碼
#B War插件提供的任務,用于歸集WAR文件
#C 編譯Java測試源代碼
#D 運行單元測試
: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 編譯產品的Java源代碼
#B War插件提供的任務,用于歸集WAR文件
#C 編譯Java測試源代碼
#D 運行單元測試
上述輸出的每一行都代表執行了一個由Java或War插件提供的任務。你可能會注意到,有一些任務被標記為UP-TO-DATE。它的意思是指該任務被跳過去了。Gradle的增量構建支持策略會自動識別不需要執行的工作。特別是在大型商業項目中,該特性會極大地節省時間。
在該工程的根節目錄中,你將會發現一個名為build的子目錄,它包含有執行構建之后的全部輸出,包括類文件,測試報告,歸集的WAR文件,以及像manifest這樣的在打包時需要的臨時文件。如下就是執行構建工作之后的工程目錄結構:
.
├── 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類文件的默認目錄
#B 歸集的WAR文件
#C 用于WAR的臨時manifest文件
你已經知道如何從一個基于標準目錄結構的Web工程去構建WAR文件。現在是時候將它布署到一個Servlet容器中去了。在下一節中,我們將在本地開發機器中啟動Jetty去運行這個Web應用。├── 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類文件的默認目錄
#B 歸集的WAR文件
#C 用于WAR的臨時manifest文件
運行應用
在本地機器中運行一個Web應用應該很容易,能夠實踐快速應用開發(RAD),并能夠提供快速的啟動時間。最棒地是,它不要求你部署一個Web容器運行時環境。Jetty一個流行的輕量級開源Web容器,它支持前面提到的所有特性。在這個Web應用中加入一個HTTP模塊,它就變成了一個嵌入式實現。Gradle的Jetty插件擴展了War插件,它提供的任務可以將一個Web應用部署到嵌入式容器中,并能夠啟動該應用。在你的構建腳本中,可以像如下那樣使用這個插件:
apply plugin: 'jetty'
這個將被我們用于啟動Web應用的任務名為jettyRun。它甚至可以在無需創建WAR文件的情況下啟動一個Jetty容器。執行上述命令后會得到如下形式的輸出:$ gradle jettyRun
:compileJava
:processResources UP-TO-DATE
:classes
> Building > :jettyRun > Running at http://localhost:8080/todo-webapp-jetty
在上述輸出的最后一行中,該插件告訴了你Jetty即將偵聽的請求地址。打開一個你喜歡的瀏覽器,并輸入上述地址。最后,我們會看到這個To Do Web應用的行為。圖2展示在一個瀏覽器中查看到該應用界面的截屏。:compileJava
:processResources UP-TO-DATE
:classes
> Building > :jettyRun > Running at http://localhost:8080/todo-webapp-jetty

圖2 To Do應用的Web界面及其行為
在你通過組合鍵CTRL+C去停止這個應用之前,Gradle會讓它一直運行。Jetty如何知道使用哪個端口和上下文環境去運行這個Web應用?再說一遍,這就是規范。Jetty運行Web應用所使用的默認端口就是8080。
總結
只需要較少的努力,你就可以使用Gradle去構建并運行一個Java Web應用。只要你嚴格遵循標準目錄結構,那么你的構建腳本僅需要兩行代碼。