前言

这次来说说maven这玩意,同样还是那句话,maven对我而言只是工具,一些常规操作已经足够了,有空有兴趣才会去深入研究它。接下来会记录下自己使用maven时需要注意和理解的地方,至于那些基本概念和环境配置的问题,相信大家都懂。

 

仓库

maven仓库可分为本地仓库和远程仓库。

如果公司自己有搭建maven***,那么还可以细分为本地仓库、***仓库(内网)、中央仓库(外网)。

***是指公司内网搭建的maven仓库,可供公司内部人员使用。

pom.xml里依赖jar包的寻找流程:

  1. 本地仓库找,找到直接用,不需要联网。
  2. 本地找不到,***仓库找,找到就下载到本地,以供下次直接使用。
  3. ***找不到,会直接***仓库找,然后下载到***、本地,以供下次直接使用。

没***的话,本地仓库找不到就直接***仓库找。

构建过程

maven构建过程的各个环节,代表maven工作的某个阶段

  1. 清理:将以前编译得到的旧的class字节码文件删除,为下一次编译做准备。
  2. 编译:将Java源程序编译成class字节码。
  3. 测试:自动测试,自动调用junit程序。
  4. 报告:测试程序执行的结果。
  5. 打包:动态Web工程打war包,java工程打jar包。
  6. 安装:将打包得到的文件复制到仓库中指定位置。
  7. 发布:拷贝最终的工程包到远程仓库,以供其他开发人员使用。

maven实际工作时,顺序不一定从上到下。几个阶段是重要阶段,并不是maven的全部阶段。具体执行什么阶段,执行顺序是啥,依赖于它的生命周期。

有些文章会说到最后一个构建阶段:部署->将动态Web工程生成的war包复制到Servlet容器的指定目录下,使其运行。 这需要在tomcat这种servlet容器配置点什么,然后再执行maven相关命令,war包就会自动部署到容器下。不过我现在开发基本都是在SpringBoot环境下,war包部署就不存在了。有兴趣再去熟悉熟悉。

生命周期

三套相互独立的生命周期,互不影响,定义了构建环节的执行顺序。

  1. clean生命周期:构建之前进行一些清理工作。
  2. default生命周期:常用且核心,包括编译、测试、打包、安装、发布等
  3. site生命周期:生成项目报告、站点。发布站点。(我没怎么用过....)

依赖上述的构建过程和生命周期,maven执行任何一个阶段的时候,它前面的所有阶段都会执行。

例如我们执行 mvn install 的时候,代码会被编译,测试,打包。但不会clean(清理),因为install和clean是在不同的生命周期里,但我们可以结合使用,如:mvn clean install

 

idea内置maven界面也说明这一点,点击生命周期的某一个阶段,maven会把前面到此阶段都执行下,不信你可以试试。

 

maven命令

除了通过idea的界面操作maven,我们也可以手打命令,不然在linux系统上你怎么搞?

  • 清理 mvn clean
  • 编译 mvn compile
  • 测试 mvn test
  • 打包 mvn package
  • 安装 mvn install
  • 发布 mvn deploy

执行maven构建命令,必须在pom.xml所在的目录

 

根据maven生命周期,当你执行mvn install时, compile、test、package、intall会依次执行,mvn deploy同理。

实际开发中,我都是直接敲命令编译打包安装,用得最多的是

mvn clean install -Dmaven.test.skip=true -U

加上clean是先把文件清理干净,100%确保install后是最新修改的文件。

如果当前项目并不需要被任何其他项目依赖,就没必要install了,执行mvn clean package即可

mvn命令支持带参数

  • -U :该参数能强制让Maven检查所有SNAPSHOT依赖更新,确保集成基于最新的状态。
  • -Dmaven.test.skip=true :不执行测试用例,也不编译测试用例类。
  • -DskipTests :不执行测试用例,但编译测试用例类生成相应的class文件至target/test-classes下。
  • -P : 多环境打包(后面会说)

插件

maven仓库除了保存jar包,还有插件。核心程序仅定义了抽象的生命周期,具体工作还是得由特定的插件来完成。

一般我使用maven-compiler-plugin 插件来编译,定义的jdk版本一定要跟你开发使用的jdk版本一致,不然后续打包会出错等各种奇怪问题。

pom.xml的build节点里

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
    </plugins>
</build>

依赖范围

