Maven构建工具

2021-08-18 fishedee 后端

1 概述

Maven是Java中较为重要的项目构建工具,它跟npm工具一样都是依赖的管理工具。同时,它还包含了打包,编译,测试,发布等功能的像webpack的工具。

Maven是相比Java的Ant更为好用的工作,主要改进是:

  • 约定方式定义目录,所有的Maven工程,src与test目录都是相同雷同的,约定方式的定义避免配置的麻烦。
  • 约定方式定义打包流程,Maven工具定义了多个phrase
  1. validate: validate the project is correct and all necessary information is available
  2. compile: compile the source code of the project
  3. test: test the compiled source code using a suitable unit testing framework. These tests should not require the code be packaged or deployed
  4. package: take the compiled code and package it in its distributable format, such as a JAR.
  5. integration-test: process and deploy the package if necessary into an environment where integration tests can be run
  6. verify: run any checks to verify the package is valid and meets quality criteria
  7. install: install the package into the local repository, for use as a dependency in other projects locally
  8. deploy: done in an integration or release environment, copies the final package to the remote repository for sharing with other developers and projects.

Maven的打包流程是固定的,不能被改变的,我们要配置的是不同的打包阶段(phrase),应该调用什么样的插件。而不是去配置哪个命令完成之后执行哪个命令,这大大简化了配置的难度。在之前的Ant工具,Makefile工具,npm工具中我们都是定义命令的执行流程,而不是定义阶段的需要插件是什么。

  • 约定方式的Super Pom,每个配置文件都有一个父级的配置文件,即使当前配置文件是空的,它也有每个phrase阶段默认绑定的插件是什么,这为零配置的配置文件做好了基础。

最后是,Maven的其他功能包括:

  • 脚手架生成,根据脚手架生成Maven项目
  • 插件绑定phrase,为不同的phrase绑定不同的插件
  • filter source,为资源文件注入变量
  • 条件配置,根据不同的环境配置使用不同的配置文件
  • 依赖查找,依赖分析,查找,选择与安装。Java与node的依赖不同的是,同一个包在一个项目中只能有一个版本,不允许同一个包的多版本共存,这涉及到依赖包该如何抉择的问题。
  • 仓库,依赖的仓库分为模块仓库,本地仓库,远程私有仓库,远程公有仓库等的选择。
  • 模块属性继承,就是parent模块的功能了
  • 多模块编译,多模块编译与打包的时候,需要按照依赖次序先后执行多个模块的编译与打包。

2 安装

export MVN_HOME=/Users/fish/Util/maven
export PATH=$PATH:$MVN_HOME/bin

官网下载二进制包,解压后加入以上的环境变量

mvn -v

成功后运行以上命令能看到结果,我的是3.6.3版本

<settings>
    <mirrors>
        <mirror>
            <id>aliyun</id>
            <name>aliyun</name>
            <mirrorOf>central</mirrorOf>
            <!-- 国内推荐阿里云的Maven镜像 -->
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </mirror>
    </mirrors>
</settings>

在主目录的.m2文件夹下创建一个settings.xml配置文件,配置如上来切换国内的maven镜像

3 SpringBoot入门

3.1 入门

mvn archetype:generate

这样会在全交互提示的情况下创建项目结构

mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=helloworld -Dpackage=com.mycompany.app -Dversion=1.0-SNAPSHOT

预先配置项目名称,仅交互提示项目结构就可以了

➜  maven git:(master) ✗ tree .
.
└── helloworld
    ├── pom.xml
    └── src
        ├── main
        │   └── java
        │       └── com
        │           └── mycompany
        │               └── app
        │                   └── App.java
        └── test
            └── java
                └── com
                    └── mycompany
                        └── app
                            └── AppTest.java

12 directories, 3 files

一般选择7: internal -> org.apache.maven.archetypes:maven-archetype-quickstart就可以了。项目结构如上

mvn clean package

打包生成jar包

java -cp target/helloworld-1.0-SNAPSHOT.jar com.mycompany.app.App

指定jar文件启动

3.2 Spring Boot的启动

<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.mycompany.app</groupId>
  <artifactId>mvc</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mvc</name>
  <url>http://maven.apache.org</url>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.4.RELEASE</version>
    <relativePath/>
  </parent>
  <properties>
    <start-class>com.mycompany.app.App</start-class>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

新建项目以后,新增parent和dependency,以及plugin,最后记得加上properties的starter-class。由于有额外的plugin,所以方便了使用Spring-Boot的特殊命令。

package com.mycompany.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App 
{
    public static void main( String[] args )
    {
        SpringApplication.run(App.class,args);
    }
}

App.java的文件

package com.mycompany.app;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
@RequestMapping(path="/design",produces="application/json")
public class HomeController{
    @GetMapping("/recent")
    public String home(){
        return "123";
    }
}

HomeController.java的文件

server.port=8081

src/resources/applications.properties的文件,描述了项目属性

mvn package spring-boot:run

启动项目

mvn package 
java -jar target/mvc-1.0-SNAPSHOT.jar

打包后,用JRE启动,很方便

4 入门

代码在这里

4.1 脚手架

mvn -B archetype:generate -DgroupId=com.myapp -DartifactId=basic -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4

创建一个maven项目,脚手架是maven-archetype-quickstart的1.4版本,而且,项目的groupId是com.myapp,artifactId是basic。groupId是用来区别不同项目的全名,artifactId是项目名称。

.
├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── com
    │           └── myapp
    │               └── App.java
    └── test
        └── java
            └── com
                └── myapp
                    └── AppTest.java

9 directories, 3 files

这是生成的目录结构,可以看到groupId

4.2 打包

mvn package

