Maven(一)maven设置,坐标,pom,java工程,web工程,目录结构,构建,依赖,继承,聚合

文章目录

前言

maven的原意是专家,内行

关于Maven

总之,Maven作为一个构建工具,不仅帮我们自动化构建,还能抽象构建过程,提供构建任务实现.他跨平台,对外提供一致的操作接口,这一切足以使他成为优秀的,流行的构建工具

但是Maven不仅是构建工具,他还是一个依赖管理工具和项目信息管理工具.他还提供了中央仓库,能帮我们自动下载构件

使用Maven还能享受一个额外的好处,即Maven对于项目目录结构、测试用例命名方式等内容都有既定的规则,只要遵循了这些成熟的规则,用户在项目间切换的时候就免去了额外的学习成本,可以说是约定优于配置

这个约定优于配置,我在学习java web写配置文件的时候就体会到了,越往业务走,就会遇到前人总结的这些规则,你可以选择不遵守,但是如果遵守了这些成熟的规则,你就能管理大型的项目同时也能看明白其他人的类似项目。

依赖管理工具

依赖管理:使用maven的本地仓库管理jar包,jar包就是我们在javaweb项目中的依赖,比如说mysql的jar包,thymeleaf的jar包,数据库连接池技术的德鲁伊jar包

jar包管理有三个问题,数量,可用性,jar包依赖
数量特别大,jar包资源不好找,jar包之间的依赖非常复杂但是固定

于是我们希望有这样一个仓库,jar包根据功能进行了打包,我根据约定好的功能名直接下载打包好的jar包,这样一来程序员不需要进行jar包管理,仓库管理员事先就把这事儿做了,千千万万的程序员都可以复用仓管员打包的结果

学完javaweb,我的项目也不过3个jar包

但是,随着框架的使用,jar包的数量会越来越多,一个模块用到上百个jar包都很正常

从学习OOP开始我们就知道,数量一多,必须进行管理,这样后期才能维护和扩展

比如下面的例子,我们只用到 SpringBoot、SpringCloud 框架中的三个功能:
Nacos 服务注册发现
Web 框架环境
图模板技术 Thymeleaf

最终导入了106个jar包,在前maven时代,这都需要我们手动下载,然后放到web项目的lib依赖中去

有了maven之后呢,只需要配置3个依赖

<!-- Nacos 服务注册发现启动器 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- web启动器依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 视图模板技术 thymeleaf -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

使用 Maven 后,依赖对应的 jar 包能够自动下载,方便、快捷又规范

maven相当于有这样一个jar包仓库,约定好命名规则之后,我们就能从仓库拿到规范的一系列jar包

构建管理工具

什么是构建??
一个简单地java se源程序的编译,就是构建的一个环节
一个web程序的构建,除了源程序的编译,还有打包成war包,部署到tomcat服务器

以前我都没注意过。你可以不使用 Maven,但是构建必须要做。当我们使用 IDEA 进行开发时,构建是 IDEA 替我们做的

意思就是,Maven也可以帮我们做构建??
在这里插入图片描述
:号之前是我的module名,后面就是war,所以说打包成war包是IDE帮我们做了

还有一个就是out目录,当我们在IDE中启动web项目的时候,会在out目录下生成两个文件夹,一个是artifacts,一个是production。之前我都没有仔细看过

artifacts

在这里插入图片描述
在这里插入图片描述

这是项目源码
在这里插入图片描述
这是artifacts

可以看到,源码和artifacts的结构很相似,除了WEB-INF目录。在artifacts/WEB-INF目录下,多了classes文件夹和lib文件夹

可以看到,classes文件夹的结构和项目的src文件夹的结构一样,但是里面放的不是源码,而是源码编译后的字节码文件

lib文件夹里边放的就是,我为项目配置的所有依赖,数据库连接池的jar包,JDBC需要的jar包,thymeleaf需要的jar包

前面部署到tomcat服务器的war包,就是artifacts里边的结构,编译好的字节码文件+前端的资源

所以说web项目实际运行的不是我们写好的源码,而是按照artifacts打包好的war包。IDEA帮我们做了构建的工作,编译+打包+部署

项目开发的生命周期

我们之前是在IDE里边写代码,写完了,配置完就启动,这只能算是本地开发,还没完呢

现在想想,我以前开发的是什么玩意儿???

在这里插入图片描述
在实际开发中,源码会推送到服务器,服务器调用Maven来做构建,服务器是不会去安装IDE的,所以就不可能让IDE来做构建的事儿

结论

管理规模庞大的 jar 包,需要专门工具。
脱离 IDE 环境执行构建操作,需要专门工具。

一. Maven概述

什么是构建

前面说了,编译,打包,部署属于构建的一个环节

创建不是构建,创建一个java类不是构建

Java 项目开发过程中,构建指的是使用『原材料生产产品』的过程

原材料:Java 源代码,基于 HTML 的 Thymeleaf 文件,图片,配置文件(原材料会按照约定好的路径,将不同的原材料放到不同的路径下)

产品:一个可以在服务器上运行的项目

构建的主要环节

构建过程包含的主要的环节:

清理:删除上一次构建的结果,为下一次构建做好准备
编译:Java 源程序编译成 *.class 字节码文件
测试:运行提前准备好的测试程序
报告:针对刚才测试的结果生成一个全面的信息
打包:
	Java工程:jar包
	Web工程:war包
