優化 maven 依賴實踐

語言: CN / TW / HK

背景

Maven 是非常優秀的項目管理工具,我們可以方便地在 pom 文件裏配置 Java 工程的依賴。但是隨着項目的迭代和時間推移,pom 文件在新老需求的不斷增刪改之下變得越來越臃腫,到後來連自己都不知道哪些是有效依賴。

下面將簡單介紹如何用 Maven 插件 maven dependency plugin 來解決這個問題。

maven dependency plugin

maven dependency plugin 是一個強大的 Maven 依賴處理插件,官網介紹如下:

The dependency plugin provides the capability to manipulate artifacts. It can copy and/or unpack artifacts from local or remote repositories to a specified location.

該插件可以將文件從本地或遠程倉庫複製 / 解壓到指定的位置,相應的,我們比較熟知的 goal 可能是 dependency:copydependency:unpack。但除此之外,maven dependency plugin 還提供了插件依賴分析的能力,與此對應的 goal 是 dependency:analyze,這也是本文要介紹的重點。

dependency:analyze 使用

此命令會掃描項目的依賴關係,並分析出哪些依賴是:

  • 被引用但未聲明 (used and undeclared):比如在工程中使用了某個類,但這個類的 Maven 依賴並沒有配置在 pom 文件中。
  • 聲明瞭但未被引用 (unused and declared):在 pom 文件中聲明瞭某個依賴,但是該依賴卻沒有在工程中被使用。

假設我們新建一個 example 項目,同時添加 jackson-databindlog4j 的依賴,並配置 maven-dependency-plugin

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.12.3</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <version>3.2.0</version>
        </plugin>
    </plugins>
</build>

然後我們簡單地定義一個 Java Bean,並帶上 JsonProperty 註解:

class Foo {
    @JsonProperty(value = "myBar")
    private String bar;

    public String getBar() {
        return bar;
    }
    public void setBar(String bar) {
        this.bar = bar;
    }
}

最後我們試着跑一下 dependency:analyze 命令:

mvn dependency:analyze

[WARNING] Used undeclared dependencies found:
[WARNING]    com.fasterxml.jackson.core:jackson-annotations:jar:2.12.3:compile
[WARNING] Unused declared dependencies found:
[WARNING]    com.fasterxml.jackson.core:jackson-databind:jar:2.12.3:compile
[WARNING]    log4j:log4j:jar:1.2.17:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.539 s

可以看到,控制枱輸出了幾個 WARNING

  • Used undeclared dependencies found:因為我們在工程中用到了來自於 jackson-annotations 包的 JsonProperty 註解,但是我們並沒有在 pom 文件中聲明此依賴包(此依賴其實是來自於 jackson-databind 的依賴)。
  • Unused declared dependencies found:我們在 pom 裏面聲明瞭 jackson-databindlog4j 的依賴,卻沒有在工程中的任何地方使用到。

所以單單看我們工程中的代碼,要想最小化工程的依賴,其實只需添加 jackson-annotations 就已足夠:

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.12.3</version>
    </dependency>
</dependencies>

由此可見,通過 maven dependency plugin 的依賴分析能力,我們可以一目瞭然的知道應該如何優化項目依賴。

插件缺陷

dependency:analyze 內部默認使用了 Maven Dependency Analyzer 作為依賴分析工具,由於它是基於字節碼的掃描,而非源代碼,所以無法檢測某些特殊情況。除此之外,配置文件中的依賴引用也無法被掃描到。

只能掃描直接依賴

比如我們在工程中需要配置 RabbitMQ,但代碼只使用了 org.springframework.amqp.core.AmqpTemplate,該依賴來自於

<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-amqp</artifactId>
    <version>1.3.5.RELEASE</version>
</dependency>

但假如在 pom 裏面只添加此依賴,則啟動會報錯:

Caused by: org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/rabbit]
Offending resource: URL [file:./target/test-14.4.0/WEB-INF/classes/spring-rabbit.xml]

因為 spring-amqp 只是 RabbitMQ 的一個模塊,相當於只是拼圖的一部分,若希望得到完整的功能,最終應該引入如下包:

<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit</artifactId>
    <version>1.3.5.RELEASE</version>
</dependency>

它提供了 Spring 解析所需的配置文件如 spring.handlersspring.schemas 等。

無法解析配置文件

如果有些 class 只在配置文件中被引用,則無法被插件掃描。如 applicationContext.xml 中配置的 org.springframework.jdbc.datasource.DataSourceTransactionManager,引用了來自 spring-jdbc 的包,但它依然會被認為是 Unused declared dependencies

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

包括在 web.xml 中的配置,也無法被識別:

<servlet>
    <servlet-name>Jersey REST service</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
</servlet>

上述依賴來自 jersey-servlet 包,也會被認為是 Unused declared dependencies

類似的,在 application.properties 中的配置,也無法解析:

driver=oracle.jdbc.driver.OracleDriver

上述依賴來自 ojdbc8 包,也會被認為是 Unused declared dependencies

workaround

那對於這些無法被插件掃描到的依賴,又不想讓它報 Warning 該怎麼辦呢?插件也支持手動指定依賴,比如要強制添加 spring-jdbc,可以通過如下方式:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <configuration>
        <usedDependencies>
            <dependency>org.springframework:spring-jdbc</dependency>
        </usedDependencies>
    </configuration>
</plugin>

總結

maven dependency plugin 除了提供最常用的依賴複製和解壓等功能以外,還提供了依賴分析的功能,我們可以利用它來構建最小化的項目依賴,可以説是強迫症程序員的福音。但因為它內部是根據字節碼來掃描依賴,所以在有些情況會造成誤判,需要特別注意。

maven dependency plugin