万字 Maven 入门教程

2022-10-08 13:52:17 浏览数 (1)

Maven 这个词可以翻译为专家的意思。它是由 Apache 组织的开源,主要服务 Java 平台项目的构建、依赖管理和项目信息管理。

有了 Maven 我们只需编写一些配置然后运行一条命令就可以自动完成项目编译、测试、打包、发布等流程。

安装

Maven 需要依赖 Java 环境,所以首先要确认安装了 Java,首先去官网下载 Maven,然后就可以把它随便解压到一个文件夹,并把这个文件夹路径设置为 M2_HOME 环境变量,最后将 %M2_HOME%bin(Windows)加入到 PATH,Linux 为 export PATH=PATH:M2_HOME/bin。

代码语言:javascript复制
mvn -v # 在命令行运行这条命令,查看 Maven 版本

对于升级就是重复上面的流程。

Maven 安装目录下的 conf 文件下存放着 Maven 的配置 settings.xml,它的作用域是全局的,我们可以复制它到 ~/.m2 下,用户目录下的 settings.xml 修改只对当前的用户有作用。Maven 的依赖包仓库放在,~/.m2 文件夹下的 repository 文件夹中。

因为 Maven 实际上执行的是 Java 命令,我们可以通过 MAVEN_OPT 环境变量设置它的参数。通常需要设置它的值为 -Xms128m -Xmx512m 因为对于大点的项目可能出现内存不够的错误。

对于编辑器中的 Maven 我们可以设置它使用我们下载的 Maven,这样就可以避免两个 Maven 版本不一致而造成的构建行为不一致。

入门

对于 Maven 项目,最核心的就是 pom.xml (Project Object Model) 我们需要把项目的构建配置信息都写在里面。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?><project 
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.demo</groupId>
    <artifactId>hello-world</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>hello world project</name>
    <description>Demo project</description></project>

第一行是 XML 头,指定了 XML 版本和文件编码。

然后就是 project 元素,它是配置文件的根元素,它还声明了 POM 的命名空间。

然后就是 modelVersion 对于 Maven2 和 Maven3 它只能是 4.0.0 版本。

groupId, artifactIdversion 定义了一个项目的基本坐标。

groupId 定义了项目属于哪个组,它通常和 Java 中包名命名一样,例如 a 公司启动了一个 myapp 项目,那么他的 groupId 就可能是 com.a.myapp

artifactId 定义了当前 Maven 项目在组中唯一的 ID,因为一个项目可能有多个子项目或模块。

version 指定了当前项目的版本。SNAPSHOT 为快照版本。

name 给项目更友好的名称,description 是对项目的描述。

上面这些字段定义了项目基本的信息,下面我们就可以编写项目代码了。

代码语言:javascript复制
package com.demo.helloworld;public class HelloWorld {    public String sayHello() {        return "hello world";
    }    public static void main(String[] args) {
        System.out.println(sayHello());
    }
}

Maven 采用约定大于配置的方式,在大多数的情况下项目源码应该放在项目文件夹下的 src/main/java 下(Maven 会自动在该目录下搜寻源码),资源放在 src/main/resources 下,测试代码放在 src/test/java 下。

我们的包名也应该和 groupIdartifactId 相吻合。

然后执行

代码语言:javascript复制
mvn clean compile

clean 是让 Maven 清除项目输出 target 目录。compile 任务用来将项目编译到 target/classes 目录下。

然后我们用 JUnit 编写单元测试,首先需要在 pom.xml 加上 junit 的依赖。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?><project 
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.demo</groupId>
    <artifactId>hello-world</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>hello world project</name>
    <description>Demo project</description>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies></project>

引入一个依赖我们需要填写它的基本坐标,有了这个坐标,Maven 就会自动取中央仓库下载,这个依赖到 ~/.m2/repository 文件夹下。

scope test 是表示依赖只对测试有效,在主代码中引入 junit 会报错。

代码语言:javascript复制
package com.demo.helloworld;import org.junit.Test;import static org.junit.Assert.assertEquals;public class HelloWorldTest {    @Test
    public void test() {        HelloWorld helloWorld = new HelloWorld();
        assertEquals("hello world", helloWorld.sayHello());
    }
}