进行打包,编译和单元测试都会进行,会自动生成target目录

java -cp target/basic-1.0-SNAPSHOT.jar com.myapp.App

启动项目

4.3 清理

mvn clean 

清空项目的生成文件,删除target目录

4.4 文档

mvn site

在target/site目录中的生成文档网站

打开index.html就能打开网站了

4.5 安装

mvn install

安装当前包到本地local仓库去

显示是安装到用户目录的~/.m2/repository目录上了

4.6 配置说明

<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.myapp</groupId>
  <artifactId>app</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>

  <name>app</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

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

modelVersion就是Maven配置文件的版本,dependencies就是依赖的描述,其他都没啥好说的。

5 资源

资源filter就是在编译时对资源文件替换属性(properties)的过程

5.1 内置文件属性

代码在这里

资源filter,就是在编译时,对资源进行属性值替换的操作

mvn -B archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=com.mycompany.source -DartifactId=my-source

创建项目,注意这次有填入DarchetypeGroupId,-B参数是免交互方式提示的

.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── mycompany
    │   │           └── source
    │   │               └── App.java
    │   └── resources
    │       └── application.properties
    └── test
        └── java
            └── com
                └── mycompany
                    └── source
                        └── AppTest.java

12 directories, 4 files

建立application.properties文件

application.name=${project.name}
application.version=${project.version}
message=${my.filter.value}

资源文件的内容

package com.mycompany.source;
import java.io.*;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        try{
            new App().run();
        }catch(Exception e){
            e.printStackTrace();
        }
        
    }
    public void run()throws Exception{
        InputStream is = getClass().getResourceAsStream( "/application.properties" );
        byte b[]=new byte[1024]; 
        is.read(b); 
        is.close();
        System.out.println("data is ["+ new String(b)+"]");
    }
}

App.java文件的内容

<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.mycompany.source</groupId>
  <artifactId>my-source</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>

  <name>my-source</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <my.filter.value>hello</my.filter.value>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
      </resource>
    </resources>
  </build>
</project>

pom.xml的文件的内容,主要是加入了build的resources的配置,指定哪些资源文件执行属性filter的操作。

  my-source git:(master)  java -cp target/my-source-1.0.jar com.mycompany.source.App
data is [application.name=my-source
application.version=1.0
message=hello]
  my-source git:(master) 

这是输出描述,即使我们没有定义任何的属性,Maven也会有自己预定义的属性,就是project开头的属性了。另外一方面,我们可以在pom.xml的properties标签中加入自己自定义的属性,这就是message输出为hello的原因了。

5.2 外置命令行属性

代码在这里

mvn -B archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=com.mycompany.source -DartifactId=my-source3

创建项目

.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── mycompany
    │   │           └── source
    │   │               └── App.java
    │   └── resources
    │       └── application.properties
    └── test
        └── java
            └── com
                └── mycompany
                    └── source
                        └── AppTest.java

12 directories, 4 files

目录结构,和刚才的一样

myname=${commandline.props}

application.properties的内容

<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.mycompany.source</groupId>
  <artifactId>my-source3</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>my-source3</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
   <build>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
      </resource>
    </resources>
  </build>
</project>

pom.xml和刚才的也是一样的

mvn package "-Dcommandline.props=Hello Cat"

打包的时候,使用-D参数传入属性值

  my-source3 git:(master)  java -cp target/my-source3-1.0-SNAPSHOT.jar com.mycompany.source.App
data is [myname=Hello Cat]
  my-source3 git:(master) 

这是输出结果,可以看到资源文件的内容,在编译时,根据命令行的输入参数改变了

5.3 外置文件属性

代码在这里

mvn -B archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=com.mycompany.source -DartifactId=my-source2

创建项目

.
├── filter.properties
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── mycompany
    │   │           └── source
    │   │               └── App.java
    │   └── resources
    │       └── application.properties
    └── test
        └── java
            └── com
                └── mycompany
                    └── source
                        └── AppTest.java

目录结构,加入了filter.properties文件

message=${my.filter.value}
message2=${my.filter.value2}

application.properties的资源文件内容

my.filter.value=hello!
my.filter.value2=Cat

filter.properties的内容,这是外置的属性文件描述

<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.mycompany.source</groupId>
  <artifactId>my-source2</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>my-source2</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <my.filter.value>hello</my.filter.value>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <filters>
      <filter>filter.properties</filter>
    </filters>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
      </resource>
    </resources>
  </build>
</project>

这是pom文件,在build标签下,加入了filters的配置,指定了外置配置文件的来源。

  my-source2 git:(master)  java -cp target/my-source2-1.0-SNAPSHOT.jar com.mycompany.source.App
data is [message=hello
message2=Cat]

这是输出内容

6 生命周期

Maven 有以下三个标准的生命周期:

  • clean:项目清理的处理
  • default(或 build):项目部署的处理
  • site:项目站点文档创建的处理

这是一个典型的build工作的生命周期

6.1 插件与生命周期

代码在这里

mvn -B archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=com.mycompany.app -DartifactId=lifecycle

创建项目

<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.mycompany.app</groupId>
  <artifactId>lifecycle</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>lifecycle</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.1</version>
        <executions>
          <execution>
              <id>id.compile</id>
              <phase>compile</phase>
              <goals>
                <goal>run</goal>
              </goals>
              <configuration>
                <tasks>
                    <echo>compile phase</echo>
                </tasks>
              </configuration>
          </execution>
          <execution>
              <id>id.test</id>
              <phase>test</phase>
              <goals>
              <goal>run</goal>
              </goals>
              <configuration>
                <tasks>
                    <echo>test phase</echo>
                </tasks>
              </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