标识jar包能被管理的范围。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.3</version>
    <scope>compile</scope>
</dependency>
  • compile:依赖项目参与编译、测试、部署运行
  • test:仅参与测试,包括测试代码的编译、运行
  • provided:可以参与编译、测试,但打包不会引入

一张表格来总结

 

上述3个比较常见,还有另外3个不常见的

  • runtime:无需参与编译、后期测试和运行周期需参与,一般用于数据库驱动jar包
  • system:jar包从本地次磁盘路径拿,不用在maven仓库,一般加个systemPath节点表示具体路径
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.3</version>
    <scope>system</scope>
    <systemPath>../xx/xx.jar</systemPath>
</dependency>
  • import:导入另一jar包的东西,仅支持在<dependencyManagement>中定义(后续有例子)

注意:非compile的依赖不能传递!

打包方式

packaging节点里

<groupId>com.goku</groupId>
<artifactId>goku-manage</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
  • pom:依赖文件打包, 打出来可以作为其他项目的maven依赖,子工程继承父工程,子工程可直接使用父工程的配置,一般用在聚合工程来做jar包的版本控制。
  • jar:普通项目打包,开发时要引用通用类,打成jar包便于存放管理。SpringBoot内置tomcat,打成jar可直接运行。
  • war:web项目打包,打成war包部署到服务器。

聚合与继承

聚合工程里有多个pom.xml

父工程通过modules聚合子工程,该工程执行maven命令时,2个子工程也会执行

<modules>
    <module>goku-user</module>
    <module>goku-order</module>
</modules>

子工程通过parent标签继承父工程,可直接引用父工程的配置项

<parent>
    <groupId>com.goku</groupId>
    <artifactId>goku-manage</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>

至于聚合模块的好处有很多,真真切切让我感受到的有其二

  1. 依赖包划分合理,职责结构清晰,不存在一个庞大的pom.xml。
  2. 内部代码改动后只需要编译当前工程,耗时减少。特别适合分布式项目。

统一版本号

聚合工程里,版本号可以统一配置,统一管理。同个公司的不同项目,所有依赖包版本最好相同。

有些公司会单独建一个工程,只用来统一版本号,打包成pom形式,给公司内部各个项目继承。

顶层父工程的pom文件里的properties节点

<properties>
    <java.version>1.8</java.version>
    <plugin.jdk.version>1.8</plugin.jdk.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring-boot.version>2.1.3.RELEASE</spring-boot.version>
    <fasterjson.version>1.2.12</fasterjson.version>
</properties>

dependency的vesion引用配置好的版本号

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

dependencyManagement统一管理版本

请注意<scope>import</scope>

<dependencyManagement>
    <dependencies>
        <dependency>
            <!-- Import dependency management from Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.0.0.RELEASE</version>
            <type>pom</type>
            <!--导入spring-boot-dependencies里的配置,一定程度上解决了单继承问题。-->
            <scope>import</scope>
         </dependency>
         <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fasterjson.version}</version>
        </dependency>
     </dependencies>
 </dependencyManagement>

说明:

  1. dependencyManagement只是声明依赖,不会引入依赖。
  2. 子模块不会继承父模块中dependencyManagement所有预定义的depandency,若想使用还需引入需要的依赖,只不过不需要加version节点了。
  3. 据说在父模块中严禁直接使用depandencys预定义依赖,应该做到按需引入。

多环境打包

项目打包时区分环境,实际上是根据不同环境生成不同的配置文件。

一般区分本地、开发/测试、线上。

pom.xml里的build和profiles(以下配置仅针对SpringBoot的yml文件)

<build>
    <!-- 打包后文件名称:项目名-环境-版本 -->
    <finalName>${project.artifactId}</finalName>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <!-- 开启过滤替换功能-->
            <filtering>true</filtering>
            <includes>
                <!-- 项目打包完成的包中只包含当前环境文件 -->
                <include>application.yml</include>
                <include>application-${profileActive}.yml</include>
                <include>**/*.xml</include>
                <include>**/*.properties</include>
            </includes>
        </resource>
    </resources>
</build>