然后我们需要对 Maven 的编译插件进行一些配置,因为它默认只支持 Java 1.5 所以我们需要配置它为更高版本的 Java

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?><project 
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.demo</groupId>
    <artifactId>hello-world</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>hello world project</name>
    <description>Demo project</description>
    
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
        
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies></project>

然后执行

代码语言:javascript复制
mvn clean test

它会编译测试文件然后运行测试,最后我们就能够看到测试通过的输出。

下一步就是对项目打包,因为我们没有指定项目打包的类型,所以默认就是 jar

代码语言:javascript复制
mvn clean package

进行打包之前 Maven 会自动帮我们编译,测试,通过了就打成 jar 包,放在 target 目录下,名称为 hello-world-0.0.1-SNAPSHOT.jar 它是根据 artifactIdversion 还有打包类型进行命名。

test 会自动帮我们执行 compilepackage 会自动帮我们执行 testinstall 会自动帮我们执行 packageinstall 是将项目安装到仓库中。

我们还可以使用 archetype 插件来生成项目骨架,执行 mvn archetype:generate 命令就可以了,当然也可以使用编辑器新建一个 Maven 项目来选择项目模板。

坐标

Maven 是通过坐标找到一个依赖的,而一组坐标是通过一些元素定义的。

  • groupId 一个组织的的一个实际项目
  • artifactId 实际项目中的一个 Maven 项目或模块
  • version 版本
  • packaging 打包方式有 jarwar
  • classifier 用来定义构建输出的附属构建 如 hello-world-1.0.0-javadoc.jar,它里面包含了 Java 文档,javadoc 就是就是附属构建的 classifier

前三个必须定义,packaging 默认 jarclassifier 不能直接定义。

依赖

一个项目依赖需要放在 dependencies 中,dependency 有几个子元素。

  • groupId, artifactIdversion 是项目基本坐标。
  • type 依赖类型,默认是 jar
  • scope 依赖范围
  • optional 是否可选
  • exclusions 用来排除传递依赖

其中依赖范围有几个值可以选。依赖范围主要是控制编译, 测试运行classpath

  • compile 默认,在编译,测试和运行都有效
  • test 只对测试 classpath 有效,如 junit 它只要在测试的时候能用到就行。
  • provided 对编译和测试有效,比如 servlet-api,因为运行时容器都会提供,所以无需重复引入。
  • runtime 运行时依赖,对测试和运行时有效,在编译时无效,比如 jdbc 驱动实现,只有在需要运行的时候在需要特定的驱动实现。
  • system 系统依赖范围,它与 provided 依赖范围完全一致,只是它的依赖必须使用 systemPath 显式的指定依赖路径,它不是通过 Maven 仓库解析。
代码语言:javascript复制
    <dependencies>
        <dependency>
            <groupId>javax.sql</groupId>
            <artifactId>jdbc-stdext</artifactId>
            <version>2.0</version>
            <scope>system</scope>
            <systemPath>${java.home}/lib/rt.jar</systemPath>
        </dependency>
    </dependencies>

还有一种 import 为导入依赖范围,不会对上面三个依赖范围产生影响。

传递性依赖

传递依赖就是比如我们依赖 spring-core (compile) 但是 spring-core 依赖 commons-logging (compile),那么我们的项目也依赖 commons-logging (compile)。有了这个机制,我们就不用考虑 spring 依赖什么,不必手动安装它的依赖,Maven 会自动将必要的间接依赖引入当前项目。spring boot 的起步依赖就是利用 Maven 的传递依赖。

依赖范围也会对传递依赖产生影响。

左侧表示直接依赖,上面是代表间接依赖,中间就表示传递依赖范围。

依赖调解

比如我们项目传递依赖中有两个依赖是一样的但是它的版本不一样,那么 Maven 就会看谁的路径最短,最短的优先。

如果它们是一样长的,Maven 就查看 POM 中的依赖声明谁在前面谁就优先。

可选依赖

如果我们项目依赖 A(compile),A 依赖 B(compile) 和 C (compile),但是 BC 定义为可选的,那么依赖就不会被传递。依赖可选可以通过 <optional>true</optional> 指定。

排除依赖

如果我们想排除一个传递依赖,比如 spring boot 默认是使用的 jackson,如果我们想用 gson,那么我们就可以将 jackason 排除,然后显式的引入 gson。

代码语言:javascript复制
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-json</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.5</version>
    </dependency></dependencies>

归并依赖

如果我们依赖一个项目的很多模块,因为是一个项目所以版本号都是一样的,这样我们就要给每个依赖填写一样的版本号,升级的话又要一个一个的改。

这时候我们就可以声明一个变量,然后其他地方直接使用就行了。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?><project 
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.demo</groupId>
    <artifactId>hello-world</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>hello world project</name>
    <description>Demo project</description>
    
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.version>2.5.6</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        
    </dependencies></project>

${} 可以引入 Maven 的属性。

仓库

Maven 中任何一个项目或插件的输出都称为构件。任何构件都会有一个唯一的坐标。为了重用 Maven 的依赖都统一放在仓库中,而不是每个项目都有个 lib 一样的文件夹来装它的依赖。

Maven 的仓库分为远程仓库和本地仓库,Maven 会首先通过坐标去本地仓库寻找依赖,如果没有就去远程仓库下载依赖,然后在放入本地仓库再使用。如果都没有的话那么就会报错。

我们可以自定义远程仓库,Maven 自带了一个远程仓库,它包含绝大部分的构件,默认情况都会去这个中央仓库下载构件。

私服是另一种远程仓库,为了节约宽带和时间,在局域网中搭建一个私有仓库,用其代理外部远程仓库,内部项目还可以安装到私服上供其他项目使用。

除了上面两种还有其他的公开远程仓库。比如 jboss repository 等。

本地仓库

本地仓库默认位置是当前用户目录下的 .m2/repository 文件夹,如果我们想更改它的位置可以修改 .m2/settings.xml 文件。

代码语言:javascript复制
<settings>

    <localRepository>D:mavenrepository</localRepository></settings>

如果我们本地有两个项目 AB ,项目 B 依赖于 A,那么我们可以将项目 A 安装到本地仓库,这样我们就可以在 B 项目中依赖 A 了,我们可以在 A 项目中执行 mvn clean install 来将 A 项目安装到本地仓库。

远程仓库

Maven 需要知道最少一个远程仓库,这样 Maven 才能下载构件到本地。中央仓库就是默认的远程仓库,所有 Maven 项目 pom.xml 会继承一个超级POM,它就在 Maven 安装目录下的 lib/maven-model-builder-3.6.1.jarorgapachemavenmodel 文件夹,名为 pom-4.0.0.xml

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?><project>
    <modelVersion>4.0.0</modelVersion>

    <repositories>
        <repository>
            <id>central</id>
            <name>Central Repository</name>
            <url>https://repo.maven.apache.org/maven2</url>
            <layout>default</layout>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>central</id>
            <name>Central Repository</name>
            <url>https://repo.maven.apache.org/maven2</url>
            <layout>default</layout>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <releases>
                <updatePolicy>never</updatePolicy>
            </releases>
        </pluginRepository>
    </pluginRepositories>

    </project>

私服

私服是特殊远程仓库,它代理多个外部远程仓库,我们使用私服来下载构件,私服上如果没有就会取远程下载,然后缓存起来。

配置

如果我们需要的构件不在中央仓库而在另外一个仓库,我们就可以在 pom.xml 中配置该仓库。

代码语言:javascript复制
<repositories>
    <repository>
        <id>jboss</id>
        <name>JBoss Repository</name>
        <url>http://repository.jboss.org/maven2/</url>
        <layout>default</layout>
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository></repositories>

其中 id 必须时唯一的,如果有仓库声明和中央仓库一样的 id 就会覆盖它,url 就是仓库地址,releasessnapshots 分别用来控制发布版和快照版构件的下载,enabledtrue 表示开启下载,false 表示关闭下载。

快照版本是表示开发中的版本,开发中项目会平凡的变化,比如我们开发一个项目中一个模块,但是它要依赖另一个模块,我们就将它安装到本地依赖,这样就可以在我们项目中使用,但是如果依赖项目变了,但是我们还是会使用缓存本地的模块,这时候就要使用 snapshot 版本了,对于快照版本 Maven 每次会去检查当前是不是最新的,如果不是就下载最新的代码。

snapshots 可以设置 Maven 检查更新频率。

代码语言:javascript复制
<snapshots>
    <updatePolicy>daily</updatePolicy>
    <checksumPolicy>ignore</checksumPolicy></snapshots>

never 从不,always 每次构件都去检查,daily 每天(默认值)。

checksumPolicy 是 Maven 下载构件时会校验构件,默认时 warn 警告,还有 fail 项目构建会失败,ignore 忽略。

远程仓库验证

对于组织内部的仓库往往需要认证才运行访问,我们可以在 settings.xml 中设置仓库的账号和密码。

代码语言:javascript复制
<servers>
    <id>repo</id>
    <username>username</username>
    <password>password</password></servers>

其中 id 和我们定义远程仓库 id 对应。

部署到远程仓库

私服的一个作用就是用来部署第三方构件,Maven 可以帮助我们将构件部署到仓库中。

代码语言:javascript复制
<project>
    <distributionManagement>
        <repository>
            <id>id</id>
            <name>Release Repository</name>
            <url>http://196.0.0.1/path/to/release</url>
        </repository>
        <snapshotRepository>
            <id>snapshot</id>
            <name>Snapshot Repository</name>
            <url>http://196.0.0.2/path/to/release</url>
        </snapshotRepository>
    </distributionManagement></project>

repository 表示发布版的仓库,snapshotRepository 表示快照仓库,id 是唯一标识,我们可以通过它来设置账号和密码。

然后我们可以执行如下命令发布

代码语言:javascript复制
mvn clean deploy

仓库解析依赖机制

当依赖返回是 system 时 Maven 回去本地寻找。

当是显式版构件时 如 1.2, 1.3-beta-1 等,Maven 会去所有远程仓库下载到本地。

当依赖版本是 RELEASE LATESTSNAPSHOT 时会根据更新策略去所有远程仓库搜寻构件元数据,然后和本地的元数据合并,再通过合并后的值取寻找版本。

镜像

我们还可以在 settings.xml 中设置镜像镜像服务器。

代码语言:javascript复制
<mirrors>
    <mirror>
        <id>maven.net.cn</id>
        <name> maven central mirror</name>
        <url>http://maven.net.cn/content/groups/public/</url>
        <mirrorOf>central</mirrorOf>
    </mirror></mirrors>

我们上面给中央仓库设置一个镜像,我们也可以设置 mirrorOf* 表示匹配所有远程仓库。

生命周期和插件

Maven 有 3 套生命周期,分别是 cleandefaultsite,Maven 的生命周期是抽象的存在,就像一个接口,它把实际工作交给个插件。这 3 套生命周期是相互独立的。

每个生命周期都有一些阶段(phase),如 clean 生命周期有 3 个阶段,pre-clean, cleanpost-clean,阶段是有顺序的,当执行 clean 阶段,会执行它前面的 pre-clean

clean 生命周期的阶段一共有

  1. pre-clean 执行清理前需要执行的工作
  2. clean 清理上次构件
  3. post-clean 清理过后需要执行的操作

default 生命周期是最核心的部分,它一共有如下阶段

  1. validate
  2. initialize
  3. generate-sources
  4. process-sources 处理主资源文件,一般是 src/main/resources 目录下的文件。
  5. generate-resources
  6. process-resources
  7. compile 编译项目源码。
  8. process-classes
  9. generate-test-sources
  10. process-test-sources 处理项目测试资源文件。
  11. generate-test-resources
  12. process-test-resources
  13. test-compile 编译测试源码
  14. process-test-classes
  15. test 使用单元测试框架运行测试。
  16. prepare-package
  17. package 接受编译好的代码,打包成可发布格式。
  18. pre-integration-test
  19. integration-test
  20. post-integration-test
  21. verify
  22. install 将包安装到 Maven 本地仓库,供本地其他项目使用
  23. deploy 将包复制到远程仓库。

site 生命周期目的是建立和发布项目站点。

  1. pre-site 生成站点之前要执行的操作
  2. site 生成项目站点文档
  3. post-site 执行生成站点之后要完成的工作
  4. site-deploy 将站点发布到服务器

命令行

执行 Maven 任务主要方式就是调用 Maven 的生命周期阶段。

mvn clean 就是执行 clean 生命周期的 clean 阶段

mvn test 就是执行 default 生命周期的 test 阶段

mvn clean install 就是执行 clean 生命周期 clean 阶段和 defaultinstall 阶段。

插件目标

Maven 只是定义了生命周期,然而实际的工作还是要交给插件。一个插件会有一个或多个目标(goal)每个目标对应一个功能,如 surefire:test surefire 是插件名,test 是插件目标,surefire 是 Maven 默认测试插件。

Maven 的生命周期的阶段和插件的目标相互绑定,来完成实际任务。

Maven 默认为主要的生命周期阶段绑定了很多插件目标,当调用生命周期阶段时,相应的插件就会被执行。

自定义绑定

代码语言:javascript复制
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>2.1.1</version>
            <executions>
                <execution>
                    <id>attach-sources</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>jar-no-fork</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins></build>

上面我们将 maven-source-pluginjar-no-fork 目标绑定到了 verify 阶段。id 为任务名。

插件配置

插件也有参数,我们可以通过命令行或在 pom.xml 设置它的参数。

我们可以通过 -D参数键=参数值 来设置插件目标参数,如 mvn package -Dmaven.test.skip=true -D 是 Java 自带的,用来设置 Java 系统属性,Maven 只是重用了该参数。

pom.xml 中我们可以通过 configuration 设置参数。

代码语言:javascript复制
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins></build>

除了在外层设置插件参数(全局),我们还可以对一个 execution 设置参数。

代码语言:javascript复制
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-antrun-plugin</artifactId>
            <version>1.3</version>
            <executions>
                <execution>
                    <id>ant-validate</id>
                    <phase>validate</phase>
                    <goals>
                        <goal>run</goal>
                    </goals>
                    <configuration>
                        <tasks>
                            <echo>lalala</echo>
                        </tasks>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins></build>

命令行调用插件

我们还可以通过命令行调用插件目标。

代码语言:javascript复制
mvn [options] [goal<s>] [phase[s]]

因为有些插件目标不适合绑定到生命周期阶段执行,所以我们可以直接在命令行执行插件的目标。

代码语言:javascript复制
mvn dependency:tree # 查看项目依赖# 当然我们将插件的 groupId artifactId version 都写上

我们知道插件有它的基本坐标,Maven 是如何通过 dependency 查到对应的插件呢?

因为 dependencymaven-dependency-plugin 插件的前缀,Maven 可以通过前缀查找到对应的 artifactId。Maven 会通过本地仓库查找插件,如果查不到就会取远程仓库查找。

对于未指定 groupId 的插件,Maven 会默认使用 org.apache.maven.plugins 作为它的 groupId。Maven 在超级POM 中设定了核心插件的版本,我们项目中就可以继承到这些版本的设定,而无需自己设置。

如果一个插件既不是核心插件又没有设定版本,那么会检查所有仓库可用版本,然后做出选择。

聚合与继承

Maven 还支持多模块开发,我们一个项目可能有很多的模块,Maven 可以将它们聚合在一起。

假如我们有一个项目 app,它分为 a、b 和 c 三个模块,也就是三个 Maven 项目,因为它们是一个项目所以它们的 groupIdversion 都是一样的。

我们项目目录可能像下面这样。

代码语言:javascript复制
|-app
    |- a
        |- src
        |- pom.xml
    |- b
        |- src
        |- pom.xml
    |- c
        |- src
        |- pom.xml
    pom.xml

我们最外层有一个 pom.xml,我们用它来聚合项目中的模块。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?><project 
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.demo.app</groupId>
    <artifactId>app-aggregator</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>a</module>
        <module>b</module>
        <module>c</module>
    </modules></project>

需要将 packaging 设置为 pom,然后设定它要聚合的模块。

module 中是模块的相对路径,如果和其他模块是平行目录则路径就是 ../a 等。

现在我们就不用一个一个取构件了,我们在最外层执行 mvn clean install。Maven 会解析 pom.xml 并计算出模块的构建次序,然后顺序执行。

继承

我们发现我们的子模块有很多相同的配置,这时候我们就可以使用继承来消除重复。

我们可以再创建一个用来做 parentpom.xml 也可以重用我们上面创建的 aggregator pom.xml,如果重用的话我们就无需修改它,但是需要修改要继承它的模块。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?><project 
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>app-a</artifactId>
    <parent>
        <groupId>com.demo.app</groupId>
        <artifactId>app-aggregator</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath>
    </parent></project>

我们使用 parent 来指明模块要继承的父级。这里的 relativePath 的默认值就是 ../pom.xml 我们也可以省略它。

现在我们继承了父级的 groupIdversion,如果我们需要不同的值,也可以覆盖它。几乎所有的项目都可以继承父级的。

如果我们父级声明了一个依赖,那么所有子模块都会继承这个依赖,即使有的模块不需要这个依赖。

Maven 提供了 dependencyManagement 来让子模块不会引入实际依赖,只有子模块声明才会依赖。

代码语言:javascript复制
<dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot</artifactId>
        <version>2.1.7.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test</artifactId>
        <version>2.1.7.RELEASE</version>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test-autoconfigure</artifactId>
        <version>2.1.7.RELEASE</version>
      </dependency>
    </dependencies></dependencyManagement>
代码语言:javascript复制
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot</artifactId>
  </dependency></dependencies>

有了 dependencyManagement,我们子模块不会直接继承,但是如果声明了需要继承就无需填写依赖的版本了。

spring boot 就是利用 Maven 的继承,我许让我们自己填写依赖的版本。

还有一个 pluginManagement (在 build 元素下) 它的作用和 dependencyManagement 是一样的,只是它是作用于插件。

测试

Maven 的测试是使用 maven-surefire-plugin 插件。

有时候我们想跳过测试可以在命令行加入 -DskipTests-Dmaven.test.skip=true 不仅跳过测试,也会跳过测试代码的编译。

我们还可以运行指定测试,如 -Dtest=*Tests 表示只运行 Tests 结尾的测试,* 匹配 0 或多个字符。还可以使用 , 分割多个参数,如 -Dtest=*Tests,*IT

Maven 属性

Maven pom.xml 中可以使用 ${} 来注入属性,它一共支持 6 类属性。

  • 内置属性,如 {version} 项目版本 和 {basedir} 项目根目录
  • POM 属性,可以引用 pom.xml 中的属性,如 {project.version}, {project.build.sourceDirectory} 等
  • 自定义属性,在 properties 中自定义的属性
  • settings.xml 中的属性,如 ${settings.loaclRepository}
  • Java 系统属性,如 ${user.name}
  • 环境变量,如 ${JAVA_HOME}

Profile

我们项目中开发环境和线上环境不同往往需要不同的配置。Maven 中的 Profile 就可以针对环境的不同使用不同的配置。

代码语言:javascript复制
db.url=${db.url}db.password=${db.password}
代码语言:javascript复制
<profiles>
    <profile>
      <id>dev</id>
      <activation> 
        <activeByDefault>true</activeByDefault> 
      </activation>
      <properties>
        <db.url>dev.url</db.url>
        <db.password>dev.password</db.password>
      </properties>
    </profile>
    <profile>
      <id>prod</id>
      <properties>
        <db.url>prod.url</db.url>
        <db.password>prod.password</db.password>
      </properties>
    </profile></profiles><!-- 
    需要资源开启过滤,这样上面 properties 文件的 ${} 就可以注入我们的 properties 中属性了 