pom.xml文件的配置。

在Maven中,使用插件是由两部分组成,插件和goal。所以,我们先在plugins中声明一个maven-antrun-plugin插件,然后在executions声明每个任务。每个任务包括要执行插件的哪个goal,以及插件执行时的配置configuration,id是可选的,phase也是可选的。

 <plugin>
   <groupId>org.codehaus.modello</groupId>
   <artifactId>modello-maven-plugin</artifactId>
   <version>1.8.1</version>
   <executions>
     <execution>
       <configuration>
         <models>
           <model>src/main/mdo/maven.mdo</model>
         </models>
         <version>4.0.0</version>
       </configuration>
       <goals>
         <goal>java</goal>
       </goals>
     </execution>
   </executions>
 </plugin>

有些插件的goal默认就绑定到哪些phase上面了,所以就不需要再配置phase了。当有多个execution任务的时候,才需要去配置id标签

输出以上,可以看到不同编译阶段,会触发插件的输出

6.2 生命周期默认绑定

不同的生命周期下,Maven已经有默认的插件的绑定,这里就不啰嗦了,具体看这里

7 多模块聚合与继承

7.1 模块继承

代码在这里

.
├── parent
│   └── pom.xml
└── subModule
    ├── pom.xml
    └── src
        ├── main
        │   └── java
        │       └── com
        │           └── mycompany
        │               └── app
        │                   └── App.java
        └── test
            └── java
                └── com
                    └── mycompany
                        └── app
                            └── AppTest.java

13 directories, 4 files

目录结构如上,我们在同一个目录中建立了两个模块,parent模块与subModule模块。

<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.mycompany.app</groupId>
  <artifactId>multi1</artifactId>
  <version>1.0</version>
  <packaging>pom</packaging>

  <name>multi1</name>
  <url>http://maven.apache.org</url>
  <properties>
    <subModuleVersion>1.0.0</subModuleVersion>
  </properties>
</project>

这是parent/pom.xml的配置,注意声明了subModuleVersion的属性,以及packaging的值为pom。

<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>

  <parent>
     <groupId>com.mycompany.app</groupId>
     <artifactId>multi1</artifactId>
     <version>1.0</version>
     <relativePath>../parent/pom.xml</relativePath>
  </parent>
  
  <artifactId>subModule</artifactId>
  <version>${subModuleVersion}</version>
  <packaging>jar</packaging>

  <name>subModule</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

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

这是subModule/pom.xml的配置,注意,声明了parent配置,并用relativePath指向到同级的parent目录。在这里,subModule无需再声明groupId,version也可以直接用父级的属性subModuleVersion。这是模块继承带来的意义,子模块会继承父模块的groupId,以及父模块的properties。另外,在parent标签中,依然需要指定版本号version,而relativePath默认值为../pom.xml,如果默认值不对的话,需要显式指定。

mvn package

在subModule的目录中,执行mvn package,发现可以执行运行,生成的版本号的确也是父模块定义的版本号

mvn package

在parent的目录中,执行mvn package。什么工作都没有做,这是一个正常的现象。

7.2 模块聚合

代码在这里

模块聚合的目的是,将多个互相依赖的模块进行统一的编译打包等操作

.
├── parent
│   └── pom.xml
├── subModule
│   ├── pom.xml
│   └── src
│       ├── main
│       │   └── java
│       │       └── com
│       │           └── mycompany
│       │               └── app
│       │                   ├── App.java
│       │                   └── Service.java
│       └── test
│           └── java
│               └── com
│                   └── mycompany
│                       └── app
│                           └── AppTest.java
└── subModule2
    ├── pom.xml
    └── src
        ├── main
        │   └── java
        │       └── com
        │           └── mycompany
        │               └── app2
        │                   └── App.java
        └── test
            └── java
                └── com
                    └── mycompany
                        └── app
                            └── AppTest.java

25 directories, 8 files

这是多模块的目录结构,有一个主模块,以及两个子模块。

<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.mycompany.app</groupId>
  <artifactId>parent</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>

  <name>parent</name>
  <url>http://maven.apache.org</url>

  <modules>
    <module>../subModule</module>
    <module>../subModule2</module>
  </modules>
</project>

这是parent/pom.xml,模块聚合的话,需要设置modules标签,并且以相对路径的方式指向子模块的位置

<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.mycompany.app</groupId>
  <artifactId>subModule</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>subModule</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

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

这是subModule/pom.xml的配置,普通的配置,没啥特别的,注意没有parent标签配置

package com.mycompany.app;

public class Service 
{
    public String go(){
        return "Hello SubModule Service Go!";
    }
}

这是subModule下的Service.java文件,一个模块对外的实现

<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.mycompany.app2</groupId>
  <artifactId>subModule2</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>subModule2</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
     <dependency>
      <groupId>com.mycompany.app</groupId>
      <artifactId>subModule</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
</project>

这是subModule2/pom.xml的配置,注意没有parent的配置。但是加入了一个dependency,指向subModule的模块。

package com.mycompany.app2;

import com.mycompany.app.Service;
/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        Service service = new Service();
        System.out.println(service.go());
    }
}

这是subModule2的入口方法,注意应用了subModule模块的Service类。

mvn package

这个时候,如果我们在subModule2中进行打包操作,会提示失败。因为没有找到subModule模块,这点和模块继承是不同的。

mvn package

我们需要在parent模块,执行mvn package操作,才是正常的

java -cp ../subModule2/target/subModule2-1.0-SNAPSHOT.jar:../subModule/target/subModule-1.0-SNAPSHOT.jar  com.mycompany.app2.App

在parent目录下,执行以上命令就能启动subModule2模块了