安装:把一个 Maven 工程经过打包操作生成的 jar 包或 war 包存入 Maven 仓库
部署:
	部署 jar 包:把一个 jar 包部署到 Nexus 私服服务器上
	部署 war 包:借助相关 Maven 插件(例如 cargo),将 war 包部署到Tomcat 服务器上

什么是依赖

之前在IOC部分讲的依赖是类层面的,在类A里边有一个B类型的属性,这叫A依赖B

在Maven中讲依赖是在工程层面,如果 A 工程里面用到了 B 工程的类、接口、配置文件等等这样的资源,那么我们就可以说 A 依赖 B

依赖管理

依赖管理需要解决的问题:
(1)jar包数量
(2)jar包质量
(3)jar包之间的依赖
(4)jar包之间的冲突

解决方案:
(1)jar 包的下载:使用 Maven 之后,jar 包会从规范的远程仓库下载到本地
(2)jar 包之间的依赖:通过依赖的传递性自动完成
(3)jar 包之间的冲突:通过对依赖的配置进行调整,让某些jar包不会被导入

Maven的工作机制

maven的核心程序:负责总体的调度工作(中央控制器??)
maven插件:具体干活的程序,以jar包形式存在
Maven仓库:存放jar包
在这里插入图片描述

二. maven核心程序的安装

解压之后
在这里插入图片描述
在解压目录中,我们需要着重关注 Maven 的核心配置文件:conf/settings.xml

指定本地仓库

Maven中的仓库是本地仓库+远程仓库

本地仓库默认值:用户家目录/.m2/repository。也就是C:/用户/XXXX/.m2/repository

在conf/settings.xml文件中有本地仓库的配置

<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->

指定远程仓库

Maven 下载 jar 包默认访问境外的中央仓库,而国外网站速度很慢。改成阿里云提供的镜像仓库,它是中央仓库的一个镜像(结构,内容完全一致)

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

指定JDK版本

按照默认配置运行,Java 工程使用的默认 JDK 版本是 1.5

将 profile 标签整个复制到 settings.xml 文件的 profiles 标签内

<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环境变量

Maven 是一个用 Java 语言开发的程序,它必须基于 JDK 来运行,需要通过 JAVA_HOME 来找到 JDK 的安装位置

Maven环境变量

跟java环境变量的配置一样,主要是要找到maven的bin目录

在cmd验证的命令,mvn -V

三. Maven命令行环境

核心一:坐标(定位jar包)

我猜是如何在maven仓库中定位某一功能对应的jar包组合

果然,使用三个『向量』在『Maven的仓库』中唯一的定位到一个『jar』包

和我猜想的差别在于,可以直接定位到某个jar包,也就是以jar包为单位,并不是以功能为单位

(1)groupId:公司或组织的 id
(2)artifactId:一个项目或者是项目中的一个模块的 id
(3)version:版本号

groupId:公司或组织域名的倒序,通常也会加上项目名称
例如:com.atguigu.maven
artifactId:模块的名称,将来作为 Maven 工程的工程名
version:模块的版本号,根据自己的需要设定
例如:SNAPSHOT 表示快照版本,正在迭代过程中,不稳定的版本
例如:RELEASE 表示正式版本

举例:当我们创建maven工程的时候,就可以对这三个向量赋值
groupId:com.atguigu.maven
artifactId:pro01-atguigu-maven(感觉就是module的名字)
version:1.0-SNAPSHOT

坐标和仓库的关系

仓库会根据坐标创建相应的目录

<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>

Maven本地仓库根目录\javax\servlet\servlet-api\2.5\servlet-api-2.5.jar

可以看到最后一级目录使用-(横线)组合了artifactId和version

现实世界中的仓库我们总归是要选一块地的,软件仓库的地是硬盘

三个目录

(1)Maven 核心程序:中军大帐
(2)Maven 本地仓库:兵营
(3)本地工作空间:战场

创建Maven工程

在这里插入图片描述
在maven workspace目录,登录cmd,运行 mvn archetype:generate 命令

arche,希腊语,表示起源,本原,非常抽象的一个词

archetype,原型

前面说了,maven核心程序只负责总调度,插件负责干活

创建Maven工程,本质上就是创建Maven约定好的文件目录。我们可以手动创建,就像当时直接在tomcat的webapp中创建web项目,但是使用命令创建非常快

然后就需要我们输入三个向量的值,以确定坐标

Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 7:【直接回车,使用默认值】
//7 表示快速开始

Define value for property 'groupId': com.atguigu.maven

Define value for property 'artifactId': pro01-maven-java

Define value for property 'version' 1.0-SNAPSHOT: :【直接回车,使用默认值】

Define value for property 'package' com.atguigu.maven: :【直接回车,使用默认值】

Confirm properties configuration: 
groupId: com.atguigu.maven 
artifactId: pro01-maven-java 
version: 1.0-SNAPSHOT 
package: com.atguigu.maven Y: :【直接回车,表示确认。如果前面有输入错误,想要重新输入,则输入 N 再回车。】

这个package不知道是啥??

可以想见,如果手动创建maven工程目录这得多麻烦

