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个依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
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的结构很相似,除了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 环境执行构建操作,需要专门工具。
前面说了,编译,打包,部署属于构建的一个环节
创建不是构建,创建一个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插件:具体干活的程序,以jar包形式存在
Maven仓库:存放jar包
解压之后
在解压目录中,我们需要着重关注 Maven 的核心配置文件:conf/settings.xml
Maven中的仓库是本地仓库+远程仓库
本地仓库默认值:用户家目录/.m2/repository。也就是C:/用户/XXXX/.m2/repository
在conf/settings.xml文件中有本地仓库的配置
Maven 下载 jar 包默认访问境外的中央仓库,而国外网站速度很慢。改成阿里云提供的镜像仓库,它是中央仓库的一个镜像(结构,内容完全一致)
<mirror>
<id>nexus-aliyunid>
<mirrorOf>centralmirrorOf>
<name>Nexus aliyunname>
<url>http://maven.aliyun.com/nexus/content/groups/publicurl>
mirror>
按照默认配置运行,Java 工程使用的默认 JDK 版本是 1.5
将 profile 标签整个复制到 settings.xml 文件的 profiles 标签内
<profile>
<id>jdk-1.8id>
<activation>
<activeByDefault>trueactiveByDefault>
<jdk>1.8jdk>
activation>
<properties>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<maven.compiler.compilerVersion>1.8maven.compiler.compilerVersion>
properties>
profile>
Maven 是一个用 Java 语言开发的程序,它必须基于 JDK 来运行,需要通过 JAVA_HOME 来找到 JDK 的安装位置
跟java环境变量的配置一样,主要是要找到maven的bin目录
在cmd验证的命令,mvn -V
我猜是如何在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.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>2.5version>
Maven本地仓库根目录\javax\servlet\servlet-api\2.5\servlet-api-2.5.jar
可以看到最后一级目录使用-(横线)组合了artifactId和version
现实世界中的仓库我们总归是要选一块地的,软件仓库的地是硬盘
(1)Maven 核心程序:中军大帐
(2)Maven 本地仓库:兵营
(3)本地工作空间:战场
在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
(1)Maven 默认生成的工程,对 junit 依赖的是较低的 3.8.1 版本,我们可以改成较适合的 4.12 版本。在工程目录下的pom.xml中更改
(2)自动生成的 App.java 和 AppTest.java 可以删除
一个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.0modelVersion>
坐标信息,配置3个坐标向量的值
<groupId>com.atguigu.mavengroupId> 代表公司的某个项目
<artifactId>pro01-maven-javaartifactId> 代表项目下的某个模块
<version>1.0-SNAPSHOTversion> 代表当前模块的版本
取值jar,对应java工程
取值war,对应web工程
取值pom,表示一个管理工程的工程,父工程管理子工程
<packaging>jarpackaging> 代表当前maven工程打包的方式
<name>pro01-maven-javaname> maven工程名
<url>http://maven.apache.orgurl> maven官网
定义属性值,可以自定义
<properties>
在构建过程中,读取源码时采用的字符集
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
配置具体的依赖信息
maven导入jar包也就是在这个标签里面导入
<dependencies>
配置一个具体的依赖
<dependency>
使用jar包的坐标来导入一个jar包
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
配置导入的jar包的范围,可能还有main作为scope标签的取值
<scope>testscope>
dependency>
dependencies>
project>
从后面创建好的Maven工程的文件目录来看,工程名(artifactId),src,main/test,java这几级都是约定好的,固定的
之后就是按照groupId来构建目录
含义
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也是一种约定
约定是一种底层的默认值,一种底层的配置,约定好就不再修改
配置有默认值,他的存在就是让你修改
前面说了,构建包括编译,打包。我之前自己干过的也就是编译了,从来没有打过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,也就是坐标中的两个向量的拼接
安装的效果是将本地构建过程中生成的 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包
mvn dependency:purge-local-repository -DmanualInclude="groupId:artifactId, ..."
purge: 删除
manual:手工的
artifact:人工制品
使用 mvn archetype:generate 命令生成 Web 工程时,需要使用一个专门的 archetype。这个专门生成 Web 工程骨架的 archetype 可以参照官网看到它的用法
mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeVersion=1.4
这条命令是由我们指定一个archetype插件,-D表示我们要写一些参数,从形势来看,插件也是jar包啊,存在我们的本地仓库,因为我们看到他用了坐标
这个插件应该是maven-archetype-webapp
不管是创建maven java工程还是web工程,都是在workspace根目录下执行命令
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 se源码,一般是放在WEB-INF的上一级目录
在缺失IDE的情况下,web工程的se源码目录需要我们手动创建
其实也是仿照java工程的结构,java源代码放在java目录下,但是在IDEA中,Java源代码是放在src下的
然后在java目录下创建servlet类所在的包
之后就可以造类了
这算是体会到了当年的程序员前辈是怎么一步步写项目了
也就是把映射给写上
<servlet>
<servlet-name>helloServletservlet-name>
<servlet-class>com.atguigu.maven.HelloServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>helloServletservlet-name>
<url-pattern>/helloServleturl-pattern>
servlet-mapping>
java中只提供了servlet接口,实现类是由服务器厂商提供,也就是jar包
现在没有这些jar包,编译的时候当然就找不到实现类了
因此,要编译一个web工程就需要我们导入jar包,至于servlet的实现类,那是在tomcat的lib目录下的servlet-api.jar这个压缩包里的
可以到https://mvnrepository.com/网站查询
搜出来可能有很多jar包,不同组织进行了不同的封装
选一个点进去,可能又有很多歌版本
选择一个版本点进去
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
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进行部署
仓库里边只放jar包,放war包没有意义,难道依赖.html???
我们可以看到,打包之后很多目录被取消了:
(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
值得注意的是,不管是编译后还是打包后的项目,我都没有看到配置的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 工程,没有反过来 Java 工程依赖 Web 工程。本质上来说,Web 工程依赖的 Java 工程其实就是 Web 工程里导入的 jar 包。最终 Java 工程会变成 jar 包,放在 Web 工程的 WEB-INF/lib 目录下
jar包不是都要放在maven仓库吗???
此时假设Maven工程还没有编译
如果是在pom.xml配置依赖,那么配置的就是jar包的坐标,那就是说java工程必须打包且放在本地仓库
我们在前面已经将java工程的jar包安装到了maven本地仓库
<dependency>
<groupId>com.atguigu.mavengroupId>
<artifactId>pro01-maven-javaartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
也是借鉴了java工程的test目录
建立这样一个目录:
pro02-maven-web\src\test\java\com\atguigu\maven
再确认web工程是否依赖了4.12版本的junit
直接执行mvn test,他会自动对主体程序和测试程序源代码进行编译
执行mvn package
执行mvn dependency:list
还可以通过mvn dependency:tree查看依赖之间的关系
我们实际使用的应该是jar包和war包
jar包放在Maven本地仓库
war包部署到服务器
打包是不包含测试程序的.class文件的
jar包的文件目录
war包的文件目录(不包含依赖)
war包的文件目录(有依赖)增加了一个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,我就不清楚了
在pom.xml中配置依赖的时候,除了坐标,还会配制jar包的scope
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
scope标签的值:compile/ test/ provided/ system/ runtime/ import
所谓的有效无效,指的是是否能访问依赖中的类,比如说main目录有效,指的是main目录下的.java文件可以调用依赖中的类
简单说就是A依赖B,然后我们还需要看A的那些部分可以访问到B
项目1是一个java工程,已经打包并安装到maven本地仓库,他只有1个.class文件
项目2是一个web工程,已经在pom.xml中配置项目1作为依赖,且scope=compile
我们在项目2的main目录下的.java文件中导入项目1jar包中的类,然后编译项目2,成功,说明可以访问
在IDE中感受更强一些
比如说这个开发过程,如果说在IDE中可以用点运算符.调出来那就算是有效
而部署到服务器的意思是,这个依赖是否参与打包,如果参与打包,那么lib文件夹里边就会有这个jar包
这么一说我就明白了,
servlet-api.jar的scope是provided,所以不参与打包,也就不在lib中
pro-01.jar的scope是compile,所以参与打包,那就会在lib中
对于一个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.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
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包
按照依赖的传递性,我们只需要配置依赖树的根依赖就可以了,那如果节点依赖不存在怎么办呢???
之所以产生这个问题,我想是因为我对整个流程还不够熟悉
比如说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.mavengroupId>
<artifactId>pro01-maven-javaartifactId>
<version>1.0-SNAPSHOTversion>
<scope>compilescope>
<exclusions>
<exclusion>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
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.mavengroupId>
<artifactId>pro03-maven-parentartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>pompackaging>
打包方式为 pom 的 Maven 工程中不写业务代码,它是专门管理其他 Maven 工程的工程
因为不写业务代码,所以呢自带的依赖就可以删掉了
类似于IDEA那种工程结构,在父工程的根目录创建一个一个子工程(模块工程)
从继承角度,是在父工程中创建多个子工程
从聚合角度,是在总工程中创建多个模块工程
很明显,IDEA采用的是聚合角度
我们在父工程下没创建一个子工程,父工程的pom就会自动配置一个module
<modules>
<module>pro04-maven-modulemodule>
<module>pro05-maven-modulemodule>
<module>pro06-maven-modulemodule>
modules>
后面会提到这就是聚合要在pom.xml中做的配置
在父工程的根目录下创建的子工程的pom中会自动配置父工程,方式呢就是给出父工程的坐标(虽然父工程没有打包)
<parent>
<groupId>com.atguigu.mavengroupId>
<artifactId>pro03-maven-parentartifactId>
<version>1.0-SNAPSHOTversion>
parent>
子工程的pom里边包含父子工程的坐标,如果父子工程坐标的groupId和version一样,那么就可以省略
一般这两个向量都是一样的
增加了一个标签,dependencyManagement
我们可以看到,所有的依赖都放到了父工程,但是没有scope值,默认都是compile
父工程统一管理依赖版本的机制也越来越清晰:父工程配置依赖的版本,子工程配置父工程中的某个依赖;感觉上父工程变成了一个专属于子工程的临时仓库了
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>4.0.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>4.0.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>4.0.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-expressionartifactId>
<version>4.0.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>4.0.0.RELEASEversion>
dependency>
dependencies>
dependencyManagement>
在子工程中配置父工程中配置的某个依赖
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
dependency>
前面说了,父工程配置依赖的版本,子工程配置父工程中的某个依赖
所以有这么几种情况:
(1)子工程省略依赖的版本,依赖父工程的那个版本
(2)子工程设置了一个不同于父工程的版本,依赖自己写的这个版本
但一般情况,子类的依赖版本和父类保持一致
我们这个例子,父工程依赖的是spring框架相关的jar包,spring有要求,这些包的版本必须一致,但是按照我们上面的配置方法,配置了5个依赖,如果要改版本号,我得改5次;下次要是配置50个依赖,那我就得改50次版本,太麻烦了
于是,我们在属性标签自定义一个标签用来表示spring依赖的版本
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<atguigu.spring.version>4.3.6.RELEASEatguigu.spring.version>
properties>
使用自定义属性时,使用${}的形式来引用自定义的属性名
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<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
答案是不会,没有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包