从实验中我们可以看出,Maven在模块依赖的时候,可以绕过本地仓库,远程仓库,直接在当前模块modules中寻找对应的依赖实现,相当方便。相比之下,npm的依赖查找相当僵硬地一定要放在node_modules目录,为了解决开发时的多模块查找的问题时,引入了麻烦的npm link命令。

7.3 组合使用

代码在这里

模块继承就是子模块中进行parent标签的配置,目的是为了继承父模块的配置。而模块聚合就是父模块中进行modules标签的配置,目的是为了同时编译打包和识别多个模块。两者并不冲突,我们也经常混合在一起经常使用。

.
├── parent
│   └── pom.xml
├── subModule
│   ├── pom.xml
│   └── src
│       ├── main
│       │   └── java
│       │       └── com
│       │           └── mycompany
│       │               └── app
│       │                   ├── App.java
│       │                   └── Service.java
│       └── test
│           └── java
│               └── com
│                   └── mycompany
│                       └── app
│                           └── AppTest.java
└── subModule2
    ├── pom.xml
    └── src
        ├── main
        │   └── java
        │       └── com
        │           └── mycompany
        │               └── app2
        │                   └── App.java
        └── test
            └── java
                └── com
                    └── mycompany
                        └── app
                            └── AppTest.java

25 directories, 8 files

跟刚才一样的目录和代码,仅仅是配置文件不同

<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.mycompany.app</groupId>
  <artifactId>parent</artifactId>
  <version>1.0</version>
  <packaging>pom</packaging>

  <name>parent</name>
  <url>http://maven.apache.org</url>

  <properties>
    <subModuleVersion>1.2.0</subModuleVersion>
    <subModuleVersion2>1.3.0</subModuleVersion2>
  </properties>

  <modules>
    <module>../subModule</module>
    <module>../subModule2</module>
  </modules>
</project>

parent/pom.xml文件,加入了properties

<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>


  <parent>
     <groupId>com.mycompany.app</groupId>
     <artifactId>parent</artifactId>
     <version>1.0</version>
     <relativePath>../parent/pom.xml</relativePath>
  </parent>

  <groupId>com.mycompany.app</groupId>
  <artifactId>subModule</artifactId>
  <version>${subModuleVersion}</version>
  <packaging>jar</packaging>

  
  <name>subModule</name>
  <url>http://maven.apache.org</url>


  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

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

subModule/pom.xml文件,加入了parent,并且version使用了属性引用

<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>

  <parent>
     <groupId>com.mycompany.app</groupId>
     <artifactId>parent</artifactId>
     <version>1.0</version>
     <relativePath>../parent/pom.xml</relativePath>
  </parent>

  <groupId>com.mycompany.app2</groupId>
  <artifactId>subModule2</artifactId>
  <version>${subModuleVersion2}</version>
  <packaging>jar</packaging>
  
  <name>subModule2</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
     <dependency>
      <groupId>com.mycompany.app</groupId>
      <artifactId>subModule</artifactId>
      <version>${subModuleVersion}</version>
    </dependency>
  </dependencies>
</project>

subModule2/pom.xml的配置文件,加入了parent的配置,以及version与dependence的version都是用属性引用。

mvn package

在subModule2的目录下输入命令,依然出错,可见parent指令依然只是继承属性,不会影响依赖查找

mvn package

在parent的目录下输入命令,和之前一样正常运行

7.4 FAQ

7.4.1 指定模块打包

mvn -pl xxx -am package

默认的mvn package会打包所有模块,我们可以指定-pl来指定只打包特定的模块,但是要注意的是,需要加上-am参数,表示将依赖的模块也一起编译。

7.4.2 指定模块执行Maven指令

mvn -pl app flyway:clean

我们只想执行app模块的flyway:clean指令,但是运行就会报错。因为app模块,依赖于common模块,而common模块没有在-pl列表中,所以app模块在执行flyway:clean的时候就会失败(需要编译common模块)。

mvn -pl app,common flyway:clean

这一次,我们将app与common模块一起加入-pl后执行,发现还是报错。因为common模块是公用模块,是没有flyway-maven-plugin的。

# 在顶级目录,将所有模块安装到本地目录
mvn install
# 在app模块中,直接执行flyway:clean指令即可
cd app
mvn flyway clean

最终,我们使用其他方法来迂回实现。在app模块(非顶级模块)直接执行maven指令的时候,依赖是只会在local repository或者remote repository中寻找,不会在当前项目的多模块中寻找的。因此,我们需要将当前项目所有模块都提前install到local repository,就能实现单独模块的maven指令执行了。

相关资料:

8 条件配置

8.1 条件配置与激活

代码在这里

<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.mycompany.app</groupId>
  <artifactId>profile</artifactId>
  <packaging>jar</packaging>

  <name>profile</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <profiles>
    <profile>
      <id>profile_a</id>
      <activation>
      <!--默认触发-->
        <activeByDefault>true</activeByDefault>
      </activation>
      <properties>
        <my_version>1.0</my_version>
      </properties>
    </profile>

    <profile>
      <id>profile_b</id>
      <!--命令行触发,mvn package -P profile_b -->
      <properties>
        <my_version>1.1</my_version>
      </properties>
    </profile>

    <profile>
      <id>profile_c</id>
      <!--指定属性触发,mvn package -Ddebug=123 -->
      <activation>
        <property>
          <name>debug</name>
          <value>123</value>
        </property>
      </activation>
      <properties>
        <my_version>1.2</my_version>
      </properties>
    </profile>

    <profile>
      <id>profile_d</id>
      <!--jdk版本触发,mvn package //jdk版本为1.8-->
      <activation>
        <jdk>1.7</jdk>
      </activation>
      <properties>
        <my_version>1.3</my_version>
      </properties>
    </profile>

    <profile>
      <id>profile_e</id>
      <!--os与cpu架构触发,mvn package //x86的cpu-->
      <activation>
         <os>
          <arch>x86</arch>
        </os>
      </activation>
      <properties>
        <my_version>1.4</my_version>
      </properties>
    </profile>
  </profiles>

  <version>${my_version}</version>
