文章首发于博客:布袋青年,原文链接直达:史上最全Maven教程,没有之一。


假如你是有必定的开发经验,我相信你必定被项目 lib 下的 JAR 包折磨过,假如碰上兼容问题,更是逐一下载不同版别 JAR 包进行替换排查,相信是每个程序员都不想再阅历一边的噩梦。

Maven 的出现则大大降低开发人员的准备工作,让开发人员更专注与事务,下面即介绍 Maven 根本运用。

Maven 是一个项目办理工具,能够对 Java 项目进行构建、依靠办理。

一、根底装备

1. 库房装备

Maven 中引进了库房的概念,开发人员将所编写的 JAR 按照相应格局推送到库房中,当其他开发者需求引证这个 jar 包时在工程中引证相应依靠,则会先从中心库房进行下载到本地库房,此刻项目将读取本地库房的内容。

关于部分安排或机构一般会在此根底上额定建立私人库房,在引证依靠时会先从私人库房进行读取,假如未找到再从中心库房下载至私人库房,终究再下载到本地库房。

image.png

经过这种方法开发者则无需再手动办理冗杂的项目 JAR 包,然后完成更高的效率。

2. 根本信息

一个最根本的 Maven 项目一般应包括如下内容,当咱们引证一个模块时,也是经过 groupIdartifactIdversion 三项内容进行确定。

标  签 作  用
groupId 一般为安排或公司域名反写。
artifactId 项目的称号。
version 项目的版别信息。
name 项目的简称。
description 项目的扼要描绘。

下面是一个根本界说示例:

<?xml version="1.0" encoding="UTF-8"?>
<project ...>
    <!-- 固定 4.0.0, 指定了当时 POM 模型的版别 -->
    <modelVersion>4.0.0</modelVersion>
    <groupId>xyz.ibudai</groupId>
    <artifactId>maven-demo</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <version>maven demo</version>
    <description>This is maven demo.</description>
</project>

二、依靠办理

1. 依靠引进

经过 dependencies 标签咱们即可导入所需求的工程依靠。

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.27</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

其间 scope 的可选值如下:

效果域 效果
compile 编译时需求用到该 JAR 包(默许)
runtime 编译时不需求,但运行时需求用到。
provided 编译时需求用到,但运行时由 JDK 或某个服务器供给。
test 编译Test时需求用到该 JAR 包。

2. 直接依靠

当项目需求引证到其它依靠时,只需指定所依靠的工程的根本信息即可,剩下的一切都交给 Maven 处理。即便是所要依靠的工程依靠了其它工程,咱们也只需引进项目所直接的依靠的工程。

如下图示例中 Dependency-A 引证了 Dependency-B ,而 Dependency-B 又依靠于 Dependency-C ,在传统项目中若在 Dependency-A 中引证 Dependency-B 则需求一起手动增加 Dependency-BDependency-C 所对应的 JAR 包,但在 Maven 中咱们只需求引进 Dependency-B 即可, Mavne 会主动将子模块所依靠的包导入。

image.png

  • 依靠次序

    maven 工程中遵从先界说先导入的原则,即当存在多个相同直接依靠,优先导入其父依靠界说在前的简洁依靠。

    举个比如,如工程中引进 Dependency-ADependency-B 两个依靠,二者又分别引证了不同版别的 Dependency-C ,但关于 Maven 而言终究编译时同一个依靠即便是不同的版别也只会挑选一份。

    其核算规则如下:若 Dependency-A 界说在 Dependency-B 之前则终究将导入 Dependency-A 中的 C-1.0 版别。而在右侧图例中虽然 Dependency-A 引进优先级高于 Dependency-B ,可是 C-2.0 的直接依靠层级高于 C-1.0,因而将导入 C-2.0 版别。

image.png

3. 依靠扫除

在引证多个模块时可能会发生版别兼容冲突问题,经过 excludes 标签即可完成依靠扫除。

如下咱们在工程中引进了 demo-a 依靠,但其又引证 dependency-b 依靠,如想要在当时工程中移除 dependency-b 依靠,此刻即可经过 excludes 标签将 dependency-b 扫除依靠。

<dependencies>
    <dependency>
        <groupId>xyz.ibudai</groupId>
        <artifactId>demo-a</artifactId>
        <version>1.0.0</version>
        <excludes>
            <exclude>
                <groupId>xyz.ibudai</groupId>
                <artifactId>dependency-b</artifactId>
                <version>1.0.0</version>
            </exclude>
        </excludes>
    </dependency>
</dependencies>

