教壞小朋友:Maven 的一些姿勢

最近遇到了一個(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é)起來就好了。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容