</project>

在profiles中声明条件配置,然后可以通过以下方式来激活对应的配置:

  • 命令行指定哪个配置文件,-P profile-1,profile-2,
  • Profile下的activation触发条件,包括有activeByDefault,property,jdk,os等等。
<project>
  ...
  <repositories>
    <repository>
      <id>global-repo</id>
      ...
    </repository>
  </repositories>
  ...
  <profiles>
    <profile>
      <id>profile-1</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <repositories>
        <repository>
          <id>profile-1-repo</id>
          ...
        </repository>
      </repositories>
    </profile>
    <profile>
      <id>profile-2</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <repositories>
        <repository>
          <id>profile-2-repo</id>
          ...
        </repository>
      </repositories>
    </profile>
    ...
  </profiles>
  ...
</project>

注意,条件配置是允许同时触发多个配置的,当多个配置文件的配置同时激活的时候,配置就会合并在一起。例如,多个激活的配置文件都有repository配置的时候,最终就有多个repository的配置,那么它们各自的优先级为:

  • Profile放在更前面的,优先级更高。
  • 没有Profile的外层配置,优先级最低。

8.2 多级配置文件的条件配置

Maven有多级的配置文件,包括有

  • 每个项目,在pom.xml中定义
  • 每个用户,在%USER_HOME%/.m2/settings.xml中定义
  • 全局,在${maven.home}/conf/settings.xml中定义

在额外文件的定义的配置文件(settings.xml或者profiles.xml),为了保证Maven配置文件的可移植性,这类的配置文件中只允许在Profile标签下面进行配置以下内容:

  • repositories
  • pluginRepositories
  • properties

在pom.xml的配置文件中,我们允许在Profile标签下面进行配置以下的内容:

  • repositories
  • pluginRepositories
  • dependencies
  • plugins
  • properties, (not actually available in the main POM, but used behind the scenes)
  • modules
  • reports
  • reporting
  • dependencyManagement
  • distributionManagement

build元素下允许包含以下的内容 * defaultGoal * resources * testResources * directory * finalName * filters * pluginManagement * plugins

8.3 条件配置激活查看

mvn help:active-profiles

查看哪些条件配置激活的

mvn help:effective-pom

查看进行了Super BOM,多级配置文件合并以后,最终计算得到的pom配置结果

9 依赖机制

Java的依赖与npm的依赖不同的是,同一个包只能有一个版本号。所以,Maven在选定依赖版本的时候,需要深度遍历每个模块的依赖,然后最终选定一个依赖的包的版本作为整个项目的唯一版本。

9.1 依赖调解

 A
  ├── B
     └── C
         └── D 2.0
  └── E
      └── D 1.0

Maven使用最近路径优先的版本选择,例如,A项目通过依赖B,和依赖E,都最终依赖了D库,但是各自D库的版本都不同。那么,最终会选择D 1.0版本,因为A-E-D的路径最短。

当路径一致的时候,Maven选择依赖放在前面的版本。

A
  ├── B
     └── C
         └── D 2.0
  ├── E
     └── D 1.0
  
  └── D 2.0

我们可以利用这个机制,在A项目顶部显式指定依赖D 2.0版本,来强行指定Maven最终必须使用D 2.0版本,而不是D 1.0版本。因为A-D的路径最短。

9.2 依赖作用域

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>group-c</groupId>
      <artifactId>artifact-b</artifactId>
      <version>1.0</version>
      <type>war</type>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
  ...
</project>

依赖有6种作用域,分别是:

  • compile,默认值,编译,测试,和打包都包含该依赖
  • provided,编译和测试包含该作用域,打包不包含该依赖,依赖由运行时的容器提供。例如:servlet-api,运行项目时,容器已经提供,就不需要Maven重复地引入一遍了。一般就相当于头文件的作用,指示有这些接口,打包时并没有加入这个依赖,运行时由容器提供真正的实现。
  • runtime,测试包含该作用域,打包和编译不包含该依赖,依赖由运行时的实现提供。例如:JDBC驱动实现,项目代码编译只需要JDK提供的JDBC接口,只有在测试或运行项目时才需要实现上述接口的具体JDBC驱动。相当于通过动态链接库的方式包含它,编译的时候也不会需要它。编译的时候为什么不需要它,因为程序依赖的可能是一个约定的接口。例如,JDBC驱动,我们用的是JDBC的接口,实际的实现通过程序运行时附近的jar包来提供的。
  • test,测试包含该作用域,打包和编译不包含该依赖,依赖在运行时也不会提供。例如,junit依赖,它仅仅是测试时需要用到的。
  • system,与provided类似,只不过运行时的依赖需要显式指定
  • import,仅仅在dependencyManagement中的依赖需要这样设置,后面会说到

参看这里这里

9.3 依赖Optional

<project>
  ...
  <dependencies>
    <!-- declare the dependency to be set as optional -->
    <dependency>
      <groupId>sample.ProjectA</groupId>
      <artifactId>Project-A</artifactId>
      <version>1.0</version>
      <scope>compile</scope>
      <optional>true</optional> <!-- value will be true or false only -->
    </dependency>
  </dependencies>
</project>

声明一个依赖为optional的方式,就是在optional标签设置为true

Project-A -> Project-B
Project-X -> Project-A