除了手动经过 excludes 标签扫除依靠,被引模块也能够在导入依靠时经过 optional 标签禁用依靠传递。

上述示例中若在 demo-a 工程中引进 dependency-b 依靠时增加 optional 标签,那么其它工程在引进 demo-a 依靠时将不会将 dependency-b 作为直接依靠导入。

<dependencies>
    <dependency>
        <groupId>xyz.ibudai</groupId>
        <artifactId>demo-b</artifactId>
        <version>1.0.0</version>
        <optional>true</optional>
    </dependency>
</dependencies>

4. 变量装备

当项目中引进了大量依靠,为了便利办理一般将引进依靠的版别经过变量进行一致装备,然后完成更直观的依靠办理。

经过 properties 标签即可自界说变量装备,然后运用 ${} 引证变量。

<properties>
    <mysql.version>8.0.30</mysql.version>
    <junit.version>4.13.2</junit.version>
</properties>
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <!-- 运用 "${}" 引证上述自界说变量 -->
        <version>${mysql.version}</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
    </dependency>
</dependencies>

三、模块装备

1. 模块办理

当咱们项目包括多个子项目时,经过 modules 标签即可完成模块办理。

<!-- maven-demo pom.xml -->
<modules>
    <module>module-1</module>
    <module>module-2</module>
</modules>

如下在 maven-demo 中又包括了 module-1module-2 两个工程。
image.png

2. 模块承继

经过 parent 即可符号当时模块的父模块,且子模块将会承继父模块中的一切依靠装备。子模块若没有指定的 groupIdversion 默许承继父模块中的装备。

其间 relativePath 用于指定父模块的 POM 文件目录,省略时默许值为 ../pom.xml 即当时目录的上一级中,若仍未找到则会在本地库房中寻找。

<!-- module-1 pom.xml -->
<parent>
    <groupId>xyz.ibudai</groupId>
    <artifactId>maven-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../pom.xml</relativePath>
</parent>
<artifactId>module-1</artifactId>

四、一致办理

1. 依靠办理

当一共项目包括多个模块,且多个模块引证了相同依靠时显然重复引证是不太适宜的,而经过 dependencyManagement 即可很好的处理依靠共用的问题。

将项目依靠统必界说在父模块的 dependencyManagement 标签中,子模块只需承继父模块并在 dependencies 引进所需的依靠,便可主动读取父模块 dependencyManagement 所指定的版别。

dependencyManagement 既不会在当时模块引进依靠,也不会给其子模块引进依靠,但其能够被承继的,只有在子模块下相同声明了该依靠,才会引进到模块中,子模块中只需在依靠中引进 groupIdartifactId 即可, 也能够指定版别则会进行掩盖。

2. 模块示例

接下来以下图中的模块层级联系进行举例:

image.png

  • maven-demo

    maven-demodependencyManagement 界说 mysqljunit 两个依靠。

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.30</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.2</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
  • module-1

    module-1 中承继 maven-demo 工程,引进 mysql,无需指定版别,将会主动读取父模块中 dependencyManagement 中所指定的版别。当然你也能够挑选指定版别,则将会进行掩盖,但并不主张这么操作,将提高项目维护难度。

    module-1pom 文件内容如下:

    <parent>
        <groupId>xyz.ibudai</groupId>
        <artifactId>maven-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>
    
  • module-2

    module-2 装备同 module-1,经过 dependencyManagement 咱们即完成了项目依靠版别的一致办理。

    <parent>
        <groupId>xyz.ibudai</groupId>
        <artifactId>maven-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
    </dependencies>
    

3. 依靠导入

上面介绍了怎么经过 dependencyManagement 完成全局的依靠版别办理,但假如工程中的两个子模块都需求装备相同的 dependencyManagement 装备时,当然你能够挑选经过承继父模块来完成,也能够用笨办法直接复制粘贴一份。

在上述的 maven-demo 创建同级模块 maven-demo1 ,假如要完成 maven-demo 中装备的 dependencyManagement 则在其 dependencyManagement 装备中导入 maven-demo 并将 scope 设置为 import,并将 type 设置为 pom

经过导入即可完成更轻量化的模块信息承继,具体装备内容如下:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>xyz.ibudai</groupId>
            <artifactId>maven-demo</artifactId>
            <version>1.0.0-SNAPSHOT</version>
            <!-- 导入目标模块的 dependencyManagement -->
            <!-- 依靠范围为 import -->
            <scope>import</scope>
            <!-- 类型一般为 pom -->
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

