【認識 Gradle】(7)Java 專案相依管理
【認識 Gradle】(6)Java 專案與 Build Script 客製化 << 前情 在 Maven 帶給開發者許多好的體驗,相依性管理是其中對一般開發者較具有吸引力的因素。Gradle 納入套件管理的功能,並且能使用 Maven Repository 作為套件下載來源。在 Gradle 管理套相的相依性,可以寫成這樣: apply plugin: 'java' /* 設定 maven repository server $ */ repositories { mavenCentral() } /* 宣告專案的相依函式庫 $ */ dependencies { compile group: 'commons-logging', name: 'commons-logging', version: '1.1.1' compile group: 'log4j', name: 'log4j', version: '1.2.16' } 使用 記得看過一則討論是使用 Maven 管理相依性問題是否實用嗎?讓專案直接與使用到的函式庫(JARs)包含開發環境的相依套件設定,通通送進版本控制系統。當有人需要重新建起開發環境時,只要由版本控制系統取回匯入 IDE 後就能使用。我必需承認這樣確實很方便,不過有幾個問題需要思考。 首先,版本控制系統較擅長處理純文字的檔案,對於函式庫這類非純文字檔的存儲效率不太好,無法以較省空間的方式儲存。隨著函式庫更換版本的次數越多,版本控制系統檔案庫虛胖得越快。若你的團隊可能有多個地理位置,只有靠近版本控制系統的人可以使用愉快。專案開發使用的函式庫檔案越大,由版本控制系統取出的時間越久。這雖然是理所當然的事,但曾待過這樣的團隊,版本控制系統的伺服務在遠方,只有與它同辦公室的同事覺得取出專案很快。若你們團隊都離版本控制系統伺服務很近,那麼這個『等待』的問題應該還算在能接受的範圍。 另一個問題是,在同一組開發團隊裡,使用的解決方案可能會很相近,使用的函式庫相互重複的情況相當明顯。相同的 Logging Tool、相同的 IoC Framework、相同的 Web Framework、相同的 ORM Framework 或公司累積的內部函式庫。可以試著算算公司內有多少相近的專案,並想一下 N 個專案,就有 N 組重複的函式庫在版本控制系統內,最好提醒管理版本控制系統伺服器端的同事,硬碟空間不能太小氣。若換成是使用『套件相依管理』的功能,那麼它的變動只是一個文字檔內的幾行字,對於版本控制系統的負擔很小。 除了上述的缺點之外,我們得回到相依管理的本質。它並不是在管理一個一個的 JAR 檔案是否要存在這個專案。它是在管理『一組』JARs 檔,它們一起出現是具有意義的,而那個意義才是『相依性』管理的核心。就像在專案中使用 Hibernate 或 Spring Framework 不會只有它們本身提供的 JARs 就足夠,還有相依的第三方函式庫。若單純以獨立的 JAR 檔的角度來管理專案需要用到的函式庫,那隨著專案的發展有些功能已不再使用,年代久遠之後,也沒有人會記得哪一個 JAR 檔是為了什麼目的而加進來的,特別是這個 JAR 檔為了滿足函式庫自身相依性的情況。 相依關係我們使用 Help tasks ---------- dependencies - Displays all dependencies declared in root project 'gradleLab'. dependencyInsight - Displays the insight into a specific dependency in root project 'gradleLab'. help - Displays a help message projects - Displays the sub-projects of root project 'gradleLab'. properties - Displays the properties of root project 'gradleLab'. tasks - Displays the tasks runnable from root project 'gradleLab'. 使用 qty:gradleLab qrtt1$ gradle dep :dependencies ------------------------------------------------------------ Root project ------------------------------------------------------------ archives - Configuration for archive artifacts. No dependencies compile - Compile classpath for source set 'main'. +--- commons-logging:commons-logging:1.1.1 \--- log4j:log4j:1.2.16 default - Configuration for default artifacts. +--- commons-logging:commons-logging:1.1.1 \--- log4j:log4j:1.2.16 runtime - Runtime classpath for source set 'main'. +--- commons-logging:commons-logging:1.1.1 \--- log4j:log4j:1.2.16 testCompile - Compile classpath for source set 'test'. +--- commons-logging:commons-logging:1.1.1 \--- log4j:log4j:1.2.16 testRuntime - Runtime classpath for source set 'test'. +--- commons-logging:commons-logging:1.1.1 \--- log4j:log4j:1.2.16 BUILD SUCCESSFUL 目前專案看單純僅有二個相依套件設定,我們試引用其它稍為巨大一點的函式庫試試,在 com.amazonaws:aws-java-sdk:1.+ compile - Compile classpath for source set 'main'. \--- com.amazonaws:aws-java-sdk:1.+ -> 1.6.11 +--- commons-logging:commons-logging:1.1.1 +--- org.apache.httpcomponents:httpclient:4.2 | +--- org.apache.httpcomponents:httpcore:4.2 | +--- commons-logging:commons-logging:1.1.1 | \--- commons-codec:commons-codec:1.6 +--- commons-codec:commons-codec:1.3 -> 1.6 +--- com.fasterxml.jackson.core:jackson-core:2.1.1 +--- com.fasterxml.jackson.core:jackson-databind:2.1.1 | +--- com.fasterxml.jackson.core:jackson-annotations:2.1.1 | \--- com.fasterxml.jackson.core:jackson-core:2.1.1 +--- com.fasterxml.jackson.core:jackson-annotations:2.1.1 \--- joda-time:joda-time:[2.2,) -> 2.3 由相依關係可以看出,
在 org.apache.httpcomponents:httpclient:4.2 套件它又有自己相依的套件:
我們能觀察到 commons-codec:commons-codec:1.3 -> 1.6 因為原先指定的版本是 1.3,但其它相依套件指定了更新的版本,於是最終使用 1.6 版。 另一組是只留下 Windows Azure SDK for Java: compile 'com.microsoft.windowsazure:microsoft-windowsazure-api:0.+' compile - Compile classpath for source set 'main'. \--- com.microsoft.windowsazure:microsoft-windowsazure-api:0.+ -> 0.4.6 +--- com.sun.jersey:jersey-client:1.13 +--- javax.inject:javax.inject:1 +--- com.sun.jersey:jersey-json:1.13 | +--- org.codehaus.jettison:jettison:1.1 | | \--- stax:stax-api:1.0.1 | +--- com.sun.xml.bind:jaxb-impl:2.2.3-1 | | \--- javax.xml.bind:jaxb-api:2.2.2 | | +--- javax.xml.stream:stax-api:1.0-2 | | \--- javax.activation:activation:1.1 | +--- org.codehaus.jackson:jackson-core-asl:1.9.2 | +--- org.codehaus.jackson:jackson-mapper-asl:1.9.2 | | \--- org.codehaus.jackson:jackson-core-asl:1.9.2 | +--- org.codehaus.jackson:jackson-jaxrs:1.9.2 | | +--- org.codehaus.jackson:jackson-core-asl:1.9.2 | | \--- org.codehaus.jackson:jackson-mapper-asl:1.9.2 (*) | \--- org.codehaus.jackson:jackson-xc:1.9.2 | +--- org.codehaus.jackson:jackson-core-asl:1.9.2 | \--- org.codehaus.jackson:jackson-mapper-asl:1.9.2 (*) +--- commons-logging:commons-logging:1.1.1 +--- javax.mail:mail:1.4.5 | \--- javax.activation:activation:1.1 \--- org.apache.commons:commons-lang3:3.1 Windows Azure 相依關係就更多層,樹狀結構更深。在專案使用越多的第三方套件,那麼要明確瞭了哪一個 JAR 檔的來歷與功用,為何將它加入專案內就越加困難。並且使用相依性管理功能能指定一個可接受的版本號範圍,對 Windows Azure 指定的是 更新到足夠新的狀態有正反兩種意義:
在希望專案自動緊追著最新發佈版本的情況採用自動版本號,不希望它自動更新的情況下使用固定的版本號。以 AWS SDK for Java 為例,在公司的專案最初設定自動版號,但發現一旦 AWS 有新服務發表,或是一些我們不會用到的功能修好 bug 就會有新的版本。由於它變動得太過頻繁,重新下載相依檔案就會花一點時間,但我們又用不到它更新的部分,於是將它設為固定版號的。 公司內部發佈的套件相依設定皆為自動版號,因為不會無來由地發佈新版,肯定是修了 Bug 或要加新功能讓需要的專案使用。它仍然有改成固定版號的時機,例如:新版的需要測試後才能發佈,那麼現行的產品就應相依於特定版號,直到新版的確定要發佈且不會再有重大修改。另一個原因是規格變更前後版不相容,不過這情況就不建議改相依性的版號,而是新的版本要提昇主版號,表示為不相容。 相依關係設定理解相依管理的優點後,在開發中如何使用它呢?對於沒有 Maven 或 Ant Ivy 使用經驗的讀者應該會有第一個疑惑, compile group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.+' 或寫成: compile 'com.amazonaws:aws-java-sdk:1.+' 這套件資訊是怎麼來的呢?有幾個途經:
使用套件查詢網站時,keyword 通常是以專案名稱,或該專案使用的 package name 為主,例如查詢 找到目標後進去它的內頁: 選擇需要的版本後,依專案工具選擇適當的設定值。請點選 Gradle 頁籤: 在 Gradle 頁籤內的資訊能直接填寫至 dependencies 內,或將它改成動態版本的宣告。 套件伺服器當專案開始利用 Gradle 管理套件的便利設施後,隨著管理與使用的需求,架設套件伺服器是隨之而來的必要工作。原先我們針對套件伺服器的設定只有簡單地採用 Maven 公開伺服器,單純作為 Open Source 函式庫的使用者來說,它是方便的: repositories { mavenCentral() } 不過對於要開發私有程式的商業單位,多數的情況不會將自己公司的函式庫發佈至 Maven 公開伺服器。取而代之的是架設自有的套件伺服器,將公司內部的函式庫發佈在私有的伺服器上,後續專案只要設定好 repositories 就能使用,例如: repositories { maven { credentials { username 'username-for-your-server' password 'password-for-your-server' } url "http://url-to-your-repo-server" } } 有了私有伺服器,通常就不會額外宣告 我們能在網路上找到許多套件伺服器,以本系列常引用的 Nexus 來說,它就是其中一套。它提供 Open Source 版與商業版,如果沒有特殊的需求用 Open Source 版已經堪用。它是以 Java Solution 寫成的,安裝的方法可參閱其官方文件 Installing Nexus。它有二種安裝方式:一種是安裝 WAR 檔版本,就使用你原有的 Java Web Container 即可,或是用獨立執行的版本(它是使用 jetty)。 qty:nexus-2.7.1-01-bundle qrtt1$ ls -alh total 0 drwx------@ 4 qrtt1 staff 136B 1 18 12:37 . drwx------+ 32 qrtt1 staff 1.1K 1 18 12:30 .. drwxr-xr-x@ 10 qrtt1 staff 340B 1 10 11:05 nexus-2.7.1-01 drwxr-xr-x@ 5 qrtt1 staff 170B 1 18 12:30 sonatype-work 以獨立執行的版本為例,下載完解壓縮後會有二個目錄。nexus 開頭的目錄為程式的目錄,而 sonatype-work 是預設存放下載資料的目錄(可以透過設定變更,維護的工作主要也是備份這個目錄的資料)。對大多數管理者來說有額外設定的需求,例如:改管理者帳號、改 port 號。 針對 Web Container 部分的設定,可由程式目錄下的 qty:nexus-2.7.1-01-bundle qrtt1$ cat nexus-2.7.1-01/conf/nexus.properties # Sonatype Nexus # ============== # This is the most basic configuration of Nexus. # Jetty section application-port=8081 application-host=0.0.0.0 nexus-webapp=${bundleBasedir}/nexus nexus-webapp-context-path=/nexus # Nexus section nexus-work=${bundleBasedir}/../sonatype-work/nexus runtime=${bundleBasedir}/nexus/WEB-INF 其他部分需啟動程式後,由 Web Console 修改。像是他預設的管理者帳號:
登入 nexus 後,在 repositories 管理頁可以看到許多事先建立完成的 repository: 可以觀察到每一個 repository 都有一個 type:
點開 group repository 的設定,可以看出它走訪不同 repository 的順序: 使用自有套件完成自有 repository 架設後,將專案設定稍作修改即可將它發佈並在其他專案引用。在學習發佈之前,我們能先修改專案使用自己的 repository server,將原先的
apply plugin: 'java' apply plugin: 'maven' repositories { maven { credentials { username 'admin' password 'admin123' } url "http://127.0.0.1:8081/nexus/content/groups/public/" } } dependencies { compile group: 'commons-logging', name: 'commons-logging', version: '1.1.1' compile group: 'log4j', name: 'log4j', version: '1.2.16' } 完成修改後,專案就不再直接抓取 Maven 公開的 repository,而是透過 nexus 作為 proxy 進行核對版本。若要對此專案進行發佈,需再加上 /* build.gradle */ apply plugin: 'java' apply plugin: 'maven' ext { maven_group_id = 'codedata' maven_artifact_id = 'gradle.happy.tour' maven_version = '0.1.3' } dependencies { compile group: 'commons-logging', name: 'commons-logging', version: '1.1.1' compile group: 'log4j', name: 'log4j', version: '1.2.16' } apply from: 'mvn.gradle' /* mvn.gradle */ apply plugin: 'maven' ext { maven_repo_id = 'admin' maven_repo_pwd = 'admin123' } repositories { maven { credentials { username maven_repo_id password maven_repo_pwd } url "http://127.0.0.1:8081/nexus/content/groups/public/" } } def enableUploadArchives = ext.has('maven_group_id') && ext.has('maven_artifact_id') && ext.has('maven_version') if (enableUploadArchives) { uploadArchives { repositories { mavenDeployer { pom.groupId = maven_group_id pom.artifactId = maven_artifact_id pom.version = maven_version def suffix = pom.version.contains("SNAPSHOT") ? "snapshots" : "releases" repository(url: "http://127.0.0.1:8081/nexus/content/repositories/${suffix}/") { authentication(userName: maven_repo_id, password: maven_repo_pwd) } } } } } 讀者可以注意到,我們做了一些改變:
一旦設定完成,我們能透過 gradle uploadArchives 指令上傳,並能看到顯示上傳到 Server 的訊息。下面範例是發佈 1.0 版本的訊息: qty:HelloGradle qrtt1$ gradle uArc :compileJava UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :jar UP-TO-DATE :uploadArchives Uploading: codedata/gradle.happy.tour/1.0/gradle.happy.tour-1.0.jar to repository remote at http://127.0.0.1:8081/nexus/content/repositories/releases/ Transferring 0K from remote Uploaded 0K BUILD SUCCESSFUL Total time: 6.724 secs 接著,我們能在另一個專案引用它,我們使用動態版本號: qty:UsingDeps qrtt1$ cat build.gradle apply plugin: 'java' apply plugin: 'maven' dependencies { compile group: 'codedata', name: 'gradle.happy.tour', version: '1.+' } apply from: 'mvn.gradle' 並能由 dependency task 查出目前的相依關係: qty:UsingDeps qrtt1$ gradle dep :dependencies ------------------------------------------------------------ Root project ------------------------------------------------------------ archives - Configuration for archive artifacts. No dependencies compile - Compile classpath for source set 'main'. \--- codedata:gradle.happy.tour:1.+ -> 1.0 +--- commons-logging:commons-logging:1.1.1 \--- log4j:log4j:1.2.16 .................................................. 接著讀者可做個簡單的練習:
結果是 UsingDeps 仍停留在 1.0 版,因為 gradle 並不會主動檢查 Server 端是否有新版本,可以下參數強制它檢查: qty:UsingDeps qrtt1$ gradle dep --refresh-dependencies :dependencies ------------------------------------------------------------ Root project ------------------------------------------------------------ archives - Configuration for archive artifacts. No dependencies compile - Compile classpath for source set 'main'. \--- codedata:gradle.happy.tour:1.+ -> 1.1 +--- commons-logging:commons-logging:1.1.1 \--- log4j:log4j:1.2.16 .................................................. 這樣透過 gradle 發佈與引用套件至自建 repository 相當方便我們管理自制的函式庫,需要注意的是相依的版本不會主動更新,需透過 使用 Maven 上傳函式庫教完 Gradle 的上傳繼續談 Maven,這並不是讀者眼花,是因為有太多情況會遇到 Maven Project,特別是使用 Open Source 專案的情況,並非所有的專案都轉向 Gradle。所以,學習如何處理 Mavan 專案也是份重要的知識。儘管可以在網路上找到各種用 Gradle 上傳單一個 JAR 檔,或整組 Maven Project(或先轉成 Gradle Project)。做法上不太一致,那不如退回到對它支援對好的工具:用 Maven 上傳至 Maven Reposiotry。得利用 Maven 上傳,像是:
用 Maven 上傳函式庫的方法就是使用 maven deploy plugin,不過我們不討論原本就是 Maven 專案並且已設好上傳位置的狀態。單純談使用 deploy plugin 內的 deploy-file 功能,用它來處理需要單檔上傳的情況。 設定 Maven Repository 證認資料Maven 專案的 Repository 資訊是設定在專案內,但認證資料是設定在安裝目錄內的 <settings> <servers> <server> <id>my_maven_repo</id> <username>admin</username> <password>admin123</password> </server> </servers> </settings> 使用 deploy plugin 時,需指定 server id mvn deploy:deploy-file \ -Durl=http://127.0.0.1:8081/nexus/content/repositories/releases \ -DrepositoryId=my_maven_repo \ -Dfile=legacy.jar \ -Dpackaging=jar \ -DgroupId=example.com \ -DartifactId=foo.bar \ -Dversion=1.0 參數說明如下:
上傳過程顯示訊息如下: qty:legacyJar qrtt1$ mvn deploy:deploy-file \ > -Durl=http://127.0.0.1:8081/nexus/content/repositories/releases \ > -DrepositoryId=my_maven_repo \ > -Dfile=legacy.jar \ > -Dpackaging=jar \ > -DgroupId=example.com \ > -DartifactId=foo.bar \ > -Dversion=1.0 [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building Maven Stub Project (No POM) 1 [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- maven-deploy-plugin:2.5:deploy-file (default-cli) @ standalone-pom --- Uploading: http://127.0.0.1:8081/nexus/content/repositories/releases/example/com/foo.bar/1.0/foo.bar-1.0.jar Uploaded: http://127.0.0.1:8081/nexus/content/repositories/releases/example/com/foo.bar/1.0/foo.bar-1.0.jar (108 KB at 718.6 KB/sec) Uploading: http://127.0.0.1:8081/nexus/content/repositories/releases/example/com/foo.bar/1.0/foo.bar-1.0.pom Uploaded: http://127.0.0.1:8081/nexus/content/repositories/releases/example/com/foo.bar/1.0/foo.bar-1.0.pom (389 B at 7.6 KB/sec) Downloading: http://127.0.0.1:8081/nexus/content/repositories/releases/example/com/foo.bar/maven-metadata.xml Downloaded: http://127.0.0.1:8081/nexus/content/repositories/releases/example/com/foo.bar/maven-metadata.xml (294 B at 9.9 KB/sec) Uploading: http://127.0.0.1:8081/nexus/content/repositories/releases/example/com/foo.bar/maven-metadata.xml Uploaded: http://127.0.0.1:8081/nexus/content/repositories/releases/example/com/foo.bar/maven-metadata.xml (323 B at 6.2 KB/sec) [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 0.971s [INFO] Finished at: Sat Jan 25 22:20:05 CST 2014 [INFO] Final Memory: 3M/81M [INFO] ------------------------------------------------------------------------ 完工後即可在 gradle 專案內使用,例如: dependencies { compile group: 'codedata', name: 'gradle.happy.tour', version: '1.+' compile group: 'example.com', name: 'foo.bar', version: '1.+' } 另一種上傳形式是指定 pom.xml,通常會用在本身是 Maven 專案有提供 pom.xml,且我們需要將它上傳至自建 repository 的情況: mvn deploy:deploy-file \ -Durl=http://127.0.0.1:8081/nexus/content/repositories/releases \ -DrepositoryId=my_maven_repo \ -Dfile=legacy.jar \ -DpomFile=your-pom.xml 能由 pom.xml 內提供的資訊就不需要寫出來,另外好處是它不再只是單一檔案上傳,還能在 pom.xml 內描述它的相依關係。 課程回顧這次的課程談到 Java 專案的相依性管理,理解相依管理的要點與重要性。學習使用 Gradle 將專案發佈成函式庫,並以 Maven 工具補足額外的上傳需求:
我們講述了如何引用、發佈、更新函式庫,並了解特定情況的上傳方式。期待讀者能在相依管理的機制下,獲得更舒適的開發體驗。 |
pcbill
02/05
補充一下我相容舊專案, 沒有相依性管理的寫法. 使用 file collection 中的 FileTree, 很簡單就可以替換掉.
dependencies {
compile fileTree(dir: 'lib', include: '**/*.jar')
}
我知道用它沒有用到相依性功能, 有點大材小用, 不過我是當作通吃的解決方案, 當有一天組織裡確定要作相依性管理, 可以很快的轉換過去.
usagiritsuka
01/28
這篇文章真不錯
最近剛開始研究Nexus
還是有很多地方不是很清楚
想請問一下
這個範例是有三個gradle檔案嗎?
另外
pom.groupId = maven_group_id
pom.artifactId = maven_artifact_id
pom.version = maven_version
這三個參數是隨意填嗎?
還是說是對應到nexus的那些值?