maven工程创建成功后
在这里插入图片描述
basedir:maven workspace的根目录

我们看到,在workspace目录下创建了一个叫做pro01-maven-java的文件,这是artifactId的值

pro01-maven-java目录下,有一个src文件夹,一个pom.xml文件

src文件夹下有两个文件夹,一个是main,一个是test

main文件夹下就是,java*com\atguigu\maven*,java后面的目录就是groupId,也可能是packageName。maven里面是App.java

test文件夹下的目录和main差不多,也是java\com\atguigu\maven,只是maven下的文件不同,是AppTest.java

对创建好的Maven工程进行调整

(1)Maven 默认生成的工程,对 junit 依赖的是较低的 3.8.1 版本,我们可以改成较适合的 4.12 版本。在工程目录下的pom.xml中更改

(2)自动生成的 App.java 和 AppTest.java 可以删除

解读pom.xml配置文件

一个Maven工程的核心配置文件,其实内容并不多

pom,Project Object Module,项目对象模型

可以这样说,使用Maven就是在学习pom.xml文件怎么配置

根标签 project,表示对当前工程进行配置
<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">

  从maven2开始固定为4.0.0,表示此文件采用的标签结构
  <modelVersion>4.0.0</modelVersion>
  坐标信息,配置3个坐标向量的值
  <groupId>com.atguigu.maven</groupId> 代表公司的某个项目
  <artifactId>pro01-maven-java</artifactId> 代表项目下的某个模块
  <version>1.0-SNAPSHOT</version> 代表当前模块的版本

  取值jar,对应java工程
  取值war,对应web工程
  取值pom,表示一个管理工程的工程,父工程管理子工程
  <packaging>jar</packaging> 代表当前maven工程打包的方式

  <name>pro01-maven-java</name> maven工程名
  <url>http://maven.apache.org</url> maven官网

  定义属性值,可以自定义
  <properties>
    在构建过程中,读取源码时采用的字符集
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  配置具体的依赖信息
  maven导入jar包也就是在这个标签里面导入
  <dependencies>
    配置一个具体的依赖
    <dependency>
      使用jar包的坐标来导入一个jar包
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>

	  配置导入的jar包的范围,可能还有main作为scope标签的取值
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

从后面创建好的Maven工程的文件目录来看,工程名(artifactId),src,main/test,java这几级都是约定好的,固定的

之后就是按照groupId来构建目录

核心二:POM

含义
POM:Project Object Model,项目对象模型。和 POM 类似的是:DOM(Document Object Model),文档对象模型。它们都是模型化思想的具体体现。

万事万物皆对象,一个项目是对象,一个文档也可以是对象

模型化思想
POM 表示将工程抽象为一个模型,再用程序中的对象来描述这个模型。这样我们就可以用程序来管理项目了。我们在开发过程中,最基本的做法就是将现实生活中的事物抽象为模型,然后封装模型相关的数据作为一个对象,这样就可以在程序中计算与现实事物相关的数据。

对应的配置文件
POM 理念集中体现在 Maven 工程根目录下 pom.xml 这个配置文件中。所以这个 pom.xml 配置文件就是 Maven 工程的核心配置文件。其实学习 Maven 就是学这个文件怎么配置,各个配置有什么用

核心三:约定的目录结构

约定这个词,我之前在学习java web的时候自己总结出来了。当时也是做IOC层xml文件的配置,因为需要我们把标签里边属性的值取出来,我就感觉这需要配置文件和java代码约定好属性名是什么,不然怎么取

这个时候我也就发现,为什么readme,文档很重要,我们需要把约定好的内容记录下来,一方面约定很多,谁记得住啊,另一方面,如果开源了代码,人家要用这个系统,借助readme可以很快了解软件的约定,才知道怎么改

约定这个词和自动是分不开的,这也很好理解,对于一个系统,当我完成约定的动作,系统就可以自动运行

落实到Maven作为构建管理工具这一点,比如说编译,Maven总得要知道我们的java源代码在哪里他才能帮我们做编译,所以我们事先约定好java源程序的位置,然后Maven就在这个目录下去取源程序

各个目录的作用

在这里插入图片描述

约定的意义

Maven 为了让构建过程能够尽可能自动化完成,所以必须约定目录结构的作用。例如:Maven 执行编译操作,必须先去 Java 源程序目录读取 Java 源代码,然后执行编译,最后把编译结果存放在 target 目录

约定大于配置

使用框架之前,我们用配置,可能是为了隐藏第三方API,可能是为了避免重复编译

有了框架之后,我们用配置可能就直接能实现一些功能,这在之前得自己一点一点写代码

Maven 对于目录结构这个问题,没有采用配置的方式,而是基于约定。这样会让我们在开发过程中非常方便。如果每次创建 Maven 工程后,还需要针对各个目录的位置进行详细的配置,那肯定非常麻烦。

目前开发领域的技术发展趋势就是:约定大于配置,配置大于编码

(1)在框架之前,所有功能都是程序员来编码
(2)有了框架,比如spring,我们使用配置来实现功能
(3)当配置也很麻烦之后,对框架继续抽象封装,比如spring boot,此时的框架就直接约定好了一些事儿,省去了相当部分的配置,这叫有限的自由才是真自由

