最近遇到了一個(gè) Maven 的小問題,著實(shí)讓有2年多的 Java 實(shí)戰(zhàn)開發(fā)經(jīng)驗(yàn)的筆者留了一身汗。自以為對 Maven 的常用東西還是比較熟悉的,實(shí)際還是停留在 “知其然而不知其所以然” 的階段。習(xí)慣了 “臨摹官方示例+遇到問題就搜索” 的快餐式手法,自然就漸漸失去刨根問底的心,是吧。
所以本篇旨在記錄所遇到問題的解決過程,以及從 “技術(shù)小白” (我還是很謙虛的好不 ??) 的視角來看看 Maven 工具的一些“小技巧”。
問題:咦,集成測試挑機(jī)器,死活跑不起來?
為了測試 redis 實(shí)現(xiàn)的 repository,使用了 embedded-redis,在測試前可以開啟一個(gè)嵌入式的 redis server,跑完測試只需要 server.stop() 就好了。這一套在我的 Macbook Pro 上跑得好好的,換到另一個(gè)同事的 Windows 上就死活報(bào)錯(cuò)說 server 起不來,但其實(shí) embedded-redis 是支持 windows的。
本著不想折騰 embedded-redis 配置的原則,咱來個(gè)曲線救國吧,讓一部分測試成為可選項(xiàng),只按需的跑一跑(不在開發(fā)本地跑,只在 Jenkins 上跑)。自然的就想到了將一部分測試作為 Integration Test,通過配置來控制它們的運(yùn)行。
然而,這次運(yùn)氣就沒那么好了,除非在 maven surefire plugin 中配置 Skip Test 或者使用 mvn install -DskipTests,似乎沒有辦法在 mvn install 的時(shí)候不去運(yùn)行集成測試。
1. 無痛起步:mvn clean install ?
從我兩年前第一天上班起,這個(gè)三個(gè)英文字母就跟打了烙印一樣深刻。拿到項(xiàng)目代碼,啥事兒不管,咱先實(shí)現(xiàn)個(gè)小目標(biāo),比如一次性跑通 mvn clean install。所以說 “入門教程” 他害人不淺吶,直到幾天前,我都還以為本地編譯打包用這 “一招鮮” 就可以了。
首先我們來看看 Maven 的 Lifecycle 吧,完整的 Lifecycle 還包含很多中間步驟。
- validate:驗(yàn)證一些東西,我也不知道是啥 ??
- compile:編譯
- test:單元測試
- package:打包成 jar 或者其他形式
- verify:集成測試
- install:安裝到 maven repository
- deploy:部署上傳到倉庫
看到這里,讀者可能意識(shí)到了什么吧,其實(shí)有個(gè)最直截了當(dāng)?shù)淖龇ǎ镜哪阒贿\(yùn)行 mvn package 不就好了????墒恰尘拖肱軅€(gè) mvn install 怎么辦?。▍⒖己竺娴摹按虬械?jar”,因?yàn)槟巢寮窃?install 的階段才執(zhí)行)
2. Test 原來可以優(yōu)雅的分為兩類
補(bǔ)充一點(diǎn),前面提到了單元測試 (UnitTest) 和集成測試 (IntegrationTest),如何讓一部分測試成為集成測試呢。有一種簡單直接的辦法就是手動(dòng)配置標(biāo)記那些測試文件(文件夾)只在集成測試的時(shí)候才運(yùn)行。
其實(shí)工具已經(jīng)提供了一個(gè)優(yōu)雅的辦法來區(qū)分哪些是單元測試,哪些是集成測試。在后文中要用的集成測試插件maven-surefire-plugin、maven-failsafe-plugin 都有個(gè) include的約定:
Unit Test 文件命名約定:凡是以 Test 作為文件名的開頭或結(jié)尾的那些測試自動(dòng)歸類為單元測試。
Integration Test 文件命名約定:凡是以 IT 作為文件名的開頭或結(jié)尾的那些測試自動(dòng)歸類為集成測試。
如此,在 “約定大于配置” 之下,很多事兒就自然優(yōu)雅了。
3. 解法:Profile
下面來說說解法,這里筆者選擇了 Maven 的 profile 來控制是否加入集成測試插件,在 all 的 profile 配置下,failsafe插件才生效,mvn install 才會(huì)去運(yùn)行那些集成測試。
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>all</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.19.1</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
大部分時(shí)候:我只想默默地完成 Build
mvn clean install
因?yàn)榍懊媾渲昧?activeByDefault 的空 Profile,所以無需額外的說明,它只會(huì)跑所有的單元測試。
集成的時(shí)候:我也想來個(gè)大寶劍
mvn clean install -P all
在 Jenkins 配置上這個(gè)命令就可以跑完所有的測試流程了。
Maven 的一些常用配置補(bǔ)完
1. 打包所有的 jar
在我們的項(xiàng)目中,雖然使用了 spring-boot,但并沒有使用插件來生產(chǎn) fat-jar。 而是把所有的 jar 文件(包括眾多的 dependencies)打包成一個(gè)文件歸檔,用于部署。下面就講講如何在 Maven 里來完成這個(gè)步驟。
使用插件打包所有的jar
這里使用了 “maven-dependency-plugin” 來復(fù)制所有的依賴 jar 到 target/lib 目錄下, 然后 “maven-assembly-plugin” 插件會(huì)將可執(zhí)行 jar 以及所有的依賴庫 jar 按照 zip-package.xml 的配置去執(zhí)行打包。如果你和我一樣配置了 “finalName” 那么最終的輸出文件名就會(huì)是例子中的 demo-service.tar.gz,否則它會(huì)默認(rèn)根據(jù)你的 {artifactId}-{version} 來生成。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>create-distribution</id>
<phase>install</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<finalName>demo-service</finalName>
<descriptors>
<descriptor>zip-package.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
assembly 的配置
<?xml version="1.0" encoding="UTF-8"?>
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2
http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>dist</id>
<formats>
<format>tar.gz</format>
</formats>
<fileSets>
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>build</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
<excludes>
<exclude>*-sources.jar</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/lib</directory>
<outputDirectory>lib</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>
上面貼的是我使用的 zip-package.xml ,打包的輸出 format 使用 gzip 壓縮,在 fileSet 里設(shè)置了 include/exclude 的文件,outputDirectory 可以用于設(shè)置文件的輸出路徑,上面例子中的配置生成的結(jié)構(gòu)就是:
build/
|-- service.jar
lib/
|-- tomcat.jar
|-- tool.jar
|-- ...
2. 手動(dòng)上傳依賴的 jar 到 私有的 Nexus 里
某些時(shí)候,你項(xiàng)目里依賴的 jar lib 可能并沒有在 public repository 里面,比如外包廠商生成的 jar。有一種做法是直接添加 sytem scope 的依賴:
<dependency>
<groupId>com.demo.www</groupId>
<artifactId>demo</artifactId>
<version>0.0.1</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/demo-0.0.1.jar</systemPath>
</dependency>
這么做不是不可以,但會(huì)看著很奇怪,依賴管理起來也很麻煩,某些時(shí)候還會(huì)遇到 bug (maven-dependency-plugin 里配置的 runtime includeScope 并不會(huì)把這個(gè) system scope 的依賴放進(jìn)去)
另一種做法就是把這個(gè) jar 上傳到你的 私有 Nexus 里面就行管理,用的時(shí)候保持平常的 dependency 規(guī)則就好了:
<dependency>
<groupId>com.demo.www</groupId>
<artifactId>demo</artifactId>
<version>0.0.1</version>
</dependency>
下面就給出筆者所使用的 bash 上傳腳本,讀者請按照你的實(shí)際情況修改即可,從此 pom 里無需system scope的依賴了。
mvn deploy:deploy-file \
-DgroupId=com.demo.www \
-DartifactId=demo \
-Dversion=0.0.1 \
-DgeneratePom=true \
-Dpackaging=jar \
-DrepositoryId=myrepository \
-Durl=http://myrepository.com/content/repositories/releases \
-Dfile=demo-sdk-java-0.0.1.jar
3. 其他的一些工具
- error-prone:google 出品必屬精品,幫你做代碼的靜態(tài)檢查,老司機(jī)(畢竟使用神器 Intellij)應(yīng)該不怎么有機(jī)會(huì)觸發(fā)它的警報(bào)。
- distributionManager: 當(dāng)你使用私有的 Nexus 的時(shí)候需要配置的。
- aspectj-maven-plugin: 用于在編譯期 weave 你的 AspectJ AOP,例子可以參考之前的一篇文章《Java AOP 實(shí)例踩坑記》。
最后:請手下留情
別問我 “你為啥不用 Gradle 呀”,我怎么好意思機(jī)智的回答說 “那是因?yàn)楣镜募夹g(shù)太老舊了呀,只會(huì)用 Maven” ??。就算我技多不壓身,但精力有限,術(shù)業(yè)有專攻,我知道 Gradle 可以很方便的做很多 DevOps 的事情,可我只想做個(gè)安靜的 Coder,也沒必要折騰這么多種構(gòu)建工具吧,以后用到了學(xué)起來就好了。