Maven

类似 iOS 的 cocoaPod,是一个跨平台的项目管理工具,作用域 Java平台项目,使用命令行完成重复,繁琐的构建。从中央仓库帮我们下载并管理项目依赖的 jar 包,自动管理这些 jar 包依赖的其他 jar 包。

boot 中放着 maven 的类加载器

conf 中

setting.xml 配置 全局的文件

额外的配置:Maven 在构建大型业务项目的时候,十分耗内存,可以为 Maven 的运行配置 JVM 内存:设置 Maven_OPTS 环境变量(中间是空格):-Xms128m Xmx512m

第一个 marven 程序


  1. Maven 规定了一套默认的项目格式:
  • src/main/java – 存放项目的 .java 文件
  • src/main/resources – 存放项目资源文件,如 spring、structs2 配置文件,db.properties
  • src/main/webapp – 存放 jsp,css,image 等文件
  • src/test/java – 存放所有测试 .java 文件,如 JUnit 测试类
  • src/test/resources – 测试资源文件(如果不写,默认和 src/main/resources 一样)
  • pom.xml – 主要要写的 maven 配置文件
  • target – 项目由 maven 自动输出位置
  1. 按照下面的目录结构创建一个目录结构


节点 描述
project 工程的根标签
modelVersion 模型版本需要设置为 4.0
groupId 工程组的唯一标识,在组织或者项目中是唯一的,例如一个银行组织 com.companyname.project-group 拥有所有和银行相关的项目
artifactId 工程的标识,通常是工程的名称,groupId和artifactId 一起定义了 artifact 在仓库中的位置
version 工程的版本号,在 artifact 仓库中,用来区分不同的版本 com.company.bank:consumer-banking:1.0 com.company.bank:consumer-banking:1.1

切换到项目根目录下(在可以看到pom.xml的文件夹下),分别执行下列命令并观察:

  1. 执行 mvn compile 命令
    • 编译源代码
  2. 行mvn clean命令
    • 清除产生的项目
  3. 执行mvn compile命令
  4. 执行mvn test命令
    • 运行测试
  5. 执行mvn package命令
    • 打包
  6. 执行mvn site命令
    • 产生 site
  7. 执行mvn install命令
    • 在本地 Repository 中安装 jar
    • 目录:/Users/Ammar/.m2

通过 maven 命令生成 maven 项目骨架


执行命令:mvn archetype:generate

可以看到有很多的archetype,每个前面有一个序号,输入序号(或者直接回车,选的就是quick-start):

  • groupId:cn.lizhaoloveit.maven
  • artifactId:HelloMaven
  • Define value for property ‘version’ 1.0-SNAPSHOT : 1.0
  • package:/n

自动创建所有目录

mvn archetype:create -DgroupId=[your group id] -DartifactId=[your archetype id] -DarchetypeArtifactId=maven-archetype-webapp

如果mvn archetype:generate列出内容过多,或者下载过慢,运行:

mvn archetype:generate -DarchetypeCatalog=internal

只列出系统内部有的archetype;

Maven 安装 jar 包


mvn install:install-file -Dfile=jar包的位置 -DgroupId=上面的groupId -DartifactId=上面的artifactId -Dversion=上面的version -Dpackaging=jar

eclipse 关联 Maven


2019-07-20 at 7.10 P

构建 web 项目


2019-07-20 at 9.56 P

web.xml is missing and <failOnMissingWebXml> is set to true

报错,缺少 web.xml,在 src/main/webapp/ 下创建 WEB-INFO 文件夹和 web.xml 文件。

导入需要依赖的 jar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<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>cn.lizhaoloveit</groupId>
<artifactId>maven-webapp</artifactId>
<version>0.1</version>
<packaging>war</packaging>
<name>marven_web</name>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>

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

</project>

安装 tomcat7 插件,


1
2
3
4
5
6
7
8
9
10
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>80</port>
<path>/</path>
<uriEncoding>UTF-8</uriEncoding><!-- 针对GET 的乱码处理 -->
</configuration>
</plugin>

pom-war-jar


