Maven——基础篇
Maven出现前的问题
- 一个项目就是一个工程,而工程内一般是通过package包来分模块,比较用户模块,订单模块等,如果项目过于庞大,通过包模块来划分就不太合适,而应该拆分为模块,这时候就要借助于maven来分模块,将一个项目拆分为多个工程。
- 项目依赖的jar包需要手动去复制甚至自己去寻找jar包,然后添加到项目工程的
WEB_INF/lib
目录下,这样才能使用到jar包上的功能,这种情况下jar包的寻找及其浪费时间且没有统一的下载渠道,非常地耗时耗力且不安全,而且项目中添加的jar包数量多会非常臃肿且不能复用。通过maven统一从中央仓库去拉去想要依赖的jar包非常简便且安全,而且拉取下来的jar包保存在本地仓库中,可以多个项目复用,我们需要依赖的jar包只要在maven中配置依赖即可。 - jar包的层级依赖问题在maven使用前需要自己解决,自己去依照经验或者是手册添加必要的依赖,比如spring-web需要依赖spring-core等jar包,需要自己去手动维护添加,增加学习成本。而使用maven依赖则只要我们去依赖了spring-web,则maven自动会帮我们解决层级依赖问题,自动导入需要依赖的jar包。
Maven是什么
Maven是一款服务于Java平台的自动化构建工具。
自动化构建工具发展过程:Make——> Ant——>Maven——>Gradle
构建
构建即将项目Java源文件、配置、图片等资源生成一个可运行项目的过程。对于一个web工程来说,就要执行编译,即把Java文件编译成class文件,再构建成可部署的项目文件war或者jar。maven对于Java Web工程到可部署war包的作用就像厨师对活的鸡处理成可食用的熟鸡一个道理。
Web工程编译构建后变成可执行部署的项目工程的目录结构图如下,当我们在代码中使用路径特别是相对路径时,要用的就是编译后文件所在的路径,因此如果可以最后拿绝对路径,避免因为项目编译后的工程目录路径变化导致相对路径失效。
源项目和编码后的可执行部署项目对比图
注意:开发过程中所有的路径或配置文件中的路径等都是以编译结果后的目录结构为标准的。项目工程只是开发环境,真正运行的是编译构建后的可部署文件如war、jar。
构建过程中的各个环节
- 清理——将以前编译的旧的class字节码删除,为下一次编译做准备
- 编译——将java文件编译成class字节码文件
- 测试——自动测试,自动调用Junit程序测试,目的是通过开发写的Junit测试代码确保核心环节无问题测试通过。
- 报告——测试程序执行的结果
- 打包——Web工程打War包,Java工程打Jar包
- 安装——Maven的特定概念,将打包后的文件复制到仓库中的指定位置。比如我们把user-api安装到仓库中,提供给其他模块依赖。
- 部署——将Web工程生成的War包复制到Servlet容器(Tomcat)的指定目录下,使其可运行
#输出环境变量值
C:\Users\castamere>echo %JAVA_HOME%
D:\techsoft\java8\jdk
#查看maven环境变量是否配置成功及maven版本
C:\Users\castamere>mvn -v
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: D:\techsoft\maven\apache-maven-3.6.3\bin\..
Java version: 1.8.0_251, vendor: Oracle Corporation, runtime: D:\techsoft\java8\jdk\jre
Default locale: zh_CN, platform encoding: GBK
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
Maven的核心概念
- 约定的目录结构
- POM——Maven工程的核心配置文件
- 坐标——指定项目的唯一位置
- 依赖——通过依赖引用第三方jar包等
- 仓库——依赖的jar包文件所在的位置,有本地和远程仓库两种
- 生命周期/插件/目标
- 继承
- 聚合
Maven常用命令
- mvn clean——清理
- mvn compile——编译主程序
- mvn test-compile——编译测试程序
- mvn test——执行测试
- mvn package——打包,生成war包或者jar包
- mvn install——安装
- mvn site——生成站点
- mvn deploy——将生成的jar包复制到远程仓库中
注意:执行与项目构建过程相关的Maven命令,必须要进入pom.xml所在的目录,即项目名目录下。
注意:maven执行命令都是从头开始执行的,也就是说第一个执行的操作是mvn clean操作。
POM——Project Object Mode——项目对象模型
类似于DOM——文档对象模型,也就是说,在编程中,我们把文档或者对象抽象成一个模型来进行表示研究,就像我们把地球抽象成地球仪一样进行研究一样。
pom.xml是Maven工程的核心配置文件,与构建过程相关的一切设置都在这个文件中进行配置,其重要程度相当于web.xml对于动态Web工程。
坐标——GAV
相当于三维空间中的xyz轴上唯一定位的坐标点。
Maven的坐标使用groupId
artifactId
version
来唯一定位一个Maven工程。
- groupId:公司或组织域名倒序+项目名。(加项目名是因为一般一个公司不止一个项目,就像滴滴除了有打车项目,还有滴滴金融等)
- artifactId:模块名,一个项目有多个模块。
- version:版本号,每个模块都会进行版本迭代。
如何通过坐标到Maven参考中寻找依赖的jar包文件的位置
GAV示例:
<dependency>
<groupId>com.didi.financial</groupId>
<artifactId>order</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
其对应的Maven仓库中的位置就是:
com/didi/financial/order/1.0.0.RELEASE/order-1.0.0.RELEASE.jar
groupId中的点替换成文件夹斜杠,并以此把模块和版本都作为文件夹,最后的jar包文件名是模块名-版本号.jar的形式
仓库
仓库的分类
- 本地仓库:当前电脑或服务器上的仓库,为本地服务器或电脑服务。我们的依赖会将依赖的jar从远程仓库中拉去到本地仓库中来,这样下一次获取依赖的jar包时就只需要读取本地参考的jar包即可。
- 远程仓库:
- 私服(一般用nexus搭建):搭建在局域网环境中,为局域网内的Maven工程提供服务,一般公司都会搭建自己的私服,这样才能把公司各个项目模块间相互依赖的jar包发布到私服上供其他依赖的项目模块拉去jar包依赖;另外一个作用作为访问外网的代理服务器,替我们上外网拉去想要的Maven依赖。
- 中央仓库:架设在Intenet上,为全世界所有Maven工程服务,当我们从本地及私服中找不到依赖的jar包时,会去中央仓库搜索。
- 中央仓库镜像:为中央仓库分担流量,提升用户访问速度,比如阿里云的maven镜像。
仓库中保存的内容
- Maven自身所需要的插件
- 第三方框架或者工具的jar包
- 我们自己开发的Maven工程
<!--快照版写法,只要加入SNAPSHOT就是快照版,对于快照版只要我们一发布,依赖快照版的其他项目便会在下次重启时会优先去远程仓库中查看是否有最新的,有则拉取新的快照版立刻更新到本地仓库中-->
<version>1.0-SNAPSHOT</version>
<!--稳定发行版写法,当我们项目模块测试通过后会打正式版,相对于快照版就是其是稳定的,发布到了远程仓库,有一个项目依赖了这个库,它第一次构建的时候会把该库从远程仓库中下载到本地仓库缓存,以后再次构建都不会去访问远程仓库了-->
<version>2.2.2.RELEASE</version>
<version>2.0.1-RELEASE</version>
注意: 对于快照版本,-SNAPSHOT必须大写,如果小写snapshot,maven会认为其是releases版本。 定义一个组件/模块为快照版本,只需要在pom文件中在该模块的版本号后加上-SNAPSHOT即可(注意这里必须是大写)。release版本不允许修改,每次进行release版本修改,发布必须提升版本号。而snapshot一般是开发过程中的迭代版本,snapshot更新后,引用的项目可以不修改版本号自动下载构建。
依赖
依赖的范围
- maven解析依赖信息时会到本地仓库去寻找被依赖的jar包,对于公司项目中需要依赖的模块,比如order项目的
order-starter
,这时候我们要对order项目的order-starter
执行mvn install
命令将其安装到仓库中,这样其他模块才能依赖到该jar包。 - 依赖的范围主要有:
- compile——例子:spring-core.jar
- test——例子:junit.jar
- provided——不参与打包部署——例子:servlet-api.jar
注意:对于compile范围的依赖对项目模块中的test程序是可以看得到
的。而主程序main是看不到测试程序的编译结果的。因为我们测试程序在开发阶段需要执行测试主程序的代码,需要能看到,而我们的主程序是不需要依赖测试程序test的代码的,因此设定在打包安装阶段会排除掉test范围的依赖,也因此看不到测试程序的代码。
依赖的传递性
通过依赖的传递性这样我们就不需要在每个模块工程中重复声明依赖,只需要在需要依赖的第一个工程或者说最下面的工程中声明依赖即可,这样其他依赖这个工程的模块就自动间接依赖了这个jar包,有点类似于Java的继承树概念。
注意:非compile范围的依赖不能传递,因此工程模块中如果有需要就要重新重复声明依赖。
依赖的排除
因为依赖会有传递性,所有当我们并不需要传递过来的间接依赖时可以通过execlusions
标签进行依赖的排除,特别是当间接依赖特别多需要对工程进行“瘦身”时,就要用到。
示例:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.2.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
</exclusions>
</dependency>
依赖的原则
- 路径最短者优先原则(依赖传递的路径)
- 先声明者优先原则(
dependency
标签中依赖声明从上到下的先后顺序)
统一管理所依赖jar包的版本——定义统一的版本号properties
之所以要定义统一的版本号是因为当我们依赖的jar包比如spring有好多个,这多个jar包一般版本号使用都是同样的,这时候使用properties
自定义标签统一配置即可,再使用${}获取配置值。
注意:properties标签中声明的自定义标签凡是需要统一声明再引用的场合都适用。
<properties>
<springboot.version>2.2.2.RELEASE</springboot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
生命周期——构建环节执行的顺序
- 生命周期各个构建环节执行的顺序是定义好的,不能被打乱,当我们执行一个命令比如
mvn package
时,在package前的compile、test等阶段也都会依次执行,即无论执行什么命令都会从这个生命周期的最初的位置开始执行。 - Maven的核心程序定义了抽象的生命周期,而生命周期中各个阶段的具体任务是由插件来完成的。
- 插件和目标(即我们执行调用插件功能的命令)
- 生命周期的各个阶段仅仅是定义了要执行的任务是什么,就像我们定义的接口一样,具体的执行由插件来实现。
- 各个阶段和插件的目标是对应的。
- 相似的目标由特定相同的插件来完成。比如compile和test-compile的执行插件都是
maven-compiler-plugin
完成。
Maven的三套生命周期
继承
目的是统一管理项目各个模块中对相同声明的依赖的版本。
对于相同的依赖统一提取到父工程中,在子工程中声明依赖引用时不指定版本号,以父工程的统一设定为准,方便管理修改。
步骤
- 创建一个打包方式为pom的父工程
- 子工程中声明对父工程的引用
- 子工程删除GAV中的GV即组织id和版本号
- 父工程中添加
dependencyManagement
统一依赖标签 - 子工程删除统一依赖jar包的版本号,交由父工程管理
注意:配置模块的父工程后,要对该模块执行安装install命令时,如果没有配置聚合,则要在执行安装前先对父模块工程先执行install安装命令才能install安装成功。
示例
父工程pom
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hello.maven</groupId>
<artifactId>hello-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 聚合各个工程模块-->
<modules>
<module>hello-spring</module>
<module>hello-mybatis</module>
</modules>
<packaging>pom</packaging>
<!--配置依赖管理-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
子工程pom
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hello-parent</artifactId>
<groupId>com.hello.maven</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hello-spring</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hello-parent</artifactId>
<groupId>com.hello.maven</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- 只需要声明模块名,其他组织名和版本号交由父工程模块管理-->
<artifactId>hello-mybatis</artifactId>
<dependencies>
<!-- 不需要声明依赖的版本号,交由父工程统一管理-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
聚合
目的是为了一键安装各个模块工程,统一构建。
配置方式是在一个总的聚合工程中,一般就是父工程中去配置各个参与聚合的模块。
<!-- 聚合各个工程模块-->
<modules>
<module>hello-spring</module>
<module>hello-mybatis</module>
</modules>
在聚合的工程目录下执行install命令:
F:\waynecode\hello-parent>mvn install
安装顺序如下:
注意:maven会自己识别依赖关系进行安装,并不会因为我们声明的模块先后顺序不同而导致安装失败。
Web工程自动部署
maven自动化构建配合Jenkins使用,用于服务器部署。
待学习Jenkins持续集成时深入学习
方式1:
<build>
<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<includes>
<include>*.yml</include>
<include>*.properties</include>
<include>mappers/**</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.3.2</version>
<executions>
<execution>
<id>exec-npm-install</id>
<phase>prepare-package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>install</argument>
<argument>--registry=http://verdaccio.zc.com</argument>
<argument>--unsafe-perm</argument>
</arguments>
<workingDirectory>../zc-fe</workingDirectory>
</configuration>
</execution>
<execution>
<id>exec-npm-run-build</id>
<phase>prepare-package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>build</argument>
<argument>${env}</argument>
</arguments>
<workingDirectory>../zc-fe</workingDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}/static</outputDirectory>
<resources>
<resource>
<directory>../zc-fe/dist</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>${main-class}</mainClass>
<outputDirectory>${project.parent.build.directory}/zc/</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
方式2:
<build>
<finalName>mall</finalName>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes/static</outputDirectory>
<resources>
<resource>
<directory>../mall-admin/dist</directory>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-resources-vue</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes/static/vue</outputDirectory>
<resources>
<resource>
<directory>../mall-vue/dist</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
扩展
- JSP的本质是Servlet,是后端的技术。
-
统一的规范
对IT开发领域非常重要,引申出来就是约定大于配置,配置大于编码。 - 把逻辑判断等理性规范的事情都交给机器来做,就像以前的电话接线员被取消一样,不需要这样一个操作。
- 机器就是用来做特别枯燥无聊的重复性工作,所有如果有可机械化程序化的工作都会被机器取代。
- 解压文件注意放到非中文无空格的路径下,避免很多错误的产生。
- 配置环境变量的规律——一般%MAVEN_HOME%配置的是bin目录的上一级文件路径地址,而path配置的一般是带bin的,比如%MAVEN_HOME%bin。
- 约定大于配置大于编码。想通过配置或者约定解决问题就要对项目的框架以及架构有比较深的理解。
- 对于maven工程来说,只要有pom.xml就看做是一个maven工程
疑问
如何向maven中央仓库发布自己的jar包呢?
简单说和我们发布到自己公司的仓库是差不多的,就是拥有一个账号密码,配置到settings.xml中,然后向sonatype提交一个issue,通过后变可以构建发布自己的jar包了。
如何用nexus搭建Maven私服?
后续单独写一篇实践文章。
Java项目的第一方、第二方、第三方的意思?
第一方是指JDK,第二方是指我们项目工程自己本身,第三方是指我们依赖借助的其他项目或框架。
Maven设置JDK版本
方法1:找到maven工程的settings.xml文件,找到 <profiles>
标签,在标签内配置jdk版本即可。
<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>
方法2:在maven项目的pom.xml中添加 ,在build->plugins
标签下添加:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
jsp-api
依赖的scope作用范围导致的空指针异常
这个是因为tomcat已经有提供了这个jar包,所有当我们在项目中依赖使用时,就要对该依赖配置作用范围为provided
,这样才不会参与打包部署,也就不会产生冲突。
参考