--><build>
    <resources>
        <resource>
            <directory>${project.basedir}/src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
    <testResources>
        <testResource>
            <directory>${project.basedir}/src/test/resources</directory>
            <filtering>true</filtering>
        </testResource>
    </testResources></build>

然后我们就可以在命令行中使用 -Pdev 来激活开发模式的配置,profile 中的配置是当激活当前 profile 才会生效的配置。

frontend-maven-plugin

有时候我们需要将前端和后端放在一起,我们就可以使用 frontend-maven-plugin , 来帮助我们安装 node npm 或 yarn 来执行 npm script。

我们只需要在前端模块中添加这个插件

代码语言:javascript复制
  <plugin>
    <groupId>com.github.eirslett</groupId>
    <artifactId>frontend-maven-plugin</artifactId>
    <version>${frontend-maven-plugin.version}</version>
    <configuration>
      <installDirectory>target</installDirectory> 
      <nodeVersion>v13.6.0</nodeVersion>
    </configuration>
    <executions>
      <execution>
        <id>install node and npm</id>
        <goals>
          <goal>install-node-and-npm</goal>
        </goals>
      </execution>
      <execution>
        <id>npm install</id>
        <goals>
          <goal>npm</goal>
        </goals>
        <configuration>
          <arguments>install</arguments>
        </configuration>
      </execution>
      <execution>
        <id>npm lint</id>
        <goals>
          <goal>npm</goal>
        </goals>
        <phase>compile</phase>
        <configuration>
          <arguments>run lint</arguments>
        </configuration>
      </execution>
      <execution>
        <id>npm run build</id>
        <goals>
          <goal>npm</goal>
        </goals>
        <phase>compile</phase>
        <configuration>
          <arguments>run build</arguments>
        </configuration>
      </execution>
      <execution>
        <id>npm run test</id>
        <goals>
          <goal>npm</goal>
        </goals>
        <phase>test</phase>
        <configuration>
          <arguments>run test</arguments>
        </configuration>
      </execution>
    </executions>
  </plugin>

它会自己安装全新的 nodenpm ,与全局的 node 隔离。然后我们使用它的 npm goal 来执行 npm 命令。 我们也可以使用 clean 插件来清理每次生成的代码。

代码语言:javascript复制
  <plugin>
    <artifactId>maven-clean-plugin</artifactId>
    <version>${maven-clean-plugin.version}</version>
    <executions>
      <execution>
        <id>remove existing output</id>
        <phase>compile</phase>
        <goals>
          <goal>clean</goal>
        </goals>
        <configuration>
          <excludeDefaultDirectories>true</excludeDefaultDirectories>
          <filesets>
            <fileset>
              <directory>build</directory>
            </fileset>
          </filesets>
        </configuration>
      </execution>
    </executions>
  </plugin>

dockerfile-maven-plugin

dockerfile-maven-plugin 插件可以帮助我们构建和发布 docker 镜像,而无需再手动输入命令。

代码语言:javascript复制
  <plugin>
    <groupId>com.spotify</groupId>
    <artifactId>dockerfile-maven-plugin</artifactId>
    <version>${dockerfile-maven-version}</version>
    <executions>
      <execution>
        <id>default</id>
        <goals>
          <goal>build</goal>
        </goals>
        <configuration>
          <contextDirectory>context</contextDirectory> 
          <dockerfile>not-context/Dockerfile</dockerfile> 
          <buildArgs> 
            <IMAGE_VERSION>0.0.1</IMAGE_VERSION>
          </buildArgs>
        </configuration>
      </execution>
      <execution>
        <id>tag</id>
        <goals>
          <goal>tag</goal>
        </goals>
        <configuration>
          <repository>test/build-tag-version</repository> 
          <tag>${project.version}</tag> 
          <skip>true</skip> 
        </configuration>
      </execution>
    </executions>
  </plugin>

我们需要一个 Dockerfile 来构建我们的 image,如果是 spring boot 项目可以简单使用 fat jar 方法来构建。

代码语言:javascript复制
FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

更多关于 spring boot docker 可以查看 Spring Boot Docker

0 人点赞