再回顾约定这个词,在数学领域,我们做了相当多的约定;计算机领域也是如此,十六进制中A,B,C,D约定为11,12,13,14,TCP也是一种约定

约定是一种底层的默认值,一种底层的配置,约定好就不再修改
配置有默认值,他的存在就是让你修改

四. 在Maven工程中编写程序

在约定目录下写程序

在这里插入图片描述
在这里插入图片描述

执行Maven的构建命令

前面说了,构建包括编译,打包。我之前自己干过的也就是编译了,从来没有打过jar包

运行 Maven 中和构建操作相关的命令时,必须进入到 pom.xml 所在的目录
pom里边有编译所需的信息(java源代码的位置),打包所需的信息(打哪一种包,jar包,war包),测试所需的信息(junit的版本),读取源码所需的信息(字符集)

清理操作

mvn clean

效果:删除 target 目录(target目录用于存放编译结果)

编译操作

主程序编译:mvn compile

测试程序编译:mvn test-compile

主体程序编译结果存放的目录:target/classes

测试程序编译结果存放的目录:target/test-classes

在这里插入图片描述

测试操作

mvn test

测试的报告存放的目录:target/surefire-reports

要注意,执行测试命令时,他会对测试程序和java源程序自动进行重新编译,也就是说,我改动测试代码或者源代码之后不需要手动重新编译

在这里插入图片描述
测试报错,执行目标失败

目标这个说法第一次出现在上面创建Maven工程,mvn archetype:generate,这个generate就是目标
在这里插入图片描述
可以看到,执行测试命令时,maven核心程序调用了一个插件,maven-surefire-plugin:2.12.4

打包操作

mvn package

打包的结果——jar 包,存放的目录:target

打包完成之后可以看到
Building jar: …\pro01-maven-java\target\pro01-maven-java-1.0-SNAPSHOT.jar

jar包名默认是用的artifactId + version,也就是坐标中的两个向量的拼接

java工程打包后的目录为:
在这里插入图片描述

安装操作

安装的效果是将本地构建过程中生成的 jar 包存入 Maven 本地仓库。这个 jar 包在 Maven 仓库中的路径是根据它的坐标生成的

我们之前在maven\conf\settings.xml目录下,指定过本地仓库的地址,不然maven核心程序怎么知道把jar包往哪里去存

maven install

命令执行后可以看到jar包被存放在本地仓库的哪个位置
…\Repo\com\atguigu\maven\pro01-maven-java\1.0-SNAPSHOT\pro01-maven-java-1.0-SNAPSHOT.pom

这样的目录结构也是一种约定
\com\atguigu\maven\这一块是groupId
pro01-maven-java这一块是artifactId
1.0-SNAPSHOT这一块是version
pro01-maven-java-1.0-SNAPSHOT这一块不带后缀的是jar包名

从这个顺序也就看出了坐标是如何在仓库中定位一个jar包的

虽然在上面只看到了一个.pom文件,但仓库存的不只是这个.pom文件
在这里插入图片描述
另外,安装操作还会将 pom.xml 文件转换为 XXX.pom 文件一起存入本地仓库。所以我们在 Maven 的本地仓库中想看一个 jar 包原始的 pom.xml 文件时,查看对应 XXX.pom 文件即可,它们是名字发生了改变,本质上是同一个文件

我们打完包之后,在workspace\项目名\src\target目录下会有jar包,安装之后会自动存到本地仓库,相当于咱有两份jar包
在这里插入图片描述

从本地仓库删除jar包

mvn dependency:purge-local-repository -DmanualInclude="groupId:artifactId, ..."

purge: 删除
manual:手工的
artifact:人工制品

在这里插入图片描述

五. 创建Maven版的web工程

一个用于web工程的archetype衍生插件

使用 mvn archetype:generate 命令生成 Web 工程时,需要使用一个专门的 archetype。这个专门生成 Web 工程骨架的 archetype 可以参照官网看到它的用法

web工程骨架,或者说叫文件目录
在这里插入图片描述

mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeVersion=1.4

这条命令是由我们指定一个archetype插件,-D表示我们要写一些参数,从形势来看,插件也是jar包啊,存在我们的本地仓库,因为我们看到他用了坐标

这个插件应该是maven-archetype-webapp

开始创建web工程

不管是创建maven java工程还是web工程,都是在workspace根目录下执行命令

执行以后
在这里插入图片描述

web工程的目录结构

在这里插入图片描述
webapp 目录下有 index.jsp

WEB-INF 目录下有 web.xml

到main为止,都是maven约定好的固定目录

如果是手动在tomcat中部署项目,那就是在tomcat根目录下的webapps中创建web项目,tomcat根目录\webapps\项目名\WEB-INF\web.xml

如果是在IDEA中创建一个web项目,那应该是项目名\web\WEB-INF\web.xml

而现在创建Maven版的web工程,目录是项目名\src\main\webapp\WEB-INF\web.xml

前端的相关资源和代码都和WEB-INF在同一级目录

创建servlet(创建java类)

servlet属于java se源码,一般是放在WEB-INF的上一级目录

在缺失IDE的情况下,web工程的se源码目录需要我们手动创建