pom 工程:用在父级工程或聚合工程中,用来做 jar 包的版本控制

war 工程:将会打包成 war,发布在服务器上的工程,如网站或服务

jar 工程:将会打包成 jar,用作 jar 包使用

jdk9 后还可以打包 jmod(Java 模块化),厉害的是这个通过 jlink命令,可以直接将jmode打包为对应环境的可执行程序,告别了让用户安装 jdk 的步骤,现在也有对应的 maven 插件了

以淘淘商城为例:

pom 工程


  1. taotao-parent :
    • 是整个项目的父工程,它是一个 pom 工程,用来做整个项目的版本控制,也就是项目中所有要使用到的 jar 包的版本都集中由父工程管理。这样你在写其他工程 pom 文件中 maven 依赖时就不需要写版本号了,当然所有的项目都要先继承它才行
  2. taotao-manager
    • 创建 taotao-manager 用来做聚合工程,它也是一个 pom 工程,创建四个 model 分别是 taotao-manager-pojo, taotao-manager-dao, taotao-manager-service, taotao-manager-web,同时会自动生成 4 个独立的 maven 工程。聚合工程知识用来帮助其他模块构建的工具,本身并没有实质的内容,具体每个工程代码的编写还是在生成的工程中去写。
    • 使用聚合工程 taotao-manager 的意义就是:原本这些模块也是一个个独立的工程,现在将它们聚合到 taotao-manager 中,这样我们构建项目的时候就只要构建 taotao-manager一个就行了,我们只要使用 maven 构建这个聚合工程 taotao-manager 就行了不用去操心模块的构建,比如 install 时,只要 install taotao-manager 就行。总之就是简化操作,正常的编码工作还是在对应的 taotao-manager-pojo, taotao-manager-dao, taotao-manager-service,taotao-manager-web 工程中进行的。

war 工程


taotao-rest, taotao-portal 这些

这些功臣个都是要部署在服务器上的,所以要打包成 war 形式,这些工程有的是用户通过浏览器直接访问,有的是通过发布服务被别的工程调用。

jar 工程


taotao-common

这个就是打包成 jar 的工程,它就是存放一些其他工程都会使用的类,工具类。我们可以在其他工程的 pom 文件中去引用它,和引用别的 jar 包没什么区别

1
2
3
4
5
<depandency>
<groupId>com.taotao</groupId>
<artifactId>taotao-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

坐标和依赖


坐标


Maven 中坐标就是用来规范 maven 唯一标示一个构建的方法

  1. groupId:项目名称
  2. artifactId:项目中的 项目/模块名称
  3. version:项目版本
  4. packing:打包的方式,比如 jar、war、pom 等(项目名-版本号.打包方式)
  5. classifier:附属构件,比如source,javadoc等,一般不会直接定义 classifier(分类器,分级器),附加构件不是直接定义的,是插件帮助构件的。

Maven 管理依赖


  • 如何引入 jar 包

在代码开发时,如果需要使用第三方 jar 包提供的类库,那么需要在 pom.xml 加入该 jar 包依赖。

1
2
3
4
5
6
7
8
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/zookeeper -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>zookeeper</artifactId>
<version>3.3.1</version>
</dependency>
</dependencies>
  • 依赖传递

pom.xml 引入 zookeeper jar 包依赖,当 Maven 解析该依赖时,需要引入的 jar 包不仅仅只有 zookeeper,还会有 zookeeper 内部依赖的 jar 包,还会有 zookeeper 内部依赖的 jar 包依赖的 jar 包…,依赖关系不断传递,直至没有依赖。

  • 包冲突的产生

假设 A->B->C->D1, E->F->D2。D1, D2 分别为 D 的不同版本。如果 pom.xml 文件中引入了 A 和 E 后,按照 Maven 依赖传递原则,工程内需要引入的实际 jar 包,A,B,C,D1和E,F,D2,D1和D2产生包冲突

  • 解决包冲突