ProjectA声明ProjectB的依赖为optional的时候,对于ProjectA的编译和打包操作没有任何区别。但是当ProjectX声明ProjectA为依赖的时候,ProjectX仅仅会导入ProjectA依赖,而不会导入ProjectB依赖,因为ProjectA对Project的依赖声明为optional的。可选依赖不会影响直接上级的依赖图计算,但是会影响祖父级及以上的依赖图计算

可选依赖的使用场景是这样的,当一个类似Hibernate的ORM框架,它能透明地支持MySql,PostgresSQL,Sql Server等等这些的驱动层。显然,在编译时,它需要逐一指定包含这些数据库的连接器依赖。但是,对于使用方来说,他可能仅仅是使用MySql的Hibernate,他并不希望因为Hibernate对各种各样的数据库支持,导致所有数据库的连接器依赖都包含进来项目,这样过分多余了。

因此Hibernate可以将这些驱动层依赖都标记为Optional,在它编译的时候,这些驱动层都包含过来。但是对于使用方来说,包含Hibernate依赖的时候,不会自动将Hibernate的那些标记为Optional的驱动层依赖也包含进来。使用方需要显式依赖Hibernate的同时,也要去显式依赖哪些驱动层依赖,以显式的方式避免默认的导入过多依赖。

9.4 依赖Exclusion

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>sample.ProjectA</groupId>
      <artifactId>Project-A</artifactId>
      <version>1.0</version>
      <scope>compile</scope>
      <exclusions>
        <exclusion>  <!-- declare the exclusion here -->
          <groupId>sample.ProjectB</groupId>
          <artifactId>Project-B</artifactId>
        </exclusion>
      </exclusions> 
    </dependency>
  </dependencies>
  ...
</project>

声明exclusion的方法也简单,在一个依赖的exclusions指定groupId和artifactId就可以了,由使用方来显示剔除那些依赖。注意,不需要指定剔除依赖的版本号,因为Maven会从ProjectA对ProjectB的依赖分析中计算得到。

Optional是库开发者为库使用者隐式剔除哪些依赖,而Exclusion是库使用者指定哪些依赖需要显式剔除。Exclusion一般是使用方用来替换一部分依赖实现。例如,我不喜欢库开发者默认指定的Log4j,我更喜欢Slf4j,那么就需要用Exclusion来显式剔除,然后在依赖中加入显式Slf4j的依赖即可。

最后,注意点有:

  • Exclusion支持跨多个层级的剔除依赖
  • Exclusion是依赖级别的,不是POM级别的,你不能全局指定剔除某个依赖

与npm对比

  • provided,runtime,exclusion这种依赖设定在npm是没有的
  • test依赖相当于npm的devDependencies
  • optional依赖相当于npm的peerDependencies,但是缺少对父版本的版本限制功能

9.5 依赖管理的继承

我们在多模块一节中,探讨了可以用parent标签来继承父级的配置,例如是properties属性。在这里,我们探讨,parent标签可以指定子级的依赖版本号,从而让多个子级统一依赖版本。

9.5.1 parent传递依赖版本

代码在这里

<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.mycompany.app</groupId>
  <artifactId>multi1</artifactId>
  <version>1.0</version>
  <packaging>pom</packaging>

  <name>multi1</name>
  <url>http://maven.apache.org</url>

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

这是parent/pom.xml文件,通过dependencyManagement来指定需要子级的依赖需要用什么版本号

<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>

  <parent>
     <groupId>com.mycompany.app</groupId>
     <artifactId>multi1</artifactId>
     <version>1.0</version>
     <relativePath>../parent/pom.xml</relativePath>
  </parent>
  
  <artifactId>subModule</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>

  <name>subModule</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <!--不需要指定版本号,因为parent的dependencyManagement标签中指定了版本号-->
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
    </dependency>
  </dependencies>
</project>

这是subModule/pom.xml文件,先用parent来继承父级配置,然后在dependency中指定什么依赖就可以了,版本号会自动从父级的dependencyManagement中查找获取。

但是,每个项目只有一个parent标签配置,当我们需要同时继承多个父级的依赖配置的时候怎么办?可以用import

9.5.2 import传递依赖版本

<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.mycompany.app</groupId>
  <artifactId>multi1</artifactId>
  <version>1.0</version>
  <packaging>pom</packaging>

  <name>multi1</name>
  <url>http://maven.apache.org</url>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>3.8.2</version>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <modules>
    <module>../subModule/pom.xml</module>
  </modules>
</project>

这是parent/pom.xml文件,依然用了dependencyManagement来指定子级依赖的版本号,同时使用了modules。

<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.mycompany.app</groupId>
  <artifactId>subModule</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>

  <name>subModule</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.mycompany.app</groupId>
        <artifactId>multi1</artifactId>
        <version>1.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

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

这是subModule/pom.xml文件,注意,这次没有再使用parent标签了。而是在dependencyManagement里面用import方式导入父级的项目。然后,我们又能愉快地直接在dependencies指定依赖就行,不需要版本号。

这种方法的优势在于可以同时import多个父项目,但是只继承依赖配置,不继承properties等配置。

9.6 依赖查看

mvn dependency:tree

可以查看依赖树结构

9.7 版本号

1.0-SNAPSHOT

快照版本号的,每次刷新依赖都会重新从远程仓库拉取,以保证最新版本

1.2

普通版本号的,会优先从本地仓库拉取,本地仓库没有该依赖才向远程仓库拉取

10 仓库与镜像

10.1 仓库

Maven有三大远程仓库,包括有:

  • MavenCenter,官方最权威,提交较为严格
  • JCenter,提交稍为严格,但是已经停止服务了
  • JitPack,像npm一样开放的仓库,提交最为开放,所以库也是最多的。