其实也是仿照java工程的结构,java源代码放在java目录下,但是在IDEA中,Java源代码是放在src下的
在这里插入图片描述
在这里插入图片描述
然后在java目录下创建servlet类所在的包
在这里插入图片描述
之后就可以造类了

这算是体会到了当年的程序员前辈是怎么一步步写项目了

在web.xml中注册servlet

也就是把映射给写上

<servlet>
	<servlet-name>helloServlet</servlet-name>
	<servlet-class>com.atguigu.maven.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>helloServlet</servlet-name>
	<url-pattern>/helloServlet</url-pattern>
</servlet-mapping>

补充一个前端的html页面

不能直接编译

java中只提供了servlet接口,实现类是由服务器厂商提供,也就是jar包

现在没有这些jar包,编译的时候当然就找不到实现类了

因此,要编译一个web工程就需要我们导入jar包,至于servlet的实现类,那是在tomcat的lib目录下的servlet-api.jar这个压缩包里的

配置对servlet-api.jar包的依赖

可以到https://mvnrepository.com/网站查询

搜出来可能有很多jar包,不同组织进行了不同的封装
在这里插入图片描述
选一个点进去,可能又有很多歌版本
在这里插入图片描述
选择一个版本点进去
在这里插入图片描述

<!-- 为了能够使用servlet接口的实现类,导入servlet-api依赖 -->
<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>javax.servlet-api</artifactId>
	<version>3.1.0</version>
	<scope>provided</scope>
</dependency>

从这也就能看出来,依赖也是通过pom.xml实现的,我们在pom中给出目标jar包的坐标,如果本地仓库有,那就复制一份到项目;如果没有,先从远程仓库镜像去下载,然后放到项目中

上面是我猜的,可能并不会把目标jar包放在项目中???因为我看到pom中也依赖了junit,但是test目录下并没有junit的jar包

这是那句话,学习maven就是学习pom.xml怎么配置

编译

这样就可以进行编译了

这样在项目名\target\classes目录下就有package目录了,package目录里边就是我们的.class文件

打包

mvn package

在这里插入图片描述
项目名\target目录下就有下面两个文件,上面一个文件夹是war包的解压结果

之后,就可以把war包放到tomcat进行部署

我们的war包可以放到本地仓库吗??

仓库里边只放jar包,放war包没有意义,难道依赖.html???

war包的结构

这是项目打包之前的文件目录
在这里插入图片描述
检查一下war包的结构
在这里插入图片描述
在这里插入图片描述

我们可以看到,打包之后很多目录被取消了:
(1)将WEB-INF和index.jsp拿出来,增加了一个看不到内容的META-INF
(2)将java目录下的package目录迁移到了WEB-INF中的classes目录下,然后把里面的.java文件换成了编译后的.class文件
在这里插入图片描述
事实上这个classes目录在编译之后就存在于target目录下了
在这里插入图片描述

部署

这一步是手动做的啊

把解压的war包扔到tomcat\webapps
在这里插入图片描述
然后启动tomcat服务器,在浏览器访问url:http://localhost:8080/mavenwebDemo/index.jsp

依赖的jar包哪里去了???

值得注意的是,不管是编译后还是打包后的项目,我都没有看到配置的servlet-api.jar包的存在

编译的时候我们是看到下载这个jar包的,这个jar包应该是被下载到了maven本地仓库,我也确实在本地仓库找到了
在这里插入图片描述
但是,我部署到tomcat上的就是一个不包含servlet-api.jar的项目啊,那这是咋回事呢??

难道说是编译完之后就不需要依赖了吗???

经过一番百度之后,可能是这样:
(1)在pom.xml中配置依赖,就是说我们指明了他在本地仓库中的位置,这个地址指向jar包
(2)编译之后,上面那个地址被写到依赖jar包的.class文件中了
(3)等到执行到这个.class文件时,就到上面那个地址去本地仓库找依赖

假设我的本机是提供服务的服务器,就相当于我要安装tomcat服务器,maven本地仓库,maven核心程序
项目得在服务器进行编译,这样本地仓库才能存储项目依赖的jar包

六. 让web工程依赖java工程

明确一个意识:从来只有 Web 工程依赖 Java 工程,没有反过来 Java 工程依赖 Web 工程。本质上来说,Web 工程依赖的 Java 工程其实就是 Web 工程里导入的 jar 包。最终 Java 工程会变成 jar 包,放在 Web 工程的 WEB-INF/lib 目录下
在这里插入图片描述
jar包不是都要放在maven仓库吗???

配置对上面创建的Maven版java工程的依赖

此时假设Maven工程还没有编译

如果是在pom.xml配置依赖,那么配置的就是jar包的坐标,那就是说java工程必须打包且放在本地仓库

我们在前面已经将java工程的jar包安装到了maven本地仓库

<!-- 配置对Java工程pro01-maven-java的依赖 -->
<!-- 具体的配置方式:在dependency标签内使用坐标实现依赖 -->
<dependency>
	<groupId>com.atguigu.maven</groupId>
	<artifactId>pro01-maven-java</artifactId>
	<version>1.0-SNAPSHOT</version>
</dependency>

在web工程中进行测试

也是借鉴了java工程的test目录
在这里插入图片描述
建立这样一个目录:
pro02-maven-web\src\test\java\com\atguigu\maven

再确认web工程是否依赖了4.12版本的junit