Maven 解析 pom.xml 文件时,同一个 jar 包只会保留一个,这样会避免引入两个 jar 包导致的工程运行不稳定性。

  1. 最短路径优先
    • Maven 面对 D1 和 D2 时,会默认选择最短路径的那个 jar 包,即D2,E->F->D2比 A->B->C->D1 路径短1.
  2. 最先声明优先
    • 如果路径一样,A->B->C1, E->F->C2 那么久选择最先声明

移除依赖,如果不想通过 A->B->C->D1 引入 D1 的话,那么再声明引入 A 的时候,将 D1 排除。

将 zookeeper 的 jline 依赖排除

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>zookeeper</artifactId>
<version>3.3.1</version>
<exclusions>
<exclusion>
<groupId>jline</groupId>
<artifactId>jline</artifactId>
</exclusion>
</exclusions>
</dependency>
  • 检测包冲突工具
1
2
3
4
mvn dependency:help
mvn dependency:analyze
mvn dependency:tree
mvn dependency:tree -Dverbose

依赖管理解决问题:

当同一个工程内有多个模块时,并且要求多个模块使用某个 jar 包的相同版本,为了方便统一版本号,升级版本号,需要提取出一个父亲模块来管理子模块共同依赖的 jar 包版本。

有两个模块 projectA, projectB,它们依赖分别如下所示:

projectA:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<project>
...
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>group-c</groupId>
<artifactId>excluded-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>

project B

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<project>
...
<dependencies>
<dependency>
<groupId>group-c</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>

projectA 和 projectB 共同依赖了 group-a/artifact-b/1.0,提取公共依赖,生成 parent,parent 依赖如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<project>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

则 projectA 和 projectB 均不需要指定 group-a/artifact-b 的 version 信息,未来升级 version 信息时,只需要在 parent 内部指定。

project A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<project>
...
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>group-c</groupId>
<artifactId>excluded-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
</dependency>
</dependencies>
</project>

projectB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<project>
...
<dependencies>
<dependency>
<groupId>group-c</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
</dependency>
</dependencies>
</project>

依赖范围 scope


scope 的 分类

  • compile

    • 默认就是 compile,compile意味着被依赖的项目需要参与当前项目的编译,后续的测试,运行周期也参与其中,是一个比较强的依赖,打包的时候通常需要包含进去。
  • test

    • 依赖项目仅仅参与测试相关的工作,包括测试代码的编译,执行,比如典型的如 junit。
  • runtime

    • runtime 表示被依赖项目无需参与项目的编译,不过后期测试和运行周期需要参与,与 compile 相比,跳过编译而已,正在终端项目(非开源,企业内部系统)中,和 comiple 区别不大,比较常见的对应的 API jar 是 comiple 的,具体是先是 runtime 的,oracle jdbc 驱动jar 就是个很好的例子,一般 scope 为 runtime ,runtime 依赖通常和 optional 搭配使用,optional 为 true,可以用 A 实现,也可以用 B 实现。
  • provided

    • 意味着打包的时候可以不用包进去,别的设施会提供,该依赖可以参与编译,测试,运行等周期,相当于 compile,但是在打包阶段做了 exclude 的动作。
  • system

    • 被依赖的项目不会从 maven 仓库抓,而是从本地文件系统拿,一定需要配合systemPath属性使用,与 provided相同。

scope 的依赖传递:


A->B->C 当前项目为 A, A依赖于B,B依赖于C,知道B在A项目中的 scope,那么 当 C 是test或者provided时,A 不依赖 C,否则 A依赖C,C的scope继承于B的scope(对于A来说)

optional


依赖的可选:可选项目不会被传递;

如果A项目依赖B项目;B项目依赖C或者D(比如一个基础项目,兼容了MYSQL或者ORACLE,那么他可能依赖mysql-connector或者classes12.jar)

但是在A项目运行的时候,只需要C或者D其中一个就可以了。那么对于B项目来说,C和D项目就可以设置optional为true;在A项目中单独设置C或者D的依赖;

举例:

比如 mybatis 和 log4j 或dbcp

exclusion 排除


移除依赖,如果不想通过 A->B->C->D1 引入 D1 的话,那么再声明引入 A 的时候,将 D1 排除。解决依赖冲突问题