<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

我们一般在pom.xml中添加远程仓库地址就可以了,不要在settings.xml添加,以免影响配置文件的可移植性。

10.2 镜像

~/.m2/settings.xml

打开用户所在目录的settings.xml文件

<mirror>
  <id>aliyunmaven</id>
  <mirrorOf>central</mirrorOf>
  <name>阿里云公共仓库</name>
  <url>https://maven.aliyun.com/repository/public</url>
</mirror>

<!-- 中央仓库1 -->
<mirror>
    <id>repo1</id>
    <mirrorOf>central</mirrorOf>
    <name>Human Readable Name for this Mirror.</name>
    <url>https://repo1.maven.org/maven2/</url>
</mirror>

<!-- 中央仓库2 -->
<mirror>
    <id>repo2</id>
    <mirrorOf>central</mirrorOf>
    <name>Human Readable Name for this Mirror.</name>
    <url>https://repo2.maven.org/maven2/</url>
</mirror>

加入阿里云公共仓库的镜像,注意阿里云只是对应central仓库的,所以mirrorOf的配置为central,不能填写为*。这样会镜像所有的仓库,导致jitpack这样的远程仓库无法正常拉取。

如果你遇到了这类错误,就只能打开代理服务器了

11 发布到远程仓库

我们将演示如何将github仓库的发布到JitPack的远程仓库,主要步骤为:

  • 上传代码到github(必须操作),并将模块发布到某一分支(可选操作),并发布Release版本(可选操作)
  • Jitpack,手动触发编译(必选操作)
  • 项目加入依赖下载使用

11.1 上传代码

先将代码,以Maven的格式上传到github,例如像这个项目,从这个项目中可以看出,这是一个多模块的Maven项目,但是我们不想将整个项目到上传到JitPack,只想上传它的其中一个模块。

npm init
npm install gh-pages --save-dev

于是,我们这里用到node的一个gh-pages的小工具

{
  "name": "spring-boot-starter-id-generator",
  "version": "1.0.0",
  "description": "Java的ID生成器",
  "main": "index.js",
  "scripts": {
    "deploy":"mvn clean && gh-pages -d spring-boot-starter-id-generator -b jitpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/fishedee/spring-boot-starter-id-generator.git"
  },
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/fishedee/spring-boot-starter-id-generator/issues"
  },
  "homepage": "https://github.com/fishedee/spring-boot-starter-id-generator#readme",
  "devDependencies": {
    "gh-pages": "^3.2.3"
  }
}

在package.json中设置deploy的scripts。这个脚本的意思是,每次先执行mvn clean,然后将spring-boot-starter-id-generator文件夹发布到jitpack分支上

这是我执行的结果

然后在Github首页的右侧,点击Release,再点击Draft a New Release

选择分支为jitpack,填写好版本号,以及发布描述就可以了

这里,我们发布了一个1.4版本的Release。发布Release的目的是让Jitpack知道从哪里拉数据去编译代码

11.2 JitPack触发编译

打开官网,右上角选择Sign In

然后选择,我们的仓库,选择Releases,以及我们的版本号1.4,最后选择Get It就可以了。它就会自动拉代码下来编译打包,成为可被远程仓库加载的模块。

11.3 引用JitPack依赖

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>com.github.fishedee</groupId>
        <artifactId>spring-boot-starter-id-generator</artifactId>
        <version>1.4</version>
    </dependency>
</dependencies>

最后,在项目的pom.xml文件中填写以上的配置,就在任意项目里面加载我们的这个模块了。为JitPack点个赞,真的简单又好用!

12 其他配置

12.1 环境变量

SPRING_PROFILES_ACTIVE=test mvn test

12.2 Java属性

mvn test -Dspring.profiles.active=test

-D是java在命令行传递属性的方式,也是可以覆盖环境变量的配置一种方式。

mvn -B clean package -Dmaven.test.skip=true -Dautoconfig.skip

也可以用-D来设置系统配置,从而影响Maven的运行配置。系统配置可以看这里

12.3 默认jdk

<profile>
    <id>jdk-1.8</id>
    <activation>
        <activeByDefault>true</activeByDefault>
        <jdk>1.8</jdk>
    </activation>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
    </properties>
</profile>

加入默认的jdk

12.4 .m2/setting

IDE读取的是自己打包的Maven包,但是它依然能读取我们的配置。

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <localRepository/>
  <interactiveMode/>
  <usePluginRegistry/>
  <offline/>
  <pluginGroups/>
  <servers/>
  <mirrors>
    <mirror>
      <id>aliyunmaven</id>
      <mirrorOf>central</mirrorOf>
      <name>阿里云公共仓库</name>
      <url>https://maven.aliyun.com/repository/public</url>

    </mirror>
  </mirrors>
  <proxies/>
  <activeProfiles/>
</settings>

在mac下,IDE的Maven工具会读取~/.m2/settings.xml的配置。以上配置写入了阿里云镜像的配置。

12.5 导入到IDE

使用IntelliJ来导入Maven项目,选择Import Project

选择项目的pom.xml即可

启动的时候,要点击任意一个源文件以后,再点击菜单栏的Run运行。

当pom.xml更新了以后,要执行Maven的Reimport来刷新Maven仓库

13 插件开发

开发属于自己的Maven插件,代码在这里

参考资料有:

  • https://www.iteye.com/blog/rept-693415
  • https://zhuanlan.zhihu.com/p/354308625
  • https://www.cnblogs.com/kiwifly/p/12602407.html

13.1 插件开发

使用Idea创建插件项目,选择mojo脚手架即可