直接执行mvn test,他会自动对主体程序和测试程序源代码进行编译

打包

执行mvn package

文件目录发生了变化,增加了一个lib文件夹存放jar包
在这里插入图片描述

查看web工程所有的依赖

执行mvn dependency:list
在这里插入图片描述
还可以通过mvn dependency:tree查看依赖之间的关系
在这里插入图片描述

七. 阶段性总结

我们实际使用的应该是jar包和war包

jar包放在Maven本地仓库
war包部署到服务器

打包是不包含测试程序的.class文件的

jar包的文件目录
在这里插入图片描述
war包的文件目录(不包含依赖)
在这里插入图片描述
war包的文件目录(有依赖)增加了一个lib文件夹
在这里插入图片描述

为什么有的依赖不在lib文件夹中

打包之后,为什么有的依赖在lib,有的不在

我猜和scope标签有关,我记得servlet-api.jar包的scope是provided,而我们自己打包的java工程配置的scope是compile,junit也没有出现在lib文件夹,它配置的scope是test

坐标

因为坐标的定义,我会下意识认为坐标只是在访问Maven仓库的时候使用

但在学习了Maven工程的文件目录之后应该明白,坐标本质上是用于创建文件目录的,我们可以拆分也可以拼接坐标的三个向量值

实际上是,坐标的使用范围包括访问Maven本地仓库,Maven工程,编译后生成的classes文件,打包后的jar包或者war包

八. 测试依赖的范围

提供scope的目的并不是为了区分compile,因为compile是默认情况,绝大多数依赖就是在所有情况有效,不然怎么叫依赖呢

其目的在于区分test,provided这种特殊情况。像测试,我测完就行了,我上线的包需要依赖junit吗,显然是不需要的。对于provided,我就不清楚了

scope标签

在pom.xml中配置依赖的时候,除了坐标,还会配制jar包的scope

<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>javax.servlet-api</artifactId>
	<version>3.1.0</version>
	<scope>provided</scope>
</dependency>

scope标签的值:compile/ test/ provided/ system/ runtime/ import

在这里插入图片描述
在这里插入图片描述
所谓的有效无效,指的是是否能访问依赖中的类,比如说main目录有效,指的是main目录下的.java文件可以调用依赖中的类

简单说就是A依赖B,然后我们还需要看A的那些部分可以访问到B

scope = compile

项目1是一个java工程,已经打包并安装到maven本地仓库,他只有1个.class文件

项目2是一个web工程,已经在pom.xml中配置项目1作为依赖,且scope=compile

我们在项目2的main目录下的.java文件中导入项目1jar包中的类,然后编译项目2,成功,说明可以访问

从时间层面看依赖是否有效(为什么有的依赖不在lib文件夹)

在IDE中感受更强一些

比如说这个开发过程,如果说在IDE中可以用点运算符.调出来那就算是有效

而部署到服务器的意思是,这个依赖是否参与打包,如果参与打包,那么lib文件夹里边就会有这个jar包

这么一说我就明白了,
servlet-api.jar的scope是provided,所以不参与打包,也就不在lib中
pro-01.jar的scope是compile,所以参与打包,那就会在lib中

为什么有的依赖不参与打包(provided)

对于一个web项目,我们知道分为后端程序和前端程序

主体功能需要用到的jar包类都需要参与打包,也就是在包里边放一个副本,不是临时到仓库中去调用

第三方框架基本也是要参与打包的

那servlet-api.jar包中的类主体功能用不到吗???他为什么会是provided的呢????

provided,已提供,如果他不参与打包,又是已提供,那么它是谁提供的呢??一定有一个提供者

web项目是会被部署到服务器的,其实也就是把war包放到tomcat安装路径的某个目录下,那就是tomcat提供的。我们在tomcat的lib目录下是可以找到servlet-api.jar包的
在这里插入图片描述
那么在main目录,test目录,开发期间用的也是tomcat提供的jar包吗
在这里插入图片描述
我觉得不是,因为在pom.xml我们配置servlet-api.jar包并不是tomcat中的哪一个

<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>javax.servlet-api</artifactId>
	<version>3.1.0</version>
	<scope>provided</scope>
</dependency>

我们配置的这一个是存在Maven本地仓库中的,我看了,仓库中有

我认为在main目录,test目录,开发期间用的应该是仓库中我们配置的这个servlet-api.jar包

仓库里边的servlet-api.jar包和tomcat提供的servlet-api.jar包不一样,这也就是为什么如果我们让配置的servlet-api.jar包参与打包可能会导致冲突

仓库的servlet-api.jar包和tomcat不兼容,只有tomcat自带的servlet-api.jar包才和tomcat兼容

九. 依赖的传递性

传递性这个说法在前面提过,说是利用依赖的传递性来解决来自动完成jar包对其他jar包的依赖

A 依赖 B,B 依赖 C,那么在 A 没有配置对 C 的依赖的情况下,A 里面能不能直接使用 C?

很明显,如果能够支持这种传递性,无疑会大大减轻程序员管理jar包的负担

在 A 依赖 B,B 依赖 C 的前提下,C 是否能够传递到 A,取决于 B 依赖 C 时使用的依赖范围:
(1)B 依赖 C 时使用 compile 范围:可以传递
(2)B 依赖 C 时使用 test 或 provided 范围:不能传递,所以需要这样的 jar 包时,就必须在需要的地方明确配置依赖才可以