<!-- 多环境配置方案 -->
<profiles>
    <profile>
        <id>local</id>
        <properties>
            <profileActive>local</profileActive>
        </properties>
        <activation>
            <!-- 默认情况下使用本地开发配置 如 打包时不包含 -p 参数-->
            <activeByDefault>true</activeByDefault>
        </activation>
    </profile>
    <!-- 打包命令package -P test -->
    <profile>
        <id>test</id>
        <properties>
            <profileActive>test</profileActive>
        </properties>
    </profile>
    <!-- 打包命令package -P prod -->
    <profile>
        <id>prod</id>
        <properties>
            <profileActive>prod</profileActive>
        </properties>
    </profile>
</profiles>

maven命令带参数-P,如:mvn clean install -P test

项目打包后只保留application.yml、ymlapplication-test.yml

application.yml里的@profileActive@也会被替换成test

 

最终打包生成的文件

 

setting.xml

localRepository

指定本地仓库地址,不配置就放在默认路径${user.home}/.m2/repository

<localRepository>D:\develop\maven\Repmaven</localRepository>

server

公司搭建了maven***,上传下载需要认证,就需要配置。

NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings are used together.

可配置账号密码 或 私钥形式,支持多个server

<servers>
    <server>
        <!--id需要与repository或mirror中的id相对应-->
        <id>server01</id>
        <username>admin</username>
        <password>SwaDmin159</password>
    </server>
    <server>
        <id>server02</id>
        <privateKey>/xxx/.ssh/id_dsa</privateKey>
        <passphrase>some_passphrase</passphrase>
        <filePermissions>664</filePermissions>
        <directoryPermissions>775</directoryPermissions>
        <configuration></configuration>
    </server>
</servers>

mirror

仓库镜像,中央仓库的代理,如果配置了镜像,就直接去镜像中下载依赖。它相当于一个拦截器,拦截maven对默认中央仓库的请求,把请求里的远程仓库地址,重定向到mirror里配置的地址。

  <mirrors>
     <mirror>
         <id>nexus-aliyun</id>
         <mirrorOf>central</mirrorOf>
         <name>Nexus aliyun</name>
         <url>http://maven.aliyun.com/nexus/content/groups/public</url>
    </mirror>
  </mirrors>

profile and activeProfiles

所有项目使用***仓库,可在setting.xml配置,profile节点配置repositories,最后再激活profile。

当然,***里不只一个仓库,可配置多个repository。

 <profile>
     <repositories>  
        <repository>  
            <!-- id需要与***里仓库的id对应 -->
            <id>public</id>  
            <name>public</name>  
            <url>http://xxx.com/nexus/content/groups/public/</url>             
            <layout>default</layout>              
        </repository> 
    </repositories>  
</profile>

<activeProfiles>
    <activeProfile>public</activeProfile>
</activeProfiles>

若只想针对某个项目,则在当前项目的pom.xml配置***,repositories节点下配置即可

<repositories>
    <repository>
        <!-- id需要与***里仓库的id对应 -->
        <id>public</id>
        <name>public</name>
        <url>http://xxx.com/nexus/content/groups/public/</url>             
        <releases>
            <enabled>true</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>

distributionManagement

有时我们自己开发的模块需要被其他部门的开发人员使用,就需要发布到***。

执行mvn deploy前,需配置distributionManagement。

快照版本跟发布版本分别存储到不同仓库地址。

<distributionManagement>
    <repository>
        <id>release</id>
        <name>Project Release</name>
       <url>http://xxx.com/nexus/content/groups/release/</url>             
    </repository>
    <snapshotRepository>
        <id>snapshots</id>
        <name>Project SNAPSHOTS</name>
           <url>http://xxx.com/nexus/content/groups/snapshot/</url>      
    </snapshotRepository>
</distributionManagement>

快照版本vs发布版本

maven会根据jar包的版本号(version节点)是否带有-SNAPSHOT来判断是快照版本还是正式版本。没有SNAPSHOT都认为是快照版本。

快照版本 :1.0.0-SNAPSHOT

发布版本 :1.5.3.RELEASE

如果是快照版本,在mvn deploy时会自动发布到快照版本库中,覆盖老的快照版本,后面使用快照版本模块时,在不更改版本号的情况下,直接编译打包时,maven会自动从远程服务器上下载最新的快照版本。

如果是正式发布版本,在mvn deploy时会自动发布到正式版本库中,后面使用正式版本的模块,在不更改版本号的情况下,编译打包时如果本地已经存在该版本的模块则不会主动去远程服务器上下载。

这个也容易理解,快照版本一般定义为不稳定版本,需要实时更新。