Maven
为什么要使用 Maven?
- 添加第三方 jar 包
在今天的 JavaEE 开发领域,有大量的第三方框架和工具可以供我们使用。要使用这些 jar 包最简单的方法就是复制粘贴到 WEB-INF/lib 目录下。但是这会导致每次创建一个新的工程就需要将 jar 包重复复制到 lib 目录下,从而造成工作区中存在大量重复的文件,让我们的工程显得很臃肿。而使用 Maven 后每个 jar 包本身只在本地仓库中保存一份,需要 jar 包的工程只需要以坐标的方式简单的引用一下就可以了。不仅极大的节约了存储空间,让项目更轻巧,更避免了重复文件太多而造成的混乱。 - jar 包之间的依赖关系
jar 包往往不是孤立存在的,很多 jar 包都需要在其他 jar 包的支持下才能够正常工作,我们称之为jar 包之间的依赖关系。最典型的例子是:commons-fileupload-1.3.jar 依赖于 commons-io-2.0.1.jar,如果没有 IO 包,FileUpload 包就不能正常工作。
那么问题来了,你知道你所使用的所有 jar 包的依赖关系吗?当你拿到一个新的从未使用过的 jar
包,你如何得知他需要哪些 jar 包的支持呢?如果不了解这个情况,导入的 jar 包不够,那么现有的程序将不能正常工作。再进一步,当你的项目中需要用到上百个 jar 包时,你还会人为的,手动的逐一确认它们依赖的其他 jar 包吗?这简直是不可想象的。
而引入 Maven 后,Maven 就可以替我们自动的将当前 jar 包所依赖的其他所有 jar 包全部导入进来,无需人工参与,节约了我们大量的时间和精力。用实际例子来说明就是:通过 Maven 导入
commons-fileupload-1.3.jar 后,commons-io-2.0.1.jar 会被自动导入,程序员不必了解这个依赖关系。 - 获取第三方 jar 包
JavaEE 开发中需要使用到的 jar 包种类繁多,几乎每个 jar 包在其本身的官网上的获取方式都不尽相同。为了查找一个 jar 包找遍互联网。不仅如此,找的 jar 包里有的时候并没有需要的那个类,又或者获取的 jar 包是不规范的。
使用 Maven 我们可以使用一个完全统一规范的 jar 包管理体系。只需要在你的项目中以坐标的
方式依赖一个 jar 包,Maven 就会自动从中央仓库进行下载,并同时下载这个 jar 包所依赖的其他 jar 包。 - 将项目拆分成多个工程模块
随着 JavaEE 项目的规模越来越庞大,开发团队的规模也与日俱增。一个项目上千人的团队持续开发很多年对于 JavaEE 项目来说再正常不过。那么我们想象一下:几百上千的人开发的项目是同一个 Web工程。那么架构师、项目经理该如何划分项目的模块、如何分工呢?这么大的项目已经不可能通过 package 结构来划分模块,必须将项目拆分成多个模块协同开发。多个模块工程中有的是 Java 工程,有的是 Web 工程。
什么是 Maven?
简介
Maven 是 Apache 软件基金会组织维护的一款自动化构建工具,专注服务于 Java 平台的项目构建和依赖管理。
构建
构建并不是创建,创建一个工程并不等于构建一个项目。构建是指以我们编写的 Java 源代码、框架配置文件等其他资源文件、JSP 页面和图片等静态资源作为“原材料”,去“生产”出一个可以运行的项目的过程。构建包括以下几个过程:
- 清理:删除以前的编译结果,为重新编译做好准备。
- 编译:将 Java 源程序编译为字节码文件。
- 测试:针对项目中的关键点进行测试,确保项目在迭代开发过程中关键点的正确性。
- 报告:在每一次测试后以标准的格式记录和展示测试结果。
- 打包:将一个包含诸多文件的工程封装为一个压缩文件用于安装或部署。Java 工程对应 jar 包,Web工程对应 war 包。
- 安装:在 Maven 环境下特指将打包的结果——jar 包或 war 包安装到本地仓库中。
- 部署:将打包的结果部署到远程仓库或将 war 包部署到服务器上运行。
编译后的目录结构
约定的目录结构
要使 Maven 能够自动构建项目,必须让项目的目录结构和下面的结构相同:
安装
- 在 Maven官网 上下载最新版本的 Maven 压缩包,将其解压在适当的目录下,注意,全路径名不能有中文和空格。
- 配置环境变量:
新建变量名:MAVEN_HOME或M2_HOME
,变量值为 Maven 的解压路径。
在 Path 中追加路径:%MAVEN_HOME%\bin
- 测试是否安装成功:
在命令行中输入:mvn -v
,若显示 Maven 版本信息,则安转成功。 - 修改本地仓库位置:
Maven 本地仓库的默认位置为:C:\Users\MCC(用户名)\.m2 下,在 Maven的安装目录下,打开 conf\settings.xml 文件,在<localRepository>
标签中设置本地仓库地址。
常用命令
注意:执行下列命令,必须先进入 pom.xml 文件所在的目录(工程目录)。
命令 | 功能 |
---|---|
mvc clean | 清理 |
mvc compile | 编译主程序 |
mvc test-compile | 编译测试程序 |
mvc test | 执行测试 |
mvc package | 打包 |
mvc install | 注册进入本地仓库 |
mvn site | 生成站点 |
注意:多个命令可以同时执行,如
POM、坐标、仓库
-
POM:Project Object Model,项目对象模型。将 Java 工程的相关信息封装为对象作为操作和管理的模型。pom.xml 是 Maven 工程的核心配置文件,可以说学习 Maven 就是学习 pom.xml 文件中的配置。
-
坐标:Maven 使用如下三个坐标在 Maven 仓库中唯一确定一个 Maven 工程。
groupId:公司或组织的域名倒序+项目名称
artifactId:模块名称
version:版本号 -
坐标与仓库中路径的对应关系:
<groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.2.6.RELEASE</version>
对应的仓库路径为:org/springframework/spring-core/5.2.6.RELEASE/spring-core-5.2.6.RELEASE.jar
-
仓库的分类:
(1)本地仓库。
(2)远程仓库:私服、中央仓库、中央仓库镜像。
仓库中保存的内容:
(1)Maven 自身所需要的插件。
(2)第三方框架或工具所需要的 jar 包。
(3)自己开发的 Maven 工程。
依赖
自己的工程
Maven 在解析依赖信息时,先在本地仓库中查找所依赖的 jar 包,若查找失败,则连接外网下载 jar 包。当某个模块需要依赖的信息是我们之前自己创建的类时,需要先将被依赖的类注册到 Maven 的本地仓库中,之后才可以被依赖。
在需要注册装入仓库的工程(或模块)目录下,使用命令:mvc install
,即可将该工程(或模块)装入仓库。
注意:将某个工程装入仓库,不仅仅是将该工程打包成 jar 包,还会生成一些其他文件,所以不能简单理解为注册装入仓库,就是将该工程打成 jar 包(mvc package 才仅仅是将工程打成 jar 包)。
依赖的范围
Maven 提供四种依赖:compile、test、provided、runtime。
- compile:编译、测试、运行时均有效。
- 对主程序:有效
- 对测试程序:有效
- 打包、部署:是
- test:测试时有效。
- 对主程序:无效
- 对测试程序:有效
- 打包、部署:否
- 典型例子:junit
- provided:编译、测试时有效。
- 对主程序:有效
- 对测试程序:有效
- 打包、部署:否
- 典型例子:servlet、jsp 等 tomcat 可以提供的 jar 包
- runtime:测试、运行时有效。
- 对主程序:有效
- 对测试程序:有效
- 打包、部署:是
- 典型例子:JDBC
注意:依赖的范围一定要写对,否则可能会报错,例如将 servlet-api.jar 声明为 compile,因为部署时,tomcat 也会提供一份 servlet-api.jar,就有可能导致 jar 包冲突,发生异常。
依赖的传递性
A 依赖 B,B 依赖 C,A 能否使用 C 呢?此时需要看 B 依赖 C 的范围是否为 compile,若是,则可以使用,否则不可以使用。
好处:依赖的传递性可以使各个模块不必引入相同的依赖,减少工程 jar 包的冗余。
注意:由于只有 compile 范围的依赖才可以被传递,因此如果需要使用非 compile 范围的依赖就必须重复声明。
依赖的排除
如果我们在当前工程中引入了一个依赖 A,而 A 又依赖了 B,由于依赖的传递性,Maven 会自动将 A 依赖的 B 引入当前工程,但是在个别情况下,B 有可能是一个不稳定的版本,或对当前工程有不良影响,这时我们就需要在引入 A 时将 B 排除。
<!-- 假设模块需要引入依赖MavenWeb,但是MavenWeb所依赖的logging是我们不想引入的,
此时可以通过<exclusion>标签进行依赖的排除
-->
<dependency>
<groupId>com.mcc</groupId>
<artifactId>MavenWeb</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
依赖的原则
当模块中有多个相同的 jar 包时,Maven 通过下面的原则,解决 jar 包冲突问题。
当然,每个模块都以自己的 pom.xml 中配置的版本为准。上面的例子中,工程A的 pom.xml 中没有配置 log4j 依赖,如果配置了,则要以自己配置的版本为准。
统一管理所依赖 jar 包的版本
项目中同一个框架所需要的核心 jar 包最好使用相同版本,避免不必要的错误,如 spring、springMVC 使用的版本号最好相同。此时如果需要统一升级框架的版本,在 pom.xml 文件中逐个修改比较浪费时间,可以使用<properties>
标签实现 jar 包版本号的统一修改。
情境:现在需要将项目中的 spring 框架升级到 5.3.4。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
实现方式:
(1)在<properties>
标签内自定义一个标签,标签名任意,标签内容为版本号。
<properties>
<spring.version>5.3.4</spring.version>
</properties>
(2)在声明依赖版本号的<version>
标签内,使用${自定义的标签名}
,引入声明的版本号。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
此外,properties 标签内还可以声明项目的编码格式等内容,如:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<spring.version>5.3.4</spring.version>
</properties>
继承
由于 test 范围的依赖不能传递,如 junit,此时每个模块中都有自己的 junit,如果想同一管理 junit 的版本,会很麻烦。“继承”可以解决这个问题。
实现方式:
(1)创建一个 Maven 工程作为父工程,注意:父工程的打包方式要为 pom。<packaging>pom</packaging>
。
<groupId>com.mcc.maven</groupId>
<artifactId>MavenParent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
(2)在子工程中使用<parent>
标签,声明父工程。其中,relativePath 标签是以当前模块为起点的父工程的 pom.xml 文件的相对路径。
<parent>
<artifactId>MavenParent</artifactId>
<groupId>com.mcc.maven</groupId>
<version>1.0-SNAPSHOT</version>
<!--以当前模块为起点的父工程的pom.xml文件的相对路径-->
<relativePath>../pom.xml</relativePath>
</parent>
(3)删除子工程的<groupId>、<version>
标签,由于子工程属于父工程的一部分,因此子工程与父工程在同一个 groupId 下,版本号可以不同,但一般情况下也应该相同,所以子工程中的这两个标签需要删除。
<artifactId>SubWeb</artifactId>
(4)在父工程中使用<dependencyManagement>
标签统一管理 test 范围的依赖,如 junit。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
(5)删除子工程中 junit 依赖中的<version>
标签。
当然,如果子工程使用的 junit 版本,确实需要和父工程不同,可以保留 dependency 标签中的 version 标签,相当于对父工程 junit 依赖版本的重写。
注意:上述过程的 (1) (2) (3) 步,在使用 idea 创建 Maven 工程时,可以自动实现。选择要继承的父工程之后,就已经自动配置好了,我们只需要进行第 (4) 步即可。
注意:
为什么在父类中写了 Junit 依赖,子类中还要写呢?
首先要明白 dependencies 与 dependencyManagement 的区别:
-
dependencies 即使在子项目中不写该依赖项,那么子项目仍然会从父项目中继承该依赖项,如果父类有很多依赖,这样没用的也继承来,会造成臃肿。
-
dependencyManagement 里只是声明依赖,并不实现引入,因此子项目需要显式的声明需要用的依赖。如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且 version 和 scope 都读取自父类 pom;另外,如果子项目中指定了版本号,那么会使用子项目中指定的 jar 包版本。
聚合
- 为什么要使用聚合?
将多个工程拆分为模块后,需要手动逐个安装到仓库后依赖才能够生效。修改源码后也需要逐个手动进行 clean 操作。而使用了聚合之后就可以批量进行 Maven 工程的安装、清理工作。 - 实现方式:
在总的聚合工程中使用<modules><module>
标签组合,指定子工程的相对路径即可。总的聚合工程可以是继承时使用的父工程,相对路径以父工程的 pom.xml 文件为起点。
<modules>
<module>SubJava</module>
<module>SubWeb</module>
</modules>
查询依赖信息
我们可以到 MavenRepository 搜索需要的 jar 包的依赖信息。
生命周期
Maven 有三套相互独立的生命周期,分别是:
- Clean Lifecycle 在进行真正的构建之前进行一些清理工作。
- Default Lifecycle 构建的核心部分,编译,测试,打包,安装,部署等等。
- Site Lifecycle 生成项目报告,站点,发布站点。
它们是相互独立的,可以只调用 clean 来清理工作目录,只调用 site 来生成站点。当然你也可以直接运行mvn clean install site
运行所有这三套生命周期。
运行任何一个阶段的时候,它前面的所有阶段都会被运行,例如我们运行 mvn install 的时候,代码会被编译,测试,打包。
idea 中配置 Maven
在 idea 中创建 Maven 版的 Java 工程
创建成功后的项目结构为:
在 idea 中执行 Maven 命令
在 idea 中创建 Maven 版的 Web 工程
自动创建所有目录结构
在 idea 中创建的 Maven 版 Web 工程,目录结构中只有 src/main/webapp,可以选择每次都手动新建目录,也可以采用修改配置文件的方式,让 idea 为我们创建。具体配置方式点击链接查看
配置文件修改完成后,按照如下步骤新建 Web 项目:
修改 web.xml 头文件版本
Maven 构建的 web 项目,使用的 webapp 版本比较低,只有2.3,不支持 jsp 自动解析 EL 表达式、servlet 的注解使用等,因此要修改 web.xml 的头文件为:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="false">
</web-app>
每次都要修改比较麻烦,可以在配置文件中修改,使得每次生成的 web.xml 版本都提高到4.0,操作方式点击链接查看
解决在 idea 中使用 Maven 骨架生成 web 项目很慢的问题
在使用 webapp 创建 Maven 版的 web 项目时,有时等待很久,Maven 都没有将工程的目录结构创建出来,此时可以在创建 web 项目时,按照上图,为项目添加一条属性name=archetypeCatalog, value=internal
,即可解决问题。