这也就是为什么引入框架之后,只需要在pom.xml配置三个框架的jar包,就可以引入106个jar包

如果依赖的jar包不存在呢

按照依赖的传递性,我们只需要配置依赖树的根依赖就可以了,那如果节点依赖不存在怎么办呢???

之所以产生这个问题,我想是因为我对整个流程还不够熟悉

比如说A依赖B,B是根依赖,后面还有一串节点依赖C,D,E,F

根据后面的说法,java工程之间的依赖和web工程依赖java工程不同,web工程打包之后war包会有一个lib文件夹用来存放java工程的jar包,而java工程打包之后jar包里边只有编译后的.class文件以及相关的信息文件,没有依赖的jar包

那假设A是web工程,B,C都是java工程。A依赖B,B依赖C
(1)C打包之后存到本地仓库
(2)B打包之后存到本地仓库
(3)A打包之后,war包里边的lib文件夹是只有B的jar包还是B和C的jar包呢????

十. 依赖的排除

阻断依赖的传递

在这里插入图片描述
有可能几个根依赖指向同一个依赖的不同版本,这回导致冲突,需要阻断不同版本,保留一个即可

配置依赖的排除其实就是阻止某些 jar 包的传递

配置依赖的排除

exclusion:名词,排除在外

假设A依赖B,首先进入A的pom,然后找到配置的对B的依赖,再在这个依赖中配置对依赖C的排除,也就是说A不会依赖这个C,B仍然依赖于这个C

但是A还是可以通过D,依赖另一个版本或者同一个版本的C

对于A,他可能会排除B依赖关联的多个依赖

从下面的配置来看,是否可以这样理解,以B为根依赖的依赖树中所有的C,不管什么版本,都会被排除,不会被A使用

<dependency>
	<groupId>com.atguigu.maven</groupId>
	<artifactId>pro01-maven-java</artifactId>
	<version>1.0-SNAPSHOT</version>
	<scope>compile</scope>
	<!-- 使用excludes标签配置依赖的排除	-->
	<exclusions>
		<!-- 在exclude标签中配置一个具体的排除 -->
		<exclusion>
			<!-- 指定要排除的依赖的坐标(不需要写version) -->
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
		</exclusion>
	</exclusions>
</dependency>

十一. 继承

Maven工程之间,A 工程继承 B 工程

B 工程:父工程
A 工程:子工程
本质上是 A 工程的 pom.xml 中的配置继承了 B 工程中 pom.xml 的配置

为什么引入继承

有这么一个需求,假设我有多个工程,他们都依赖同一个版本的jar包,一般情况下,我需要在每个工程的pom里边去配置这个依赖

有没有办法可以让我一次性为多个工程配置同样的依赖,实现一处修改,处处生效的效果

于是,引入继承,在父工程中统一管理项目中的依赖信息,具体来说是管理依赖信息的版本

举例

在一个工程中依赖多个 Spring 的 jar 包

使用 Spring 时要求所有 Spring 自己的 jar 包版本必须一致。为了能够对这些 jar 包的版本进行统一管理,我们使用继承这个机制,将所有版本信息统一在父工程中进行管理

创建父工程

创建工程的方式和之前创建Maven版java工程一样,但此时这个工程就是一个普通的java工程

执行mvn archetype:generate

需要修改packaging的值,修改为pom,这样这个Maven工程才能够管理其他Maven工程

<groupId>com.atguigu.maven</groupId>
<artifactId>pro03-maven-parent</artifactId>
<version>1.0-SNAPSHOT</version>

<!-- 当前工程作为父工程,它要去管理子工程,所以打包方式必须是 pom -->
<packaging>pom</packaging>

打包方式为 pom 的 Maven 工程中不写业务代码,它是专门管理其他 Maven 工程的工程

因为不写业务代码,所以呢自带的依赖就可以删掉了

创建模块工程

类似于IDEA那种工程结构,在父工程的根目录创建一个一个子工程(模块工程)

从继承角度,是在父工程中创建多个子工程
从聚合角度,是在总工程中创建多个模块工程

很明显,IDEA采用的是聚合角度

在这里插入图片描述

父工程的pom和子工程的pom

我们在父工程下没创建一个子工程,父工程的pom就会自动配置一个module

<modules>  
  <module>pro04-maven-module</module>
  <module>pro05-maven-module</module>
  <module>pro06-maven-module</module>
</modules>

后面会提到这就是聚合要在pom.xml中做的配置

在父工程的根目录下创建的子工程的pom中会自动配置父工程,方式呢就是给出父工程的坐标(虽然父工程没有打包)

<parent>
  <groupId>com.atguigu.maven</groupId>
  <artifactId>pro03-maven-parent</artifactId>
  <version>1.0-SNAPSHOT</version>
</parent>

子工程的pom里边包含父子工程的坐标,如果父子工程坐标的groupId和version一样,那么就可以省略

一般这两个向量都是一样的

父工程管理依赖

增加了一个标签,dependencyManagement

我们可以看到,所有的依赖都放到了父工程,但是没有scope值,默认都是compile