五、插件办理

经过前面的介绍相信关于 Maven 你现已有了一个开始的了解,但 Maven 除了依靠办理之外供给一系列强大的插件,插件关于 Maven 而言可谓时左膀右臂但却经常被人忽略。

今天就让我介绍一下 Maven 中常用的构建插件。

1. Jar

在运用 Java 开发时一般情况下咱们都会将工程打包为 JAR 文件,首先了解一下 JAR 的文件结构。

下图即为经过 Maven 打包后的 JAR 文件,其间 org.example 目录为工程中界说的包名,存在编译后的 .class 文件, META-INF 目录用于存放工程的元数据信息。

image.png

如上图中 META-INF 下的 MANIFEST.MF 文件内容如下:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: great
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_202

而经过 maven-jar-plugin 插件咱们即可在增加额定信息至打包后的 JAR 文件,插件装备信息如下:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>  
    <artifactId>maven-jar-plugin</artifactId>  
    <version>2.3.1</version>  
    <configuration>  
        <archive>  
            <manifest>  
				<mainClass>org.example.MyTest</mainClass> 
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
				<addDefaultImplementationEntries>true</addDefaultImplementationEntries> 
            </manifest>
            <!-- 装备额定特点信息 -->
            <manifestEntries>  
                <Plugin-Id>demo-plugin</Plugin-Id>  
                <Plugin-Version>1.0.0</Plugin-Version>  
            </manifestEntries>  
        </archive>  
    </configuration>  
</plugin>

在之前的工程 POM 文件中增加上述构建插件从头进行打包,能够看到 MANIFEST.MF 文件中即增加了咱们装备的额定特点。

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: great
Build-Jdk: 1.8.0_202
# Specification entries
Specification-Title: maven-v1
Specification-Version: 1.0-SNAPSHOT
# Implementation entries
Implementation-Title: maven-v1
Implementation-Version: 1.0-SNAPSHOT
Implementation-Vendor-Id: org.example
# Manifest
Main-Class: org.example.MyTest
# ManifestEntries
Plugin-Id: demo-plugin
Plugin-Version: 1.0.0

2. Assembly

在普通 Maven 工程打包时默许仅会编译工程中新建的 java 文件并存储其 .class 文件,关于 POM 文件中引证的第三方依靠并不会一起打包。

如新建一个 Maven 工程并在依靠中导入 Jackson 依靠库并进行打包编译,能够看到下图编译后的 JAR 文件中只有工程中新建的 MyTest.class 文件,项目中所导入的依靠并没有被一起打包。

image.png

而经过 assembly 插件即可将 POM 装备中的一切依靠一起打包编译至 JAR 文件中。

其间 execution 标签界说了 assembly 插件的效果阶段,如这里设置了在 Maven package 即打包阶段收效。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>  
    <artifactId>maven-assembly-plugin</artifactId>  
    <version>3.1.0</version>  
    <configuration>  
        <descriptorRefs>  
            <descriptorRef>jar-with-dependencies</descriptorRef>  
        </descriptorRefs>  
        <!-- Set jar file name -->
        <finalName>${project.artifactId}-${project.version}-all</finalName>  
        <appendAssemblyId>false</appendAssemblyId>  
        <attach>false</attach>  
        <archive>  
            <manifest>  
				<mainClass>fully.qualified.MainClass</mainClass> 
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
				<addDefaultImplementationEntries>true</addDefaultImplementationEntries> 
            </manifest>   
        </archive>  
    </configuration>  
    <executions>  
        <execution>  
	        <!-- Set effect phase -->
            <id>make-assembly</id>  
            <phase>package</phase>  
            <goals>  
                <goal>single</goal>  
            </goals>  
        </execution>  
    </executions>  
</plugin>

在工程 POM 装备中增加上述信息并从头编译打包工程,能够看到此刻 JAR 文件中除了自界说创建的 MyTest.clss 文件外一起包括了依靠的第三方库。

image.png

3. Shade

Shade 插件的功能更为强大,其供给了两个功能:第一个即与 assembly 类似可完成依靠的打包编译,与 assembly 不同的是 Shade 供给了更灵活的履行战略,可指定需求打包编译的依靠调集。

另一个即完成包的重命名功能,咱们都知道 Maven 并不答应在一共工程中一起引进单个依靠的不同版别,而经过 Shade 插件即可完成二次包装然后绕开该限制。

