一.概述
1.1 目前技术存在的问题
1.一个项目就是一个工程
如果项目非常庞大,就不适合继续使用package来划分模块。最好是每一个模块对应一个项目,利用分工协作。借助于Maven就可以将一个项目拆分成多个工程。
2.项目中需要的jar包必须手动“复制”,“粘贴”到WEB-INF/lib项目下
同样的jar包文件重复出现在不同的项目工程中,一方面是浪费存储空间,另外也让工程比较臃肿。借助Maven,可以将jar包仅仅保存在仓库中,有需要使用的工程“引用”这个文件,并不需要重复复制。
3.jar包需要别人帮我们准备好,或到官网下载
所有知名框架或第三方工具jar包已经按照统一规范放在Maven的中央仓库中。
4.一个jar包依赖的其他jar包需要自己手动加到项目中
Maven会自动将被依赖的jar包导入进来。
1.2 Maven是什么?
Maven 是 Apache 软件基金会组织维护的一款自动化构建工具,专注服务于 Java 平台的项目构建和依赖管理 。
构建:就是以我们编写的Java代码、框架配置文件、国际化等其他资源文件、jsp页面和图片等静态资源作为“原材料”,去“生产”出一个可以运行的项目的过程。
构建过程中的几个主要环节
①清理:删除以前的编译结果,为重新编译做好准备。
②编译:将Java源程序编译为字节码文件。
③测试:针对项目中的关键点进行测试,确保项目在迭代开发过程中关键点的正确性。
④报告:将每一次测试后以标准的格式记录和展示测试结果。
⑤打包:将一个包含诸多文件的工程封装为一个压缩文件用于安装或部署。Java工程对应jar包,Web工程对象war包。
⑥安装:在Maven环境下特指将打包的结果——Jar包或War包安装到本地仓库中。
⑦部署:将打包的结果部署到远程仓库或将war包部署到服务器上运行。
1.3 安装Maven核心程序
1.检查JAVA_HOME环境变量
2.解压Maven核心程序的压缩包,放在一个非中文、无空格 的路径下。
3.配置Maven相关的环境变量
①MAVEN_HOME 或 M2_HOME
②path
4.验证:运行 mvn -v 命令查看Maven版本
Maven的核心程序中仅仅定义了抽象的生命周期,而具体操作则是由Maven的插件来完成的,可以Maven的插件并不包含在Maven的核心程序中,首次使用需要联网下载。
下载得到的插件会被保存在本地仓库中,本地仓库的默认位置是:~\.m2\repositoy。
二.Maven的核心概念
2.1约定的目录结构
约定的目录结构对于Maven实现自动化构建是必不可少的一个环节,就拿自动编译来说,Maven必须找到java 源文件,下一步才能编译,而编译之后也必须有一个准确的位置保持编译得到的字节码文件。
我们在开发中如果需要让第三方工具或框架知道我们创建的资源在哪,那么基本就是两种方式
1)以配置的方式明确告诉框架,如:
< param-value>classpath:spring-context.xml < /param-value>
2)遵守框架内部已经存在的约定,如如log4j的配置文件名规定必须为 log4j.properties 或 log4j.xml 。
注意:约定>配置>编码
Maven对工程目录结构的要求就是属于后面一种。
2.2 常用的Maven命令
执行与构建过程相关的Maven命令,必须进入pom.xml 所在的目录。
有构建过程相关:编译,测试,打包....
常用的命令:
- mvn clean : 清理
- mvn compile : 编译主程序
- mvn test-compile : 编译测试程序
- mvn test : 执行测试
- mvn package : 打包
- mvn install : 安装
- mvn site :生成站点
Maven命令会有关联网问题:
- Maven 的核心程序中仅仅定义了抽象的生命周期,但是具体的工作必须有特定的插件来完成。而插件本身不包含在Maven核心程序中。
- 当我们执行的Maven命令需要用到某些插件时,Maven核心程序会首先到本地仓库中查找。
- 本地仓库的默认位置:[系统登陆用户的家目录] \ .m2\repository
- Maven核心程序如果在本地仓库中找不到需要的插件,那么它会自动连接外网,到中央仓库下载。
- 如果此时无法连接外网,则构建失败。
- 修改默认本地仓库的位置可以让Maven核心程序到我们事先准备好的目录下查找插件
①找到Maven解压目录\conf\settings.xml
②在setting.xml 文件中找到 localRepository 标签
③将 < localRepository>/path/to/local/repo< /localRepository>从注释中取出
④将标签体内容修改为自定义的Maven仓库目录
2.3 POM
Project Object Model:项目对象模型。将Java工程的相关信息封装为对象作为便于操作和管理的模型。
pom.xml 对于 Maven工程是核心配置文件,与构建过程相关的一切设置都在这个文件中进行配置。可以说学习Maven就是学习pom。xml文件中的配置。
2.4 坐标
为各种使用下面三个向量在仓库中唯一定位一个Maven工程:
(1)groupid:公司或组织域名倒序+项目名
<groupid>/groupid>
(2)artifactid:模块名
< artifactid>Hello< /artifactid>
(3)version:版本
< version>1.0.0< /version>
Maven 工程的坐标与仓库中路径的对应关系,以spring为例:
< groupId>org.springframework< /groupId>
< artifactId>spring-core< /artifactId>
< version>4.0.0.RELEASE< /version>
org/springframework/spring-core/4.0.0.RELEASE/spring-core-4.0.0.RELEASE.jar
注意:我们自己的 Maven 工程必须执行安装操作才会进入仓库。安装的命令是:mvn install
2.5 仓库
仓库的分类:
(1)本地仓库:当前电脑部署的仓库目录,为当前电脑上的所有Maven工程服务
(2)远程仓库:
①私服:搭建在局域网环境中,为局域网范围内的所有Maven工程服务
②中央仓库:假设在Internet上,为全世界所有Maven工程服务
③中央仓库镜像:为了分担中央仓库的流量,提升用户访问速度
仓库中保存的内容:Maven工程
(1)Maven自身所需要的插件
(2)第三方框架或工具的jar包
(3)我们自己开发的Maven工程
不管是什么样的jar包,在仓库中都是按照坐标生成目录结构,所以可以通过统一的方式查询或依赖。
2.6 依赖
2.6.1 依赖实验
我们分别创建两个Maven项目,分别是Hello和HelloFriend。在HelloFriend的类中调用了Hello的类:
package com.lianxi.maven;
import com.lianxi.maven.Hello;
public class HelloFriend {
public String sayHelloToFriend(String name){
Hello hello = new Hello();
String str = hello.sayHello(name)+" I am "+this.getMyName();
System.out.println(str);
return str;
}
public String getMyName(){
return "John";
}
}
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
在HelloFriend的pom.xml配置文件中有对Hello的依赖:
<dependency>
<groupId>com.lianxi.maven</groupId>
<artifactId>Hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
尝试对HelloFriend进行编译,会发现编译失败。
当 A jar 包用到了 B jar 包中的某些类时,A 就对 B 产生了依赖,这是概念上的描述。Maven解析依赖信息时会到仓库中查找被依赖的jar包。对于我们自己开发的Maven工程,要使用mvn install 命令安装后就可以进入仓库。
所以对Hello要先执行mvn install命令,然后再编译HelloFriend,就可以执行成功了。
2.6.2 依赖的标签
根元素project下的dependencies可以包含多个dependency元素,以声明一个或多个项目依赖,每个依赖可以包含的元素有:
- groupId,artifactId和version:依赖的基本坐标,对于任何依赖来说,基本坐标最重要,maven根据坐标才能找到对应的依赖;
- type:依赖的类型,对应项目坐标定义的packaging,大部分情况下无须声明,默认是jar;
- scope:依赖的范围;
- optional:标记依赖是否可选;
- exclusions:用来排除传递性依赖;
2.6.3 依赖的范围
首先需要知道,Maven在编译项目主代码的时候需要一套classpath。其次,Maven在编译和执行测试的时候会使用另一套的classpath。最后,实际运行Maven项目的时候,又会使用一套classpath。
依赖范围就是用来控制依赖与这三种classpath(编译classpath,测试classpath,运行classpath)的关系,Maven有以下几种依赖范围:
- compile:编译依赖范围,若未指定依赖范围,会默认该依赖范围,使用该依赖范围的maven依赖,对于编译、测试、运行的classpath都有效;比较典型的就是spring-core,在编译、测试和运行项目的时候都需要使用到该依赖;
- test:测试依赖范围,使用该依赖范围的maven依赖,只对测试的classpath有效;典型的就是Junit,它只有在编译测试代码的时候才有效;
- provided:已提供依赖范围,使用该依赖范围的maven依赖,只对编译和测试的classpath有效,但在运行时无效;典型的就是servlet-api,编译和测试的时候需要该依赖,但在运行项目的时候,由于容器已经提供了,就不需要maven重复的引入一遍;
- runtime:运行时依赖范围,使用此类范围的maven依赖,对于测试和运行的classpath有效,但在编译主代码的时候无效。典型的例子是JDBC驱动的实现,项目住代码只需要JDK提供JDBC的接口,只有在测试或者运行项目的时候才需要实现上述接口的具体实现;
- system:系统依赖范围,使用此类范围的maven依赖,也是对应编译和测试的classpath有效,运行时无效,但是使用该范围的依赖,必须显式的通过systemPath指定依赖的路径(因为此类依赖不是通过maven仓库解析的,而往往与本地机绑定,可移植性比较差,谨慎使用)
- import:导入依赖范围。该依赖范围不会对3种classpath产生实际的影响。
以下是一个简单的表格(Y代表有效,N代表无效);
依赖范围(scope) | 对于编译classpath有效 | 对于测试classpath有效 | 对于运行classpath有效 | 典型例子 |
compile | Y | Y | Y | spring-core |
test | N | Y | N | Junit |
provided | Y | Y | N | servlet-api |
runtime | N | Y | Y | JDBC驱动实现 |
system | Y | Y | N | 本地的,Maven仓库以外的类库文件 |
2.6.4依赖的传递性
A依赖B,B依赖C,A能否使用C呢?那要看B依赖C的范围是不是compile,如果是则可用,否则不可用。
Maven工程 | 依赖范围 | 对A的可见性 | ||
A | B | C | compile | Y |
D | test | N | ||
E | provided | N |
2.6.5 依赖的排除
如果我们当前工程中引入了一个依赖是A,而A又依赖了B,那么Maven会自动将A依赖的B引入当前工程,但是个别情况下B有可能是一个不稳定版本,或对当前工程有不良影响。这时我们可以在引入A的时候将B排除。
情景背景:
配置方式:使用了上面提到的exclusion元素
<dependency>
<groupId>com.atguigu.maven</groupId>
<artifactId>HelloFriend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<type>jar</type>
<scope>compile</scope>
<exclusions>
< exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</ exclusion>
</exclusions>
</dependency>
2.6.6 依赖的原则
依赖的原则就是为了解决jar包冲突。
- 路径最短者优先
- 路径相同时最先声明者优先
这里“声明”的先后顺序指的是 dependency 标签配置的先后顺序。
2.6.7 统一管理所依赖 .jar 包的版本
对同一个框架的一组 jar 包最好使用相同的版本。为了方便升级框架,可以将 jar 包的版本信息统一提取出来。
(1)统一声明版本号
<properties>
<自定义标签>4.1.1.RELEASE</自定义标签>
</properties>
(2)引用前面声明的版本号
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${自定义标签名称}</version>
</dependency>
……
</dependencies>
(3)其他用法
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
2.7 生命周期
2.7.1 概述
Maven生命周期定义了各个构建环节的执行顺序,有了这个顺序,Maven就可以自动化的执行构建命令了。Maven声明周期有以下特点:
- 各个构建环节执行的顺序:不能打乱顺序,必须按照既定的正确顺序来执行
- Maven的核心程序中定义了抽象到的生命周期,生命周期中各个阶段的具体任务是由插件来完成的。
- Maven核心程序为了更好的实现自动化构建,按照这一特点执行生命周期中各个阶段:不论现在要执行生命周期中的哪一个阶段,都是从这个生命周期最初始的位置开始执行,比如我们运行mvn install的时候,代码会编译,测试,打包。
Maven有三套相互独立的生命周期,分别是:
- Clean Lifecycle:在进行真正的构建之前进行一些清理工作
- Default Lifecycle:构建的核心部分,编译,测试,打包,安装,部署等等
- Site Lifecycle:生成项目报告,站点,发布站点。
它们是相互独立的,你可以仅仅调用clean来清理工作目录,仅仅调用site来生成站点。当然你可以直接运行 mvc clean install site运行所有这三套生命周期。
每套生命周期都是由一组阶段组成的,我们平时在命令行输入的命令总是会对应于一个特定的阶段。比如,运行mvn clean,这个clean是Clean生命周期中的一个阶段,有了Clean生命周期,也会有clean阶段。
2.7.2 Clean生命周期
Clean生命周期一共包含了三个阶段:
- pre-clean 执行一些需要在clean之前完成的工作
- clean 移除所有上一次构建生成的文件
- post-clean 执行一些需要在clean之后立刻完成的工作
2.7.3 Default生命周期
Default生命周期是Maven生命周期中最重要的一个,绝大部分的工作都发生在这个生命周期中,现在,只解释一些比较重要和常用的阶段:
- validate
- generate-sources
- process-sources
- generate-resources
- process-resources 复制并处理资源文件,至目标目录,准备打包。
- compile 编译项目的源代码。
- process-classes
- generate-test-sources
- process-test-sources
- generate-test-resources
- process-test-resources 复制并处理资源文件,至目标测试目录。
- test-compile 编译测试源代码。
- process-test-classes
- test 使用合适的单元测试框架运行测试。这些测试代码不会被打包或部署。
- prepare-package
- package 接受编译好的代码,打包成可发布的格式,如 JAR。
- pre-integration-test
- integration-test
- post-integration-test
- verify
- install 将包安装至本地仓库,以让其它项目依赖。
- deploy 将最终的包复制到远程的仓库,以让其它开发人员与项目共享或部署到服务器上运行。
2.7.4 Site生命周期
- pre-site 执行一些需要在生成站点文档之前完成的工作
- site 生成项目的站点文档
- post-site 执行一些需要在生成站点文档之后完成的工作,并且为部署做准备
- site-deploy 将生成的站点文档部署到特定的服务器上
2.7.5 插件和目标
- Maven的核心仅仅定义了抽象的生命周期,具体的任务都是交给插件来完成的。
- 每个插件都能实现多个功能,每个功能就是一个插件目标
- Maven的生命周期与插件目标相互绑定,以完成某个具体的构建任务。例如:compile就是插件maven-compiler-plugin的一个目标。
2.8 继承
2.8.1 使用继承的原因
现在有三个Maven项目,MakeFriends依赖Hello和HelloFriend,HelloFriend依赖Hello。由于非 compile 范围的依赖信息是不能在“依赖链”中传递的,所以有需要的工程只能单独配置。例如:
Hello | <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.0</version> <scope>test</scope> </dependency> |
HelloFriend | <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.0</version> <scope>test</scope> </dependency> |
MakeFriends | <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.0</version> <scope>test</scope> </dependency> |
此时如果项目需要将各个模块的junit版本统一为4.9,那么到各个工程中手动修改无疑是非常不可取的。 使用继承机制就可以将这样的依赖信息统一提取到父工程模块中进行统一管理。
2.8.2 解决办法
将Junit依赖统一提取到“父”工程中,在子工程中声明Junit依赖是不指定版本,以父工程中统一设定的为准。同时也便于修改。
操作步骤如下:
(1)创建一个Maven工程作为父工程。注意:打包方式为pom。(创建父工程和创建一般的 Java 工程操作一致,唯一需要注意的是:打包方式处要设置为 pom。)
(2)在子工程中声明对父工程的引用
<parent>
<!-- 父工程坐标 -->
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<relativePath>从当前目录到父项目的 pom.xml 文件的相对路径</relativePath>
</parent>
(3)将子工程的坐标中与父工程坐标中重复的内容删除(例如相同的groupId等)
(4)在父工程中统一管理Junit的依赖,将 Parent 项目中的 dependencies 标签,用 dependencyManagement 标签括起来
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
(5)在子项目中重新指定需要的依赖,删除范围和版本号
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
2.9 聚合
2.9.1 为什么要使用聚合
将多个工程拆分为模块后,需要手动逐个安装到仓库后依赖才能够生效。修改源码后也需要逐个手动进 行 clean 操作。而使用了聚合之后就可以批量进行 Maven 工程的安装、清理工作。
2.9.2 如何配置聚合
在总的聚合工程中使用 modules/module 标签组合,指定模块工程的相对路径即可。
<modules>
<module>../Hello</module>
<module>../HelloFriend</module>
<module>../MakeFriends</module>
</modules>
注意,这个甚至打乱顺序都可以执行成功
三.在Eclipse使用Maven
-
Maven插件Eclipse已经内置。
-
Maven插件的设置: Window->Preferences->Maven
①installations : 指定Maven核心程序的位置。默认是插件自带的Maven程序,改为我们自己解压的那个。
②user settings : 指定Maven核心程序中 conf/settings.xml 文件的位置,进而获取本地仓库的位置。 -
基本操作
(1)创建Maven版的Java工程
① 创建时勾选上 Create a simple project(skip archetype selection)
创建的Maven工程默认使用的是JDK1.5,打开Maven核心程序settings.xml文件,找到profiles标签,加入如下配置,即可更改(或者在这个工程下Build Path修改)
<profile>
<id>jdk-1.7</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.7</jdk>
</activation>
<properties>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<maven.compiler.compilerVersion>1.7</maven.compiler.compilerVersion>
</properties>
</profile>
(2)创建Maven版的Web工程
① 创建时勾选上 Create a simple project(skip archetype selection)
② Packing选择war
③调整web目录结构,在项目上点右键 -> properties->Project Facets -> 把Dynamic Web Module 勾选去掉,并Apply -> 将Dynamic Web Module 重新勾选 -> 点击Further configuration available -> 修改 Content directory为src/main/webapp -> Apply 即在Maven工程上生成动态Web目录结构。目录结构如下:
④新建jsp文件发现报错:The superclass “javax.servlet.http.HttpServlet” was not found on the Java Build Path/。因为缺少Tomcat 运行时环境,有两种解决方法:
a.以Maven方式添加:在pom.xml文件中添加
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
b.将Tomcat配置的Server直接引入这个工程。
⑤jsp写入EL表达式时发现报错:javax.servlet.jsp cannot be resolved to a type。解决方法:将JSPAPI导入,在pom.xml文件中添加
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1.3-b06</version>
<scope>provided</scope>
</dependency>
注意:scope一定要是provided,不然jar包冲突,运行时会报空指针异常
(3)执行Maven命令:选中pom.xml 右键 Run As->Maven build…->Goals->输入 compile ->点击 run
本篇文章是尚硅谷Maven的视频的学习笔记,还参考了《Maven实战》