父工程统一管理依赖版本的机制也越来越清晰:父工程配置依赖的版本,子工程配置父工程中的某个依赖;感觉上父工程变成了一个专属于子工程的临时仓库了

<!-- 使用dependencyManagement标签配置对依赖的管理 -->
<!-- 被管理的依赖并没有真正被引入到工程 -->
<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>4.0.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>4.0.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.0.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-expression</artifactId>
			<version>4.0.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>4.0.0.RELEASE</version>
		</dependency>
	</dependencies>
</dependencyManagement>

在子工程中配置父工程中配置的某个依赖

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
</dependency>

前面说了,父工程配置依赖的版本,子工程配置父工程中的某个依赖
所以有这么几种情况:
(1)子工程省略依赖的版本,依赖父工程的那个版本
(2)子工程设置了一个不同于父工程的版本,依赖自己写的这个版本

但一般情况,子类的依赖版本和父类保持一致

父工程自定义属性

我们这个例子,父工程依赖的是spring框架相关的jar包,spring有要求,这些包的版本必须一致,但是按照我们上面的配置方法,配置了5个依赖,如果要改版本号,我得改5次;下次要是配置50个依赖,那我就得改50次版本,太麻烦了

于是,我们在属性标签自定义一个标签用来表示spring依赖的版本

<!-- 通过自定义属性,统一指定Spring的版本 -->
<properties>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	
	<!-- 自定义标签,维护Spring版本数据 -->
	<atguigu.spring.version>4.3.6.RELEASE</atguigu.spring.version>
</properties>

使用自定义属性时,使用${}的形式来引用自定义的属性名

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-core</artifactId>
	<version>${atguigu.spring.version}</version>
</dependency>

${}这个符号我在前端见过,主要是用来区分字符串,如果直接写atguigu.spring.version,则会被视作字符串

实际开发

在商业开发中继承树会比较复杂
在这里插入图片描述
编写一套符合要求、开发各种功能都能正常工作的依赖组合并不容易。如果公司里已经有人总结了成熟的组合方案,那么再开发新项目时,如果不使用原有的积累,而是重新摸索,会浪费大量的时间。为了提高效率,我们可以使用工程继承的机制,让成熟的依赖组合方案能够保留下来。

如上图所示,公司级的父工程中管理的就是成熟的依赖组合方案,各个新项目、子系统各取所需即可

总结

创建父工程完成后,手动调整pom中的打包方式

在父工程根目录下,创建多个子工程。自动完成父子工程中pom的配置

父工程在pom中配置所有需要的依赖,确定版本号

子工程在pom中配置父工程有的那些依赖,多数情况下不需要配版本号,保持一致即可

十二. 聚合

使用一个“总工程”将各个“模块工程”汇集起来,作为一个整体对应完整的项目

之前我们说了Maven的两大功能,一个是依赖管理,一个是构建管理

继承,聚合都是在描述Maven工程之间的关系

我有一种感觉,继承完善了Maven的依赖管理功能,聚合完善了构建管理功能

聚合的作用

一键执行 Maven 命令:很多构建命令都可以在“总工程”中一键执行。

以 mvn install 命令为例:Maven 要求有父工程时先安装父工程;有依赖的工程时,先安装被依赖的工程(也就是先从依赖树的叶子依赖开始安装)

这句话咋理解呢??如果我是安装模块打包的jar包,难道说会先将父工程打包并安装吗??

我们自己考虑这些规则会很麻烦。但是工程聚合之后,在总工程执行 mvn install 可以一键完成安装,而且会自动按照正确的顺序执行。

配置聚合之后,各个模块工程会在总工程中展示一个列表,让项目中的各个模块一目了然

实践

设父工程为A,三个子工程为B,C,D,B依赖C,C依赖D,scope都默认为compile

在A的根目录去执行maven命令,执行mvn install命令,他实际上还是会先去执行编译命令mvn compile,再执行打包命令mvn package

从命令行的反馈来讲:
(1)首先就确定了构建顺序,A,D,C,B
(2)父工程A直接安装,没有进行编译和打包,怪不得仓库里的A没有jar包。那父工程存到Maven本地仓库额意义是什么呢????
(3)子工程D先进行编译,再打包,最后安装
以此类推子工程C,B

jar包之间相互依赖,会有lib文件夹吗

答案是不会,没有lib文件夹,不会把依赖的java工程的jar包放一份在自己的jar包里面

这里其实提到了一个新情况,java工程A依赖java工程B,那么打包之后,A的jar包里边会有B的jar包吗????

如果是web工程A依赖java工程B,那么打包之后,A的war包里面就会有B的jar包
这个流程是B先编译打包,并安装到本地仓库,A在打包的时候会将B的jar包从本地仓库复制一份放到自己的lib文件夹。注意啊,war包是不会放到本地仓库的,它是部署到服务器上去的

对于java工程A依赖java工程B,B的jar包会先存到本地仓库,A有必要将B的jar包复制一份放在自己的jar包中吗????我感觉没有必要,jar包都是要放仓库的,要用的时候,两个jar包都取出来

百度之后,提到Maven作为依赖管理工具,其中一个功能就是将jar包保存到仓库,有需要的工程配置对这个jar包的依赖即可,不需要把这个jar包复制一份到自己的jar包

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值