mvn archetype:create -DgroupId=* -DartifactId=* -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-mojo

或者使用mvn脚手架生成器,都是一样的

两种方式都可以,但是最重要的是要定义好artifactId,artifactId有一套固定的规范。例如artifactId为cc-maven-plugin,那么IDEA就会识别这个插件名为cc,注意这个规范不能改,否则maven和IDEA会识别不了这个插件。

<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.fishedee</groupId>
  <artifactId>cc-maven-plugin</artifactId>
  <packaging>maven-plugin</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>myPlugin Maven Mojo</name>
  <url>http://maven.apache.org</url>
  
  <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>
    <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-plugin-api</artifactId>
      <version>3.5.0</version>
    </dependency>
    <dependency>
      <groupId>org.apache.maven.plugin-tools</groupId>
      <artifactId>maven-plugin-annotations</artifactId>
      <version>3.5</version>
    </dependency>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-core</artifactId>
      <version>3.5.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-plugin-plugin</artifactId>
        <version>3.5.2</version>
      </plugin>
    </plugins>
  </build>
</project>

更新pom文件,使用以上的依赖

package com.fishedee;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

@Mojo(name="go")
public class MyMojo
    extends AbstractMojo
{

    @Parameter(
            property = "project",
            required = true,
            readonly = true
    )
    private MavenProject mavenProject;


    @Parameter(
            property = "codegen.outputDir",
            defaultValue = "src/main/java"
    )
    private String outputDir;

    @Parameter(
            property = "codegen.packageName",
            required = true
    )
    private String packageName;

    public void execute()
        throws MojoExecutionException
    {
        Log log = getLog();

        log.info("baseDir "+mavenProject.getBasedir());
        log.info("outputDir "+outputDir);
        log.info("packageName "+packageName);
    }
}

一个简单的插件代码,使用@Mojo来定义插件的目标,使用@Parameter来获取配置参数

mvn install

最重要的这一步是,将插件安装到本地的mvn库

13.2 使用插件

<?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.fishedee</groupId>
  <artifactId>plugin_sample</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>plugin_sample</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>


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

  </dependencies>


  <build>
    <plugins>
      <plugin>
        <groupId>com.fishedee</groupId>
        <artifactId>cc-maven-plugin</artifactId>
        <version>1.0-SNAPSHOT</version>
        <configuration>
          <outputDir>ac</outputDir>
          <packageName>com.kk</packageName>
        </configuration>
        <!--定义在执行compile的阶段执行插件的hello目标-->
        <executions>
          <execution>
            <goals>
              <goal>go</goal>
            </goals>
            <phase>compile</phase>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

然后我们定义一个新的项目,在plugin中指定我们的插件。注意,我们定义了该插件是在compile阶段,顺带执行它的go目标。另外,注意configuration是如何传入配置的。

最后,我们在Maven视图中可以看到这个插件。或者直接对项目compile的时候也会触发这个插件。

这是执行结果,没啥好说的

20 FAQ

20.1 Process terminated

在IDEA中发现了这个错误,肯定是因为pom.xml有语法错误了

改掉语法错误,重新reload就可以了

20.2 IDEA多模块无法识别

在多模块项目导入到IDEA以后,有时候修改了其中一个模块的版本号以后,其他模块即使修改了版本号也无法正常编译。

解决办法是在,顶级的POM文件中,选择Reload Project就可以了

20.3 java.util.zip.ZipException:invalid distance distance too far back

启动SpringBoot的时候报出以上错误,将~/.m2/repository里面的本地缓存包删掉,重新mvn clean 和mvn install就可以了。

20.4 IDEA无法识别多模块的类

导入一个多模块到IDEA以后,一个常见的问题是,编译可以成功,但是IDEA却总是提示这个类找不到,哪个类找不到。Module Settings里面对文件的配置都是正确的,问题在于缺少.iml文件,这听说是IDEA的bug

mvn idea:module

在顶层pom.xml文件,输入以上命令即可补充缺失的.iml文件。

20.5 IDEA启动报错,无法找到org.springframework.boot

idea开发 SpringBoot启动报错 程序包org.springframework.boot不存在,而使用maven 命令直接执行时无任何问题,不会报错。

这里

![](/assets/img/2022-02-21-14-23-20.png

解决方法是将启动分发给Maven来做,但是这样做会有很多额外的问题,例如是停止Maven的时候,并不能关闭程序,导致网络端口被占用。

mvn idea:idea

也有人说通过以上命令来解决,看这里

20.6 Maven打包不进行单元测试

mvn -B clean package -Dmaven.test.skip=true

使用系统参数,使得package的过程跳过单元测试的步骤

20.7 Maven单元测试的时候指定SpringBoot和Maven参数

mvn test -Dspring.profiles.active=test -Dmaven.test.failure.ignore=true

使用-D的系统配置参数就可以做到了,不需要用环境变量

20.8 Maven指定测试时的资源文件夹

<build>
  <testResources>
    <testResource>
      <directory>src/test/java/com/fishedee/erp</directory>
    </testResource>
  </testResources>
</build>

在build里面指定testResources就可以了

20.9 Maven解决编译的乱码问题


<properties>
  <start-class>com.fishedee.erp.App</start-class>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-resources-plugin</artifactId>
      <version>3.2.0</version>
      <configuration>
        <propertiesEncoding>UTF-8</propertiesEncoding>
        <encoding>UTF-8</encoding>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <encoding>UTF-8</encoding>
      </configuration>
    </plugin>
  </pulugins>
</build>

指定resources与compiler,以及默认的project.build.sourceEncoding

21 总结

maven有固定的项目目录格式,比ant严谨多了。

上述Demo的代码在这里

参考资料:

相关文章