最佳实践


  1. 项目中源代码使用的 jar 包一定在 pom.xml 中显示引用
  2. 经常 check 一下包冲突,检查是否需要处理
  3. 当使用多个模块时, parent 一定要使用包管理模块来规范 jar 包版本,而不是包依赖模块直接引入依赖。

Maven 生命周期


在Maven出现之前,项目构建的生命周期就已经存在,软件开发人员每天都在对项目进行清理、编译、测试及部署。虽然大家都在不停地做构建工作,但公司和公司间、项目和项目间,往往使用不同的方式做类似的工作。有的项目以手工的方式在执行编译测试,有的项目写了自动化脚本执行编译测试。可以想象的是,虽然各种手工方式十分类似,但不可能完全一样;同样地,对于自动化脚本,大家也是各写各的,能满足自身需求即可,换个项目就需要重头再来。

Maven的生命周期就是为了对所有的构建过程进行抽象和统一。

Maven 从大量项目和构建工具中学习和反思,总结了一套完善、易扩展的生命周期,这个生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。几乎所有的项目的构建,都能映射到这样一个生命周期上。

Maven 的生命周期是抽象的,意味着生命周期本身不做任何实际的工作,Maven 的设计中,实际的任务,都交由插件来完成。

每个构建步骤,都可以绑定一个或者多个插件行为,Maven 为大多数构建步骤编写并绑定了默认插件,编译是由 maven-compiler-plugin 完成的,测试是由 maven-surefire-plugin 完成的,当用户有特殊需要的时候,也可以配置插件定制构建行为,甚至自己编写插件。

三套互相独立的生命周期


clean、default、site。

  • clean 生命周期的目的是清理项目,
  • default 生命周期的目的是构建项目,
  • site 生命周期的目的是建立项目站点。

clean

目的是清理项目,包含3个阶段:

  • pre-clean 执行一些清理前需要完成的工作
  • clean 清理上一次构建生成的文件
  • post-clean 执行一些清理后需要完成的工作

default 生命周期

default 生命周期定义了 真正构建时所需要执行的所有步骤,阶段如下:

  1. validate
  2. initialize
  3. generate-sources
  4. process-sources 处理项目主资源文件,一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主 classpath 目录中
  5. generate-resources
  6. process-resources
  7. compile 编译项目的源码,编译 src/mian/java 目录下的 java 文件至项目输出的主 classpath 目录中
  8. process-classes
  9. generate-test-sources
  10. process-test-sources 处理项目测试资源文件,一般来说,是对 src/test/resources 目录的内容进行变量替换等工作,复制到项目输出的测试 classpath 目录中
  11. generate-test-resources
  12. process-test-resources
  13. test-compile 编译项目的测试代码,一般来说,是编译src/test/java 目录下的 Java文件至项目输出的测试 classpath目录中
  14. process-test-classes
  15. test 使用单元测试框架运行测试,测试代码不会被打包或者部署
  16. prepare-package
  17. package 接收编译好的代码,打包成可发布的格式,如jar
  18. pre-integration-test
  19. integration-test
  20. post-integration-test
  21. verify
  22. install 将包安装到 Maven 本地仓库,供本地其他 Maven 项目使用
  23. deploy 最终将包复制到远程仓库。

site 生命周期

目的是建立和发布项目站点,Maven能够基于 pom 所包含的信息,自动生成一个站点,方便团队交流和发布项目信息。生命周期如下:

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

package 和 install 的区别

  • mvn clean package依次执行了clean、resources、compile、testResources、testCompile、test、jar(打包)等7个阶段。

  • mvn clean install依次执行了clean、resources、compile、testResources、testCompile、test、jar(打包)、install等8个阶段。

  • mvn clean deploy依次执行了clean、resources、compile、testResources、testCompile、test、jar(打包)、install、deploy等9个阶段。

  • package命令完成了项目编译、单元测试、打包功能,但没有把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库和远程maven私服仓库

  • install命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库,但没有布署到远程maven私服仓库

  • deploy命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库和远程maven私服仓库

搭建 maven 私服


文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2019/07/20/Maven/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论