下面介绍一个 Shade 插件中各标签的运用。

  • artifactSet

    经过 includes 标签能够指定需求一起打包编译的第三方依靠。

    界说的格局为:groupId:artifactId

    <artifactSet>
        <includes>  
            <include>groupId:artifactId</include>  
        </includes>  
    </artifactSet>  
    
  • relocations

    经过 relocations 标签即可完成模块的重命名功能。

    其间 pattern 为需求重命名的模块包, shadedPattern 为重命名后的模块名。

    <relocations>
        <relocation>  
            <pattern>old.package.name</pattern>  
            <shadedPattern>new.package.name</shadedPattern>  
        </relocation>
    </relocations>  
    
  • filters

    经过 filters 标签能够完成非必要文件的扫除,如常见的协议文件等,可经过文件名或类型完成匹配。

    <filters>
        <filter>  
            <artifact>*:*</artifact>  
            <excludes>  
                <exclude>filename</exclude>  
                <exclude>file pattern</exclude>  
            </excludes>  
        </filter>  
    </filters>  
    
  • 完好装备

    Shade 相同能够经过 execution 设置效果阶段,上述介绍标签的完好装备内容如下:

    <plugins>
        <plugin>  
            <groupId>org.apache.maven.plugins</groupId>  
            <artifactId>maven-shade-plugin</artifactId>  
            <version>3.2.0</version>  
            <executions>  
                <!-- Working phase -->  
                <execution>  
                    <phase>package</phase>  
                    <goals>  
                        <goal>shade</goal>  
                    </goals>  
                </execution>  
            </executions>  
            <configuration>  
                <minimizeJar>true</minimizeJar>  
                <!-- Defined what dependencies to pull into the uber JAR -->  
                <artifactSet>  
                    <includes>  
                        <include>com.fasterxml.jackson.core:jackson-core</include>  
                    </includes>  
                </artifactSet>  
                <!-- Rename the package -->  
                <relocations>  
                    <relocation>  
                        <!-- Old name -->  
                        <pattern>com.fasterxml.jackson.core</pattern>  
                        <!-- New name -->  
                        <shadedPattern>com.ibudai.fasterxml.jackson.core</shadedPattern>  
                    </relocation>   
                </relocations>  
                <!-- Exclude the file that didn't want -->  
                <filters>  
                    <filter>  
                        <artifact>*:*</artifact>  
                        <excludes>  
                            <exclude>META-INF/license/**</exclude>  
                            <exclude>META-INF/*</exclude>  
                            <exclude>LICENSE</exclude>  
                            <exclude>NOTICE</exclude>  
                        </excludes>  
                    </filter>  
                </filters>  
            </configuration>  
        </plugin>  
    </plugins>
    

在之前的工程中增加上述装备并从头打包,能够看到编译后的 Jackson 模块包层级现已变成咱们自界说的内容,而 Java 的类加载即经过类的完成限定名(包名+类名)来区分是否为同一个类,因而经过 Shade 插件即可完成 Maven 的单一工程多版别引进。
转存失败,主张直接上传图片文件

六、构建装备

在上面介绍了工程的依靠办理与多模块的办理装备,下面介绍一下工程打包构建时涉及的装备。

注意以下一切装备项都是界说在 <build> 标签组内,下述不再重复阐明。

1. 版别指定

<plugin> 标签内可指定工程打包编译时运用的 JDK 版别,可根据服务器环境手动修正版别。

<plugins>
    <plugin>
        <!-- 编译时运用 JDK 版别 -->
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
            <source>8</source>
            <target>8</target>
        </configuration>
    </plugin>
</plugins>

2. 文件扫除

默许项目打包后 /resources 目录下文件都将一致打包进编译后的 JAR 文件,但为了便利装备修正一般将装备文件扫除打包,运用时只需将文件放置于 JAR 同级即可。

如下示例中将 application.yml 文件扫除打包,后续若需求修正装备无需从头打包只需重启项目即可。

<resources>
    <resource>
        <!-- 设置编译去除 yml 装备文件 -->
        <directory>src/main/resources</directory>
        <excludes>
            <exclude>application.yml</exclude>
        </excludes>
    </resource>
</resources>

3. 主类装备

在打包时可能出现无法辨认工程主类的问题,导致编译后的文件无法正常运行,此刻则能够在 pom 文件中手动设置工程的主类。

其间 <mainClass> 中装备的为项目主类的完成限定名。

<plugins>
    <plugin>
        <!-- 设置工程主类路径, 可略去 -->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
            <mainClass>xyz.ibudai.TestWebApplication</mainClass>
            <layout>JAR</layout>
        </configuration>
    </plugin>
</plugins>