当再次回来学习Java Web的相关内容的时候,发现很多东西已经物是人非了,还记得自己之前用SSH框架开发网站,会从网上搜集各种jar包,然后备份到自己的硬盘上,防止用的时候丢掉的麻烦劲儿,而今重新开始使用Maven后猛然觉得真香,不得不感慨技术真的是日新月异。
前Maven时代
在了解Maven之前,我们先来看看一个Java Web项目需要的东西管理哪些内容:
- 首先,我们要确定项目的目录结构。例如,src目录存放Java源码,resources目录存放配置文件,bin目录存放编译生成的.class文件。
- 其次,我们需要确定引入哪些依赖包。例如,如果我们需要用到commons logging,我们就必须把commons logging的jar包放入classpath。如果我们还需要log4j,就需要把log4j相关的jar包都放到classpath中。这些就是依赖包的管理。
- 最后,我们还需要配置环境,例如JDK的版本,编译打包的流程,当前代码的版本号。
这些工作难度不大,但是非常琐碎且耗时。如果每一个项目都自己搞一套配置,肯定会一团糟。我们需要的是一个标准化的Java项目管理和构建工具。Maven就是是专门为Java项目打造的管理和构建工具。
基本功能
Maven,它是一个项目管理工具,官方站点为:Maven官网,Maven的主要关键字如下:
- 项目结构:Maven 可以整合多个项目之间的引用关系并给出确定性项目结构,我们可以根据业务和分层需要任意拆分一个项目
- 依赖管理:Maven 提供规范的管理各个常用 jar 包及其各个版本,并且可以自动下载和引入项目中,Maven 可以根据指定版本自动解决 jar 包版本兼容问题, 可以把 jar 包所依赖的其它 jar 包自动下载并引入项目
- 构建流程:Maven 是目前最流行的自动化构建工具,对于生产环境下多框架、多模块整合开发有重要作用,提供了一套标准化的构建流程(编译,测试,打包,发布……)
类似自动化构建工具还有Ant, Maven, Gradle,当然Ant已经是昨日黄花了,而Gradle则是后起之秀,之后如果有需要会详细写一篇对Gradle的学习Blog。
项目结构
一个使用Maven管理的普通的Java项目,它的目录结构默认如下:
myfirstjava |--- src |---|--- main |---|---|--- java |---|---|--- resources |---|--- test |---|---|--- java |---|---|--- resources |--- pom.xml
各项配置属性的含义如下:
- myfirstjava:根目录,也就是项目名。
- src:源代码。
- main:主程序。
- main-java:主程序的Java源码。
- main-resources:主程序的配置文件。
- test:测试程序。
- test-java:测试程序的Java源码。
- test-resources:测试程序的配置文件。
- pom.xml:Maven项目的核心配置文件
而如果是个通过maven构建的JavaWeb项目的话,其配置结构如下:
各项配置属性的含义如下:
- myfirstweb:根目录,也就是项目名。
- src:源代码。
- main:主程序。
- main-java:主程序的Java源码。
- main-resources:主程序的配置文件。
- test:测试程序。
- test-java:测试程序的Java源码。
- test-resources:测试程序的配置文件。
- webapp:网站项目
- webapps-WEB-INF:编译的Servlet的class文件和web配置文件
- webapps-index.jsp:首页地址
- pom.xml:Maven项目的核心、核核心配置文件
所有的目录结构都是约定好的标准结构,我们千万不要随意修改目录结构。使用标准结构不需要做任何配置,Maven就可以正常使用
pom配置文件
依赖管理的核心即是pom.xml文件,项目的依赖包都由这个核心文件统一管理,接下来我们依照最近我在做的一个javaweb项目做示例解读下【Java Web编程 二】第一个Java Web项目(Idea2021+Maven3+Tomcat8+JDK8)的pom.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>myfirstweb</artifactId> <version>1.0-SNAPSHOT</version> <name>myfirstweb</name> <packaging>war</packaging> <properties> <java.version>1.8</java.version> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> <junit.version>5.7.1</junit.version> </properties> <dependencies> <!--引入servlet相关依赖,https://repo.maven.apache.org/maven2/javax/servlet/servlet-api/--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!--引入jsp相关依赖,https://repo.maven.apache.org/maven2/javax/servlet/jsp/jsp-api/--> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.2.1</version> <scope>provided</scope> </dependency> <!--引入junit测试引擎相关依赖--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/javax.servlet/jstl --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- https://mvnrepository.com/artifact/taglibs/standard --> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.3.1</version> </plugin> </plugins> </build> </project>
各个属性的含义如下:
- modelVersion:Maven 模型的版本,对于 Maven2 和 Maven3 来说,它只能是 4.0.0。
- groupId:组织 id,一般是公司域名的倒写。 格式可以为:域名倒写。 例如 com.baidu;域名倒写+项目名。例如 com.baidu.appolo。
- artifactId:项目名称,也是模块名称,对应 groupId 中 项目中的子项目。
- version:项目的版本号。如果项目还在开发中,是不稳定版本,通常在版本后带-SNAPSHOTversion 使用三位数字标识,例如 1.1.0。
- packaging:项目打包的类型,可以使 jar、war、rar、ear、pom,默认是 jar。
- properties :properties是 用 来 定 义 一 些 配 置 属 性 的 , 例 如project.build.sourceEncoding(项目构建源码编码方式),可以设置为UTF-8,防止中文乱码,也可定义相关构建版本号,例如java的版本号,便于日后统一升级。
- dependencies、dependency:Maven 的一个重要作用就是管理 jar 包,为了一个项目可以构建或运行,项目中不可避免的,会依赖很多其他的 jar 包,在 Maven 中,这些 jar 就被称为依赖,使用标签 dependency 来配置。而这种依赖的配置正是通过坐标来定位的。
- build:build 表示与构建相关的配置,例如设置编译插件的maven 版本
多模块时使用:
- parent:在 Maven 中,如果多个模块都需要声明相同的配置,例如:groupId、version、有相同的依赖、或者相同的组件配置等,也有类似 Java 的继承机制,用 parent 声明要继承的父工程的 pom 配置。
- modules :在 Maven 的多模块开发中,为了统一构建整个项目的所有模块,可以提供一个额外的模块,该模块打包方式为 pom,并且在其中使用 modules 聚合的其它模块,这样通过本模块就可以一键自动识别模块间的依赖关系来构建所有模块,叫 Maven 的聚合。
以上的属性配置可以划分为几个功能部分。
坐标
groupId、artifactId、version
这三者结合生成了一 个Maven 项目的基本坐标,在众多的maven 项目中可以唯一定位到某一个项目。坐标也决定着将来项目在仓库中的路径及名称。
<groupId>com.example</groupId> <artifactId>myfirstweb</artifactId> <version>1.0-SNAPSHOT</version>
依赖
如果我们的项目依赖第三方的jar包,例如依赖spring-boot,而spring-boot又依赖几十个包,如果没有依赖管理,那么我们将陷入到寻找繁琐的包中。
依赖管理
Maven解决了依赖管理问题。例如,我们的项目依赖spring-boot,而spring-boot又依赖其他包,甚至依赖的层次很深,Maven也会将这些包悉数导入。
<!--引入servlet相关依赖,https://repo.maven.apache.org/maven2/javax/servlet/servlet-api/--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency>
以上就是一个引入servlet的例子。
依赖关系
我们可以看到有一个scope的属性,这个是定义依赖的关系的,共有四个级别。分别是compile、test、runtime和provided。
其中,默认的compile是最常用的。
- compile:Maven会把这种类型的依赖直接放入classpath
- test:依赖表示仅在测试时使用,正常运行时并不需要。最常用的test依赖就是JUnit
- runtime:依赖表示编译时不需要,但运行时需要。最典型的runtime依赖是JDBC驱动
- provided:依赖表示编译时需要,但运行时不需要。最典型的provided依赖是Servlet API,编译的时候需要,但是运行时,Servlet服务器内置了相关的jar,所以运行期不需要
我们引入的时候可以依据自己的需求使用,大多数用默认即可。
那么我们比较好奇的是,给出了坐标,从哪里把jar包导入呢?答案就是仓库。
仓库
在Maven中,任何一个依赖、插件或者项目构建的输出,都可以称之为构件。Maven核心程序仅仅定义了自动化构建项目的生命周期,但具体的构建工作是由特定的构件完成的。而且为了提高构建的效率和构件复用,maven把所有的构件统一存储在某一个位置,这个位置就叫做仓库。
仓库内容
Maven 仓库存放以下三类内容,一类是我们构建项目时依赖的三方框架或包,一类是我们构建完项目要推送到仓库的自己的项目,最后一类就是Maven的插件:
- Maven 的插件,插件也是一些 jar,这些 jar 可以完成一定的功能。
- 我们自己开发项目的模块【开发完成后推送到Maven仓库】
- 第三方框架或工具的 jar 包
先对插件有个基本概念,我们接下来在构建流程中详细介绍下Maven的插件。
仓库分类
仓库共分为两类,远程仓库和本地仓库:
- 本地仓库:本机当前电脑上的资源存储位置,为本机上所有 Maven工程提供服务
- 远程仓库:不在本机上, 通过网络才能使用。多电脑共享使用的。
- 中央仓库:通过Internet访问,为全世界所有 Maven工程服务。 最权威的。
- 中央仓库的镜像:架设在不同位置,欧洲,美洲,亚洲等每个洲都有若干的服务器,为中央仓库分担流量。减轻中央仓库的访问,下载的压力。所在洲的用户首先访问的是本洲的镜像服务器。
- 私服:在局域网环境中部署的服务器,为当前局域网范围内的所有 Maven工程服务。公司中常使用这种方式。
在 Maven 构建项目的过程中如果需要某些插件,那么查找流程如下:
- 首先会到 Maven 的本地仓库中查找,如果找到则可以直接使用;
- 如果找不到,它会自动连接外网,到远程中央仓库中查找;
- 如果远程仓库中能找到,则先把所需要的插件下载到本地仓库,然后再使用,并且下次再用到相同的插件也可以直接使用本地仓库的;
- 如果没有外网或者远程仓库中也找不到,则构建失败。
我们通常使用的默认中央仓库镜像地址为:
https://repo.maven.apache.org/maven2/javax/servlet/jsp/jsp-api/
本地仓库地址位置可以修改,默认地址如下 :
在本地仓库我们可以看到下载的插件【MAC中直接按下键盘上的【command+shift+.】,这时候就可以在mac系统中就会自动显示隐藏的文件夹了】:
仓库镜像替换
上文说到我们默认的仓库镜像地址为:
https://repo.maven.apache.org/maven2/javax/servlet/jsp/jsp-api/
但其实受制于国外的网络,我们可以替换镜像地址,找到settings文件:
在其中加入如下地址:
<mirror> <id>nexus-aliyun</id> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror>
仓库资源搜索
同时想知道该通过什么配置引入三方框架和资源,可以从仓库资源的搜索地址使用groupId 或者 artifactId作为搜索条件)搜索获得
本地仓库默认存放的路径为:C:\Users\123.m2\repository(里面存放的基本上都是 .jar 架包、各种配置文件)
构建流程
Maven不但有标准化的项目结构,而且还有一套标准化的构建流程,可以自动化实现编译,打包,发布等。
Maven生命周期
使用Maven时,我们首先要了解什么是Maven的生命周期(lifecycle
)。Maven的生命周期由一系列阶段(phase
)构成,以内置的生命周期default为例,它包含以下phase:
如果我们运行mvn package
,Maven就会执行default生命周期,它会从开始一直运行到package这个phase为止:
validate ... package
以此类推:我们使用mvn这个命令时,后面的参数是phase,Maven自动根据生命周期运行到指定的phase,其实除了phase,在phase的下层还有一个叫做goal的最小任务单元。
执行一个phase又会触发一个或多个goal:
goal的命名总是abc:xyz
这种形式
Maven常用命令
Maven 提供一个项目构建的模型,把编译、测试、打包、部署等都对应成一个个的生命周期阶段,并对每一个阶段提供相应的命令,程序员只需要掌握一小堆命令,就可以完成项目的构建过程。
- mvn clean:清理(会删除原来编译和测试的目录,即 target 目录,但是已经 install 到仓库里的包不会删除)
- mvn compile:编译主程序(会在当前目录下生成一个 target,里边存放编译主程序之后生成的字节码文件)
- mvn test-compile:编译测试程序(会在当前目录下生成一个 target,里边存放编译测试程序之后生成的字节码文件)
- mvn test:测试(会生成一个目录surefire-reports,保存测试结果)
- mvn package:打包主程序(会编译、编译测试、测试、并且按照 pom.xml 配置把主程序打包生成 jar 包或者 war 包)
- mvn install:安装主程序(会把本工程打包,并且按照本工程的坐标保存到本地仓库中)
- mvn deploy:部署主程序(会把本工程打包,按照本工程的坐标保存到本地库中,并且还会保存到私服仓库中。还会自动把项目部署到 web 容器中)。
注意:执行以上命令必须在命令行进入 pom.xml 所在目录,但其实如果在IDEA里操作,这些命令都被集成了,所以直接双击对应命令即可:
Maven内置插件
在前文我们了解到,插件也是一些 jar,这些 jar 可以完成一定的功能。实际上,执行每个phase都是通过某个插件(plugin)来执行的,Maven本身其实并不知道如何执行compile,它只是负责找到对应的compiler
插件,然后执行默认的compiler:compile
这个goal来完成编译。
所以,使用Maven,实际上就是配置好需要使用的插件,然后通过phase调用它们,在idea里我们也可以看到这些插件: