Maven高级
# Maven高级
1)分模块开发与设计
1.1)工程模块与模块划分
1.2)ssm_pojo拆分
新建模块
拷贝原始项目中对应的相关内容到ssm_pojo模块中
实体类(User)
配置文件(无)
1.3)ssm_dao拆分
-
新建模块
-
拷贝原始项目中对应的相关内容到ssm_dao模块中
-
数据层接口(UserDao)
-
配置文件:保留与数据层相关配置文件(3个)
-
注意:分页插件在配置中与SqlSessionFactoryBean绑定,需要保留
-
pom.xml:引入数据层相关坐标即可,删除springmvc相关坐标
-
spring
-
mybatis
-
spring 整合mybatis
-
mysql
-
druid
-
pagehelper
-
直接依赖ssm_pojo(对ssm_pojo模块执行install指令,将其安装到本地仓库)
-
-
1.4)ssm_service拆分
-
新建模块
-
拷贝原始项目中对应的相关内容到ssm_service模块中
-
业务层接口与实现类(UserService、UserServiceImpl)
-
配置文件:保留与数据层相关配置文件(1个)
-
pom.xml:引入数据层相关坐标即可,删除springmvc相关坐标
-
spring
-
junit
-
spring 整合junit
-
直接依赖ssm_dao(对ssm_dao模块执行install指令,将其安装到本地仓库)
-
间接依赖ssm_pojo(由ssm_dao模块负责依赖关系的建立)
-
-
修改service模块spring核心配置文件名,添加模块名称,格式:applicationContext-service.xml
-
修改dao模块spring核心配置文件名,添加模块名称,格式:applicationContext-dao.xml
-
修改单元测试引入的配置文件名称,由单个文件修改为多个文件
-
1.5)ssm_control拆分
-
新建模块(使用webapp模板)
-
拷贝原始项目中对应的相关内容到ssm_controller模块中
-
现层控制器类与相关设置类(UserController、异常相关……)
-
配置文件:保留与表现层相关配置文件(1个)、服务器相关配置文件(1个)
-
pom.xml:引入数据层相关坐标即可,删除springmvc相关坐标
-
spring
-
springmvc
-
jackson
-
servlet
-
tomcat服务器插件
-
直接依赖ssm_service(对ssm_service模块执行install指令,将其安装到本地仓库)
-
间接依赖ssm_dao、ssm_pojo
-
-
修改web.xml配置文件中加载spring环境的配置文件名称,使用*通配,加载所有applicationContext-开始的配置文件
-
小节
分模块开发
-
模块中仅包含当前模块对应的功能类与配置文件
-
spring核心配置根据模块功能不同进行独立制作
-
当前模块所依赖的模块通过导入坐标的形式加入当前模块后才可以使用
-
web.xml需要加载所有的spring核心配置文件
2)聚合
2.1)多模块构建维护
2.2)聚合
-
作用:聚合用于快速构建maven工程,一次性构建多个项目/模块。
-
制作方式:
-
创建一个空模块,打包类型定义为pom
<packaging>pom</packaging>
-
定义当前模块进行构建操作时关联的其他模块名称
<modules> <module>../ssm_controller</module> <module>../ssm_service</module> <module>../ssm_dao</module> <module>../ssm_pojo</module> </modules>
-
注意事项:参与聚合操作的模块最终执行顺序与模块间的依赖关系有关,与配置顺序无关
3)继承
3.1)模块依赖关系维护
3.2)继承
-
作用:通过继承可以实现在子工程中沿用父工程中的配置
- maven中的继承与java中的继承相似,在子工程中配置继承关系
-
制作方式:
-
在子工程中声明其父工程坐标与对应的位置
<!--定义该工程的父工程--> <parent> <groupId>com.itheima</groupId> <artifactId>ssm</artifactId> <version>1.0-SNAPSHOT</version> <!--填写父工程的pom文件--> <relativePath>../ssm/pom.xml</relativePath> </parent>
-
3.3)继承依赖定义
在父工程中定义依赖管理
<!--声明此处进行依赖管理-->
<dependencyManagement>
<!--具体的依赖-->
<dependencies>
<!--spring环境-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependencies>
<dependencyManagement>
3.4)继承依赖使用
在子工程中定义依赖关系,无需声明依赖版本,版本参照父工程中依赖的版本
<dependencies>
<!--spring环境-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
3.5)继承的资源
groupId:项目组ID,项目坐标的核心元素
version:项目版本,项目坐标的核心因素
description:项目的描述信息
organization:项目的组织信息
inceptionYear:项目的创始年份
url:项目的URL地址
developers:项目的开发者信息
contributors:项目的贡献者信息
distributionManagement:项目的部署配置
issueManagement:项目的缺陷跟踪系统信息
ciManagement:项目的持续集成系统信息
scm:项目的版本控制系统西溪
malilingLists:项目的邮件列表信息
properties:自定义的Maven属性
dependencies:项目的依赖配置
dependencyManagement:项目的依赖管理配置
repositories:项目的仓库配置
build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等
reporting:包括项目的报告输出目录配置、报告插件配置等
3.6)继承与聚合
作用
-
聚合用于快速构建项目
-
继承用于快速配置
相同点:
-
聚合与继承的pom.xml文件打包方式均为pom,可以将两种关系制作到同一个pom文件中
-
聚合与继承均属于设计型模块,并无实际的模块内容
不同点:
-
聚合是在当前模块中配置关系,聚合可以感知到参与聚合的模块有哪些
-
继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己
4)属性
4.1)版本统一的重要性
4.2)属性类别
1.自定义属性
2.内置属性
3.Setting属性
4.Java系统属性
5.环境变量属性
4.3)属性类别:自定义属性
作用
- 等同于定义变量,方便统一维护
定义格式:
<!--定义自定义属性-->
<properties>
<spring.version>5.1.9.RELEASE</spring.version>
<junit.version>4.12</junit.version>
</properties>
-
聚合与继承的pom.xml文件打包方式均为pom,可以将两种关系制作到同一个pom文件中
-
聚合与继承均属于设计型模块,并无实际的模块内容
调用格式:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
4.4)属性类别:内置属性
作用
- 使用maven内置属性,快速配置
调用格式:
${basedir}
${version}
4.5)属性类别:Setting属性
作用
- 使用Maven配置文件setting.xml中的标签属性,用于动态配置
调用格式:
${settings.localRepository}
4.6)属性类别:Java系统属性
作用
- 读取Java系统属性
调用格式
${user.home}
系统属性查询方式
mvn help:system
4.7)属性类别:环境变量属性
作用
- 使用Maven配置文件setting.xml中的标签属性,用于动态配置
调用格式
${env.JAVA_HOME}
环境变量属性查询方式
mvn help:system
5)版本管理
5.1)工程版本区分
5.2)工程版本
-
SNAPSHOT(快照版本)
-
项目开发过程中,为方便团队成员合作,解决模块间相互依赖和时时更新的问题,开发者对每个模块进行构建的时候,输出的临时性版本叫快照版本(测试阶段版本)
-
u快照版本会随着开发的进展不断更新
-
-
RELEASE(发布版本)
- u项目开发到进入阶段里程碑后,向团队外部发布较为稳定的版本,这种版本所对应的构件文件是稳定的,即便进行功能的后续开发,也不会改变当前发布版本内容,这种版本称为发布版本
5.3)工程版本号约定
约定规范:
-
<主版本>.<次版本>.<增量版本>.<里程碑版本>
-
主版本:表示项目重大架构的变更,如:spring5相较于spring4的迭代
-
次版本:表示有较大的功能增加和变化,或者全面系统地修复漏洞
-
增量版本:表示有重大漏洞的修复
-
里程碑版本:表明一个版本的里程碑(版本内部)。这样的版本同下一个正式版本相比,相对来说不是很稳定,有待更多的测试
范例:
- 5.1.9.RELEASE
6)资源配置
6.1)资源配置多文件维护
6.2)配置文件引用pom属性
- 作用
- 在任意配置文件中加载pom文件中定义的属性
- 调用格式
${jdbc.url}
-
开启配置文件加载pom属性
<!--配置资源文件对应的信息--> <resources> <resource> <!--设定配置文件对应的位置目录,支持使用属性动态设定路径--> <directory>${project.basedir}/src/main/resources</directory> <!--开启对配置文件的资源加载过滤--> <filtering>true</filtering> </resource> </resources>
7)多环境开发配置
7.1)多环境兼容
7.2)多环境配置========企业非常常用 ===重要
<!--创建多环境-->
<profiles>
<!--定义具体的环境:生产环境-->
<profile>
<!--定义环境对应的唯一名称-->
<id>pro_env</id>
<!--定义环境中专用的属性值-->
<properties>
<jdbc.url>jdbc:mysql://127.1.1.1:3306/ssm_db</jdbc.url>
</properties>
<!--设置默认启动-->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<!--定义具体的环境:开发环境-->
<profile>
<id>dev_env</id>
……
</profile>
</profiles>
7.3)加载指定环境
作用
- 加载指定环境配置
调用格式
mvn 指令 –P 环境定义id
范例
mvn install –P pro_env
8)跳过测试
8.1)跳过测试环节的应用场景
整体模块功能未开发
模块中某个功能未开发完毕
单个功能更新调试导致其他功能失败
快速打包
……
8.2)使用命令跳过测试
命令
mvn 指令 –D skipTests
注意事项
- 执行的指令生命周期必须包含测试环节
8.3)使用界面操作跳过测试
8.4)使用配置跳过测试
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<skipTests>true</skipTests><!--设置跳过测试-->
<includes> <!--包含指定的测试用例-->
<include>**/User*Test.java</include>
</includes>
<excludes><!--排除指定的测试用例-->
<exclude>**/User*TestCase.java</exclude>
</excludes>
</configuration>
</plugin>
9)私服
9.1)分模块合作开发
9.2)Nexus
Nexus是Sonatype公司的一款maven私服产品
下载地址:https://help.sonatype.com/repomanager3/download
9.3)Nexus*安装、启动与配置
启动服务器(命令行启动)
nexus.exe /run nexus
访问服务器(默认端口:8081)
http://localhost:8081
修改基础配置信息
- 安装路径下etc目录中nexus-default.properties文件保存有nexus基础配置信息,例如默认访问端口
修改服务器运行配置信息
- 安装路径下bin目录中nexus.vmoptions文件保存有nexus服务器启动对应的配置信息,例如默认占用内存空间
9.4)私服资源获取
9.5)仓库分类
宿主仓库hosted
- 保存无法从中央仓库获取的资源
- 自主研发
- 第三方非开源项目
代理仓库proxy
- 代理远程仓库,通过nexus访问其他公共仓库,例如中央仓库
仓库组group
- 将若干个仓库组成一个群组,简化配置
- 仓库组不能保存资源,属于设计型仓库
9.6)资源上传
上传资源时提供对应的信息
-
保存的位置(宿主仓库)
-
资源文件
-
对应坐标
9.7)idea环境中资源上传与下载
9.8)访问私服配置(本地仓库访问私服)
配置本地仓库访问私服的权限(setting.xml)
<servers>
<server>
<id>heima-release</id>
<username>admin</username>
<password>admin</password>
</server>
<server>
<id>heima-snapshots</id>
<username>admin</username>
<password>admin</password>
</server>
</servers>
配置本地仓库资源来源(setting.xml)
<mirrors>
<mirror>
<id>nexus-heima</id>
<mirrorOf>*</mirrorOf>
<url>http://localhost:8081/repository/maven-public/</url>
</mirror>
</mirrors>
9.9)访问私服配置( 项目工程访问私服)
配置当前项目访问私服上传资源的保存位置(pom.xml)
<distributionManagement>
<repository>
<id>heima-release</id>
<url>http://localhost:8081/repository/heima-release/</url>
</repository>
<snapshotRepository>
<id>heima-snapshots</id>
<url>http://localhost:8081/repository/heima-snapshots/</url>
</snapshotRepository>
</distributionManagement>
发布资源到私服命令
mvn deploy
下面是尚硅谷讲解maven的视频
P146集到最后的P173集
视频地址https://www.bilibili.com/video/BV12q4y147e4?p=172&vd_source=a3ca5632ce12ee8045822c508dc81551
第一节 重新认识Maven
1、Maven 的完整功能
在入门的时候我们介绍说 Maven 是一款『构建管理』和『依赖管理』的工具。但事实上这只是 Maven 的一部分功能。Maven 本身的产品定位是一款『项目管理工具』。
2、项目管理功能的具体体现
下面是 spring-boot-starter 的 POM 文件,可以看到:除了我们熟悉的坐标标签、dependencies 标签,还有 description、url、organization、licenses、developers、scm、issueManagement 等这些描述项目信息的标签。
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- This module was also published with a richer model, Gradle metadata, -->
<!-- which should be used instead. Do not delete the following line which -->
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
<!-- that they should prefer consuming it instead. -->
<!-- do_not_remove: published-with-gradle-metadata -->
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.5.6</version>
<name>spring-boot-starter</name>
<description>Core starter, including auto-configuration support, logging and YAML</description>
<url>https://spring.io/projects/spring-boot</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>https://spring.io</url>
</organization>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
</license>
</licenses>
<developers>
<developer>
<name>Pivotal</name>
<email>info@pivotal.io</email>
<organization>Pivotal Software, Inc.</organization>
<organizationUrl>https://www.spring.io</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/spring-projects/spring-boot.git</connection>
<developerConnection>scm:git:ssh://git@github.com/spring-projects/spring-boot.git</developerConnection>
<url>https://github.com/spring-projects/spring-boot</url>
</scm>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/spring-projects/spring-boot/issues</url>
</issueManagement>
<dependencies>
<dependency>
……
</dependency>
</dependencies>
</project>
所以从『项目管理』的角度来看,Maven 提供了如下这些功能:
- 项目对象模型(POM):将整个项目本身抽象、封装为应用程序中的一个对象,以便于管理和操作。
- 全局性构建逻辑重用:Maven 对整个构建过程进行封装之后,程序员只需要指定配置信息即可完成构建。让构建过程从 Ant 的『编程式』升级到了 Maven 的『声明式』。
- 构件的标准集合:在 Maven 提供的标准框架体系内,所有的构件都可以按照统一的规范生成和使用。
- 构件关系定义:Maven 定义了构件之间的三种基本关系,让大型应用系统可以使用 Maven 来进行管理
- 继承关系:通过从上到下的继承关系,将各个子构件中的重复信息提取到父构件中统一管理
- 聚合关系:将多个构件聚合为一个整体,便于统一操作
- 依赖关系:Maven 定义了依赖的范围、依赖的传递、依赖的排除、版本仲裁机制等一系列规范和标准,让大型项目可以有序容纳数百甚至更多依赖
- 插件目标系统:Maven 核心程序定义抽象的生命周期,然后将插件的目标绑定到生命周期中的特定阶段,实现了标准和具体实现解耦合,让 Maven 程序极具扩展性
- 项目描述信息的维护:我们不仅可以在 POM 中声明项目描述信息,更可以将整个项目相关信息收集起来生成 HTML 页面组成的一个可以直接访问的站点。这些项目描述信息包括:
- 公司或组织信息
- 项目许可证
- 开发成员信息
- issue 管理信息
- SCM 信息
第二节 POM 的四个层次
1、超级 POM
经过我们前面的学习,我们看到 Maven 在构建过程中有很多默认的设定。例如:源文件存放的目录、测试源文件存放的目录、构建输出的目录……等等。但是其实这些要素也都是被 Maven 定义过的。定义的位置就是:超级 POM。
关于超级 POM,Maven 官网是这样介绍的:
The Super POM is Maven's default POM. All POMs extend the Super POM unless explicitly set, meaning the configuration specified in the Super POM is inherited by the POMs you created for your projects.
译文:Super POM 是 Maven 的默认 POM。除非明确设置,否则所有 POM 都扩展 Super POM,这意味着 Super POM 中指定的配置由您为项目创建的 POM 继承。
所以我们自己的 POM 即使没有明确指定一个父工程(父 POM),其实也默认继承了超级 POM。就好比一个 Java 类默认继承了 Object 类。
那么超级 POM 中定义了哪些东西呢?点击这里查看。
2、父 POM
和 Java 类一样,POM 之间其实也是单继承的。如果我们给一个 POM 指定了父 POM,那么继承关系如下图所示:
3、有效 POM
①概念
有效 POM 英文翻译为 effective POM,它的概念是这样的——在 POM 的继承关系中,子 POM 可以覆盖父 POM 中的配置;如果子 POM 没有覆盖,那么父 POM 中的配置将会被继承。按照这个规则,继承关系中的所有 POM 叠加到一起,就得到了一个最终生效的 POM。显然 Maven 实际运行过程中,执行构建操作就是按照这个最终生效的 POM 来运行的。这个最终生效的 POM 就是有效 POM,英文叫effective POM。
②查看有效 POM的命令
mvn help:effective-pom
运行效果点击这里查看。
4、小结
综上所述,平时我们使用和配置的 POM 其实大致是由四个层次组成的:
- 超级 POM:所有 POM 默认继承,只是有直接和间接之分。
- 父 POM:这一层可能没有,可能有一层,也可能有很多层。
- 当前 pom.xml 配置的 POM:我们最多关注和最多使用的一层。
- 有效 POM:隐含的一层,但是实际上真正生效的一层。
第三节 属性的声明与引用
1、help 插件的各个目标
官网说明地址:https://maven.apache.org/plugins/maven-help-plugin
目标 | 说明 |
---|---|
help:active-profiles | 列出当前已激活的 profile |
help:all-profiles | 列出当前工程所有可用 profile |
help:describe | 描述一个插件和/或 Mojo 的属性 |
help:effective-pom | 以 XML 格式展示有效 POM |
help:effective-settings | 为当前工程以 XML 格式展示计算得到的 settings 配置 |
help:evaluate | 计算用户在交互模式下给出的 Maven 表达式 |
help:system | 显示平台详细信息列表,如系统属性和环境变量 |
2、使用 help:evaluate 查看属性值
①定义属性
<properties>
<com.atguigu.hello>good morning maven</com.atguigu.hello>
</properties>
②运行命令
在idea中双击Ctrl键 ,然后输入 help:evaluate
③运行结果
3、通过 Maven 访问系统属性
① Java 系统属性一览
[1] Java 代码
Properties properties = System.getProperties();
Set<Object> propNameSet = properties.keySet();
for (Object propName : propNameSet) {
String propValue = properties.getProperty((String) propName);
System.out.println(propName + " = " + propValue);
}
[2]运行结果
java.runtime.name = Java(TM) SE Runtime Environment
sun.boot.library.path = D:\software\Java\jre\bin
java.vm.version = 25.141-b15
java.vm.vendor = Oracle Corporation
java.vendor.url = http://java.oracle.com/
path.separator = ;
java.vm.name = Java HotSpot(TM) 64-Bit Server VM
file.encoding.pkg = sun.io
user.country = CN
user.script =
sun.java.launcher = SUN_STANDARD
sun.os.patch.level =
java.vm.specification.name = Java Virtual Machine Specification
user.dir = D:\idea2019workspace\atguigu-maven-test-prepare
java.runtime.version = 1.8.0_141-b15
java.awt.graphicsenv = sun.awt.Win32GraphicsEnvironment
java.endorsed.dirs = D:\software\Java\jre\lib\endorsed
os.arch = amd64
java.io.tmpdir = C:\Users\ADMINI~1\AppData\Local\Temp
line.separator =
java.vm.specification.vendor = Oracle Corporation
user.variant =
os.name = Windows 10
sun.jnu.encoding = GBK
java.library.path = D:\software\Java\bin;C:\WINDOWS\Sun\Java\bin;C:\WIN……
java.specification.name = Java Platform API Specification
java.class.version = 52.0
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
os.version = 10.0
user.home = C:\Users\Administrator
user.timezone =
java.awt.printerjob = sun.awt.windows.WPrinterJob
file.encoding = UTF-8
java.specification.version = 1.8
java.class.path = D:\software\Java\jre\lib\charsets.jar;D:\softw……
user.name = Administrator
java.vm.specification.version = 1.8
sun.java.command = com.atguigu.maven.MyTest
java.home = D:\software\Java\jre
sun.arch.data.model = 64
user.language = zh
java.specification.vendor = Oracle Corporation
awt.toolkit = sun.awt.windows.WToolkit
java.vm.info = mixed mode
java.version = 1.8.0_141
java.ext.dirs = D:\software\Java\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
sun.boot.class.path = D:\software\Java\jre\lib\resources.jar;D:\sof……
java.vendor = Oracle Corporation
file.separator =
java.vendor.url.bug = http://bugreport.sun.com/bugreport/
sun.io.unicode.encoding = UnicodeLittle
sun.cpu.endian = little
sun.desktop = windows
sun.cpu.isalist = amd64
②使用 Maven 访问系统属性
4、访问系统环境变量
看上图
5、访问 project 属性
①含义
使用表达式 ${project.xxx} 可以访问当前 POM 中的元素值。
②访问一级标签
${project.标签名}
6、访问 settings 全局配置
${settings.标签名} 可以访问 settings.xml 中配置的元素值
7、用途
- 在当前 pom.xml 文件中引用属性
- 资源过滤功能:在非 Maven 配置文件中引用属性,由 Maven 在处理资源时将引用属性的表达式替换为属性值
第四节 build 标签详解
1、一睹真容
在实际使用 Maven 的过程中,我们会发现 build 标签有时候有,有时候没,这是怎么回事呢?其实通过有效 POM 我们能够看到,build 标签的相关配置其实一直都在,只是在我们需要定制构建过程的时候才会通过配置 build 标签覆盖默认值或补充配置。这一点我们可以通过打印有效 POM 来看到。
所以本质上来说:我们配置的 build 标签都是对超级 POM 配置的叠加。那我们又为什么要在默认配置的基础上叠加呢?很简单,在默认配置无法满足需求的时候定制构建过程。
2、build 标签组成
从完整示例中我们能够看到,build 标签的子标签大致包含三个主体部分:
①定义约定的目录结构
参考示例中的如下部分:
<sourceDirectory>D:\idea2019workspace\atguigu-maven-test-prepare\src\main\java</sourceDirectory>
<scriptSourceDirectory>D:\idea2019workspace\atguigu-maven-test-prepare\src\main\scripts</scriptSourceDirectory>
<testSourceDirectory>D:\idea2019workspace\atguigu-maven-test-prepare\src\test\java</testSourceDirectory>
<outputDirectory>D:\idea2019workspace\atguigu-maven-test-prepare\target\classes</outputDirectory>
<testOutputDirectory>D:\idea2019workspace\atguigu-maven-test-prepare\target\test-classes</testOutputDirectory>
<resources>
<resource>
<directory>D:\idea2019workspace\atguigu-maven-test-prepare\src\main\resources</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>D:\idea2019workspace\atguigu-maven-test-prepare\src\test\resources</directory>
</testResource>
</testResources>
<directory>D:\idea2019workspace\atguigu-maven-test-prepare\target</directory>
我们能看到各个目录的作用如下:
目录名 | 作用 |
---|---|
sourceDirectory | 主体源程序存放目录 |
scriptSourceDirectory | 脚本源程序存放目录 |
testSourceDirectory | 测试源程序存放目录 |
outputDirectory | 主体源程序编译结果输出目录 |
testOutputDirectory | 测试源程序编译结果输出目录 |
resources | 主体资源文件存放目录 |
testResources | 测试资源文件存放目录 |
directory | 构建结果输出目录 |
②备用插件管理
pluginManagement 标签存放着几个极少用到的插件:
- maven-antrun-plugin
- maven-assembly-plugin
- maven-dependency-plugin
- maven-release-plugin
通过 pluginManagement 标签管理起来的插件就像 dependencyManagement 一样,子工程使用时可以省略版本号,起到在父工程中统一管理版本的效果。情看下面例子:
- 被 spring-boot-dependencies 管理的插件信息:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.6.2</version>
</plugin>
- 子工程使用的插件信息:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
③生命周期插件
plugins 标签存放的是默认生命周期中实际会用到的插件,这些插件想必大家都不陌生,所以抛开插件本身不谈,我们来看看 plugin 标签的结构:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<executions>
<execution>
<id>default-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
[1]坐标部分
artifactId 和 version 标签定义了插件的坐标,作为 Maven 的自带插件这里省略了 groupId。
[2]执行部分
executions 标签内可以配置多个 execution 标签,execution 标签内:
- id:指定唯一标识
- phase:关联的生命周期阶段
- goals/goal:关联指定生命周期的目标
- goals 标签中可以配置多个 goal 标签,表示一个生命周期环节可以对应当前插件的多个目标。
另外,插件目标的执行过程可以进行配置,例如 maven-site-plugin 插件的 site 目标:
<execution>
<id>default-site</id>
<phase>site</phase>
<goals>
<goal>site</goal>
</goals>
<configuration>
<outputDirectory>D:\idea2019workspace\atguigu-maven-test-prepare\target\site</outputDirectory>
<reportPlugins>
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
</reportPlugin>
</reportPlugins>
</configuration>
</execution>
configuration 标签内进行配置时使用的标签是插件本身定义的。就以 maven-site-plugin 插件为例,它的核心类是 org.apache.maven.plugins.site.render.SiteMojo,在这个类中我们看到了 outputDirectory 属性:
结论:每个插件能够做哪些设置都是各个插件自己规定的,无法一概而论。
3、典型应用:指定 JDK 版本
①提出问题
前面我们在 settings.xml 中配置了 JDK 版本,那么将来把 Maven 工程部署都服务器上,脱离了 settings.xml 配置,如何保证程序正常运行呢?思路就是我们直接把 JDK 版本信息告诉负责编译操作的 maven-compiler-plugin 插件,让它在构建过程中,按照我们指定的信息工作。
②暂时取消 settings.xml 配置
为了测试对 maven-compiler-plugin 插件进行配置的效果,我们暂时取消 settings.xml 中的 profile 配置。
<!-- 配置Maven工程的默认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> -->
③编写源文件代码
很明显这里用到了 Lambda 表达式,这是 JDK 1.8 才支持的语法。
package com.atguigu.maven;
public class Hello {
public void hello() {
new Thread(()->{
System.out.println("thread ...");
}).start();
}
}
此时我们执行编译命令:
④配置构建过程
<!-- build 标签:意思是告诉 Maven,你的构建行为,我要开始定制了! -->
<build>
<!-- plugins 标签:Maven 你给我听好了,你给我构建的时候要用到这些插件! -->
<plugins>
<!-- plugin 标签:这是我要指定的一个具体的插件 -->
<plugin>
<!-- 插件的坐标。此处引用的 maven-compiler-plugin 插件不是第三方的,是一个 Maven 自带的插件。 -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<!-- configuration 标签:配置 maven-compiler-plugin 插件 -->
<configuration>
<!-- 具体配置信息会因为插件不同、需求不同而有所差异 -->
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
⑤再次执行编译命令
这次执行编译命令的结果就是成功的
⑥两种配置方式比较
- settings.xml 中配置:仅在本地生效,如果脱离当前 settings.xml 能够覆盖的范围,则无法生效。
- 在当前 Maven 工程 pom.xml 中配置:无论在哪个环境执行编译等构建操作都有效。
[⑦补充说明
[1]source 标签含义
查看 Maven 官网页面 (opens new window),我们找到 source 标签的介绍:
翻译过来就是:调用 Java 编译器命令时传入的 -source 参数。那对编译器来说,-source 参数是啥意思呢?
『提供与指定发行版的源兼容性』这句话我的理解是:
- 我们写代码是按 JDK 1.8 写的——这就是『源兼容性』里的『源』。
- 指定发行版就是我们指定的 JDK 1.8。
- 『兼容性』是谁和谁兼容呢?现在源代码是既定的,所以就是要求编译器使用指定的 JDK 版本来兼容我们的源代码。
另外我们还看到:
这个功能还可以通过在 properties 标签中配置 maven.compiler.source 属性来实现。所以我们也经常会看到类似这样的配置:
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
[2]target 标签含义
调用 Java 编译器命令时传入的 -target 参数。那对编译器来说,-target 参数是啥意思呢?
『生成特定 VM 版本的类文件』这句话我的理解是:
- VM 指 JVM
- 类文件指 *.class 字节码文件
- 整体意思就是源文件编译后,生成的 *.class 字节码文件要符合指定的 JVM 版本
4、典型应用:SpringBoot 定制化打包
①需求
很显然 spring-boot-maven-plugin 并不是 Maven 自带的插件,而是 SpringBoot 提供的,用来改变 Maven 默认的构建行为。具体来说是改变打包的行为。默认情况下 Maven 调用 maven-jar-plugin 插件的 jar 目标,生成普通的 jar 包。
普通 jar 包没法使用 java -jar xxx.jar 这样的命令来启动、运行,但是 SpringBoot 的设计理念就是每一个『微服务』导出为一个 jar 包,这个 jar 包可以使用 java -jar xxx.jar 这样的命令直接启动运行。
这样一来,打包的方式肯定要进行调整。所以 SpringBoot 提供了 spring-boot-maven-plugin 这个插件来定制打包行为。
②示例代码
所有的一切已经都被 SpringBoot 封装好了,所以配置非常简单,提供插件坐标即可。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.5</version>
</plugin>
</plugins>
</build>
③插件的七个目标
目标名称 | 作用 |
---|---|
spring-boot:build-image | Package an application into a OCI image using a buildpack. |
spring-boot:build-info | Generate a build-info.properties file based on the content of the current MavenProject. |
spring-boot:help | Display help information on spring-boot-maven-plugin. Call mvn spring-boot:help -Ddetail=true -Dgoal= |
spring-boot:repackage | Repackage existing JAR and WAR archives so that they can be executed from the command line using java -jar. With layout=NONE can also be used simply to package a JAR with nested dependencies (and no main class, so not executable). |
spring-boot:run | Run an application in place. |
spring-boot:start | Start a spring application. Contrary to the run goal, this does not block and allows other goals to operate on the application. This goal is typically used in integration test scenario where the application is started before a test suite and stopped after. |
spring-boot:stop | Stop an application that has been started by the 'start' goal. Typically invoked once a test suite has completed. |
5、典型应用:Mybatis 逆向工程
使用 Mybatis 的逆向工程需要使用如下配置,MBG 插件的特点是需要提供插件所需的依赖:
<!-- 控制 Maven 在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.8</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
6、小结
不知大家有没有发现,通常需要用到 build 标签的时候底层都会帮我们封装好,需要我们配置的地方不多。即使有些地方需要我们配置,也不会真的我们自己去写,把现成的案例复制过来就行。
所以对 build 标签来说,我们的掌握要求就是:能大致看懂就行。
第五节 依赖配置补充
TIPs:
Maven 官网介绍依赖机制(opens new window)
1、依赖范围
①import
管理依赖最基本的办法是继承父工程,但是和 Java 类一样,Maven 也是单继承的。如果不同体系的依赖信息封装在不同 POM 中了,没办法继承多个父工程怎么办?这时就可以使用 import 依赖范围。
典型案例当然是在项目中引入 SpringBoot、SpringCloud 依赖:
<dependencyManagement>
<dependencies>
<!-- SpringCloud 依赖导入 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringCloud Alibaba 依赖导入 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringBoot 依赖导入 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
import 依赖范围使用要求:
- 打包类型必须是 pom
- 必须放在 dependencyManagement 中
官网说明如下:
This scope is only supported on a dependency of type
pom
in the<dependencyManagement>
section. It indicates the dependency is to be replaced with the effective list of dependencies in the specified POM's<dependencyManagement>
section. Since they are replaced, dependencies with a scope ofimport
do not actually participate in limiting the transitivity of a dependency.
②system
以 Windows 系统环境下开发为例,假设现在 D:\tempare\atguigu-maven-test-aaa-1.0-SNAPSHOT.jar 想要引入到我们的项目中,此时我们就可以将依赖配置为 system 范围:
<dependency>
<groupId>com.atguigu.maven</groupId>
<artifactId>atguigu-maven-test-aaa</artifactId>
<version>1.0-SNAPSHOT</version>
<systemPath>D:\tempare\atguigu-maven-test-aaa-1.0-SNAPSHOT.jar</systemPath>
<scope>system</scope>
</dependency>
但是很明显:这样引入依赖完全不具有可移植性,所以不要使用。如果需要引入体系外 jar 包我们后面会讲专门的办法。
③runtime
专门用于编译时不需要,但是运行时需要的 jar 包。比如:编译时我们根据接口调用方法,但是实际运行时需要的是接口的实现类。典型案例是:
<!--热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
2、可选依赖
①配置举例
<!--热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
②本质含义
可选其实就是『可有可无』。官网的解释是:
3、版本仲裁
即自动选择版本
#①最短路径优先
在下图的例子中,对模块 pro25-module-a 来说,Maven 会采纳 1.2.12 版本。
此时 Maven 采纳哪个版本,取决于在 pro29-module-x 中,对 pro30-module-y 和 pro31-module-z 两个模块的依赖哪一个先声明。
#③小结
其实 Maven 的版本仲裁机制只是在没有人为干预的情况下,自主决定 jar 包版本的一个办法。而实际上我们要使用具体的哪一个版本,还要取决于项目中的实际情况。所以在项目正常运行的情况下,jar 包版本可以由 Maven 仲裁,不必我们操心;而发生冲突时 Maven 仲裁决定的版本无法满足要求,此时就应该由程序员明确指定 jar 包版本。
第六节 Maven 自定义插件
1、本节定位
其实实际开发中几乎没有什么场景需要我们开发自定义 Maven 插件,所以本节只是通过这个角度帮助我们更好的理解插件的目标和生命周期阶段之间的关系。
2、插件开发
①创建工程
[略]
②设定打包方式
<packaging>maven-plugin</packaging>
③引入依赖
下面两种方式二选一:
[1]将来在文档注释中使用注解
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.5.2</version>
</dependency>
[2]将来直接使用注解
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.5.2</version>
</dependency>
④创建 Mojo 类
Mojo 类是一个 Maven 插件的核心类。
Mojo 这个单词的意思是:Maven Old Java Object,其实 mojo 这个单词本身包含魔力;符咒(袋);护身符;(人的)魅力的含义,Maven 用 Mojo 是因为它是对 POJO 开的一个小玩笑。
[1] Mojo 接口
每一个 Mojo 都需要实现 org.apache.maven.plugin.Mojo 接口。
[2] AbstractMojo 抽象类
我们实现 Mojo 接口比较困难,幸好可以继承 AbstractMojo,此时我们只要实现 execute() 这一个方法即可。
public class MyHelloPlugin extends AbstractMojo {
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info("---> This is my first maven plugin. <---");
}
}
3、插件配置
#①Mojo 类中的配置
#[1]文档注释中用注解
对应的 pom.xml 中的依赖: maven-plugin-api
[2]直接在类上标记注解
对应 pom.xml 中的依赖:maven-plugin-annotations
// name 属性:指定目标名称
@Mojo(name = "firstBlood")
public class MyPluginOfFistBlood extends AbstractMojo {
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info("---> first blood <---");
}
}
②安装插件
要在后续使用插件,就必须至少将插件安装到本地仓库。
#③注册插件
我们需要将插件坐标中的 groupId 部分注册到 settings.xml 中。
<pluginGroups>
<!-- pluginGroup
| Specifies a further group identifier to use for plugin lookup.
<pluginGroup>com.your.plugins</pluginGroup>
-->
<pluginGroup>com.atguigu.maven</pluginGroup>
</pluginGroups>
4、使用插件
#①识别插件前缀
Maven 根据插件的 artifactId 来识别插件前缀。例如下面两种情况:
#[1]前置匹配
- 匹配规则:${prefix}-maven-plugin
- artifactId:hello-maven-plugin
- 前缀:hello
#[2]中间匹配
- 匹配规则:maven-${prefix}-plugin
- artifactId:maven-good-plugin
- 前缀:good
#②在命令行直接用
- 命令:
mvn hello:sayHello
- 效果:
③配置到 build 标签里
这里找一个和插件无关的 Maven 工程配置才有说服力。
[1]配置
<build>
<plugins>
<plugin>
<groupId>com.atguigu.maven</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<executions>
<execution>
<id>hello</id>
<!-- 指定和目标关联的生命周期阶段 -->
<phase>clean</phase>
<goals>
<goal>sayHello</goal>
</goals>
</execution>
<execution>
<id>blood</id>
<phase>validate</phase>
<goals>
<goal>firstBlood</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
[2]效果
[4]命令行使用
执行已和插件目标绑定的生命周期:
第七节 profile 详解
#1、profile 概述
后面略
第十章 生产实践
1、Nexus 安装
#①下载地址
小诀窍:使用迅雷下载比直接用浏览器下载快很多
https://download.sonatype.com/nexus/3/latest-unix.tar.gz
#②上传、解压
上传到 Linux 系统,解压后即可使用,不需要安装。但是需要注意:必须提前安装 JDK。
③启动 Nexus
[root@x ~]# /opt/nexus-3.37.0-01/bin/nexus start
WARNING: ************************************************************
WARNING: Detected execution as "root" user. This is NOT recommended!
WARNING: ************************************************************
Starting nexus
[root@x ~]# /opt/nexus-3.37.0-01/bin/nexus status
WARNING: ************************************************************
WARNING: Detected execution as "root" user. This is NOT recommended!
WARNING: ************************************************************
nexus is running.
#④查看端口占用情况
[root@x ~]# netstat -anp | grep java
tcp 0 0 127.0.0.1:45614 0.0.0.0:* LISTEN 9872/java
tcp 0 0 0.0.0.0:8081 0.0.0.0:* LISTEN 9872/java
上面 45614 这个每次都不一样,不用管它。我们要访问的是 8081 这个端口。但是需要注意:8081 端口的这个进程要在启动 /opt/nexus-3.37.0-01/bin/nexus 这个主体程序一、两分钟后才会启动,请耐心等待。
#⑤访问 Nexus 首页
首页地址:http://[Linux 服务器地址]:8081/
初始化界面还是很酷的:
这里参考提示:
- 用户名:admin
- 密码:查看 /opt/sonatype-work/nexus3/admin.password 文件
[root@hello ~]# cat /opt/sonatype-work/nexus3/admin.password
ed5e96a8-67aa-4dca-9ee8-1930b1dd5415
所以登录信息输入如下:
3、对接 Nexus
#①通过 Nexus 下载 jar 包
#[1]了解 Nexus 上的各种仓库
仓库类型 | 说明 |
---|---|
proxy | 某个远程仓库的代理 |
group | 存放:通过 Nexus 获取的第三方 jar 包 |
hosted | 存放:本团队其他开发人员部署到 Nexus 的 jar 包 |
仓库名称 | 说明 |
---|---|
maven-central | Nexus 对 Maven 中央仓库的代理 |
maven-public | Nexus 默认创建,供开发人员下载使用的组仓库 |
maven-releasse | Nexus 默认创建,供开发人员部署自己 jar 包的宿主仓库 要求 releasse 版本 |
maven-snapshots | Nexus 默认创建,供开发人员部署自己 jar 包的宿主仓库 要求 snapshots 版本 |
初始状态下,这几个仓库都没有内容:
#[2]使用空的本地仓库
把上图中看到的地址复制出来即可。如果我们在前面允许了匿名访问,到这里就够了。但如果我们禁用了匿名访问,那么接下来我们还要继续配置 settings.xml:
<server>
<id>nexus-mine</id>
<username>admin</username>
<password>atguigu</password>
</server>
这里需要格外注意:server 标签内的 id 标签值必须和 mirror 标签中的 id 值一样。
#[4]效果
找一个用到框架的 Maven 工程,执行命令:
mvn clean compile
下载过程日志:
Downloading from nexus-mine: http://192.168.198.100:8081/repository/maven-public/com/jayway/jsonpath/json-path/2.4.0/json-path-2.4.0.pom
Downloaded from nexus-mine: http://192.168.198.100:8081/repository/maven-public/com/jayway/jsonpath/json-path/2.4.0/json-path-2.4.0.pom (2.6 kB at 110 kB/s)
Downloading from nexus-mine: http://192.168.198.100:8081/repository/maven-public/net/minidev/json-smart/2.3/json-smart-2.3.pom
Downloaded from nexus-mine: http://192.168.198.100:8081/repository/maven-public/net/minidev/json-smart/2.3/json-smart-2.3.pom (9.0 kB at 376 kB/s)
Downloading from nexus-mine: http://192.168.198.100:8081/repository/maven-public/net/minidev/minidev-parent/2.3/minidev-parent-2.3.pom
Downloaded from nexus-mine: http://192.168.198.100:8081/repository/maven-public/net/minidev/minidev-parent/2.3/minidev-parent-2.3.pom (8.5 kB at 404 kB/s)
Downloading from nexus-mine: http://192.168.198.100:8081/repository/maven-public/net/minidev/accessors-smart/1.2/accessors-smart-1.2.pom
Downloaded from nexus-mine: http://192.168.198.100:8081/repository/maven-public/net/minidev/accessors-smart/1.2/accessors-smart-1.2.pom (12 kB at 463 kB/s)
下载后,Nexus 服务器上就有了 jar 包:
②将 jar 包部署到 Nexus
#[1]配置 Maven 工程
<distributionManagement>
<snapshotRepository>
<id>nexus-mine</id>
<name>Nexus Snapshot</name>
<url>http://192.168.198.100:8081/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
1
2
3
4
5
6
7
这里 snapshotRepository 的 id 标签也必须和 settings.xml 中指定的 mirror 标签的 id 属性一致。
#[2]执行部署命令
mvn deploy
1
Uploading to nexus-mine: http://192.168.198.100:8081/repository/maven-snapshots/com/atguigu/demo/demo07-redis-data-provider/1.0-SNAPSHOT/maven-metadata.xml
Uploaded to nexus-mine: http://192.168.198.100:8081/repository/maven-snapshots/com/atguigu/demo/demo07-redis-data-provider/1.0-SNAPSHOT/maven-metadata.xml (786 B at 19 kB/s)
Uploading to nexus-mine: http://192.168.198.100:8081/repository/maven-snapshots/com/atguigu/demo/demo07-redis-data-provider/maven-metadata.xml
Uploaded to nexus-mine: http://192.168.198.100:8081/repository/maven-snapshots/com/atguigu/demo/demo07-redis-data-provider/maven-metadata.xml (300 B at 6.5 kB/s)
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] demo-imperial-court-ms-show 1.0-SNAPSHOT ........... SUCCESS [ 1.875 s]
[INFO] demo09-base-entity ................................. SUCCESS [ 21.883 s]
[INFO] demo10-base-util ................................... SUCCESS [ 0.324 s]
[INFO] demo08-base-api .................................... SUCCESS [ 1.171 s]
[INFO] demo01-imperial-court-gateway ...................... SUCCESS [ 0.403 s]
[INFO] demo02-user-auth-center ............................ SUCCESS [ 2.932 s]
[INFO] demo03-emp-manager-center .......................... SUCCESS [ 0.312 s]
[INFO] demo04-memorials-manager-center .................... SUCCESS [ 0.362 s]
[INFO] demo05-working-manager-center ...................... SUCCESS [ 0.371 s]
[INFO] demo06-mysql-data-provider ......................... SUCCESS [ 6.779 s]
[INFO] demo07-redis-data-provider 1.0-SNAPSHOT ............ SUCCESS [ 0.273 s]
③引用别人部署的 jar 包
#[1]提出问题
- 默认访问的 Nexus 仓库:maven-public
- 存放别人部署 jar 包的仓库:maven-snapshots
#[2]配置 Maven 工程
<repositories>
<repository>
<id>nexus-mine</id>
<name>nexus-mine</name>
<url>http://192.168.198.100:8081/repository/maven-snapshots/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
#4、修改仓库配置
举例:修改 maven-central 仓库代理的远程库地址
第二节 jar包冲突问题
#1、谁需要面对 jar 包冲突?
先给结论:编订依赖列表的程序员。初次设定一组依赖,因为尚未经过验证,所以确实有可能存在各种问题,需要做有针对性的调整。那么谁来做这件事呢?我们最不希望看到的就是:团队中每个程序员都需要自己去找依赖,即使是做同一个项目,每个模块也各加各的依赖,没有统一管理。那前人踩过的坑,后人还要再踩一遍。而且大家用的依赖有很多细节都不一样,版本更是五花八门,这就让事情变得更加复杂。
所以虽然初期需要根据项目开发和实际运行情况对依赖配置不断调整,最终确定一个各方面都 OK 的版本。但是一旦确定下来,放在父工程中做依赖管理,各个子模块各取所需,这样基本上就能很好的避免问题的扩散。
即使开发中遇到了新问题,也可以回到源头检查、调整 dependencyManagement 配置的列表——而不是每个模块都要改。所以学完这一节你应该就会对前面讲过的『继承』有了更深的理解。
#2、表现形式
由于实际开发时我们往往都会整合使用很多大型框架,所以一个项目中哪怕只是一个模块也会涉及到大量 jar 包。数以百计的 jar 包要彼此协调、精密配合才能保证程序正常运行。而规模如此庞大的 jar 包组合在一起难免会有磕磕碰碰。最关键的是由于 jar 包冲突所导致的问题非常诡异,这里我们只能罗列较为典型的问题,而没法保证穷举。
但是我们仍然能够指出一点:一般来说,由于我们自己编写代码、配置文件写错所导致的问题通常能够在异常信息中看到我们自己类的全类名或配置文件的所在路径。如果整个错误信息中完全没有我们负责的部分,全部是框架、第三方工具包里面的类报错,这往往就是 jar 包的问题所引起的。
而具体的表现形式中,主要体现为找不到类或找不到方法。
#①抛异常:找不到类
此时抛出的常见的异常类型:
- java.lang.ClassNotFoundException:编译过程中找不到类
- java.lang.NoClassDefFoundError:运行过程中找不到类
- java.lang.LinkageError:不同类加载器分别加载的多个类有相同的全限定名
我们来举个例子:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.x.x</version>
</dependency>
httpclient 这个 jar 包中有一个类:org.apache.http.conn.ssl.NoopHostnameVerifier。这个类在较低版本中没有,但在较高版本存在。比如:
jar 包版本 | 是否存在 |
---|---|
4.3.6 | 否 |
4.4 | 是 |
那当我们确实需要用到 NoopHostnameVerifier 这个类,我们看到 Maven 通过依赖传递机制引入了这个 jar 包,所以没有明确地显式声明对这个 jar 包的依赖。可是 Maven 传递过来的 jar 包是 4.3.6 版本,里面没有包含我们需要的类,就会抛出异常。
而『冲突』体现在:4.3.6 和 4.4 这两个版本的 jar 包都被框架所依赖的 jar 包给传递进来了,但是假设 Maven 根据『版本仲裁』规则实际采纳的是 4.3.6。
#②抛异常:找不到方法
程序找不到符合预期的方法。这种情况多见于通过反射调用方法,所以经常会导致:java.lang.NoSuchMethodError。比如 antlr:antlr:x.x.x 这个包中有一个接口:antlr.collections.AST
版本 | getLine()方法 |
---|---|
2.7.2 | 无 |
2.7.6 | 有 |
#③没报错但结果不对
发生这种情况比较典型的原因是:两个 jar 包中的类分别实现了同一个接口,这本来是很正常的。但是问题在于:由于没有注意命名规范,两个不同实现类恰巧是同一个名字。
具体例子是有的同学在实际工作中遇到过:项目中部分模块使用 log4j 打印日志;其它模块使用 logback,编译运行都不会冲突,但是会引起日志服务降级,让你的 log 配置文件失效。比如:你指定了 error 级别输出,但是冲突就会导致 info、debug 都在输出。
3、本质
以上表现形式归根到底是两种基本情况导致的:
#①同一jar包的不同版本
③不同jar包中包含同名类
这里我们拿 netty 来举个例子,netty 是一个类似 Tomcat 的 Servlet 容器。通常我们不会直接依赖它,所以基本上都是框架传递进来的。那么当我们用到的框架很多时,就会有不同的框架用不同的坐标导入 netty。大家可以参照下表对比一下两组坐标:
截止到3.2.10.Final版本以前的坐标形式: | 从3.3.0.Final版本开始以后的坐标形式: |
---|---|
但是偏偏这两个『不同的包』里面又有很多『全限定名相同』的类。例如:
org.jboss.netty.channel.socket.ServerSocketChannelConfig.class org.jboss.netty.channel.socket.nio.NioSocketChannelConfig.class org.jboss.netty.util.internal.jzlib.Deflate.class org.jboss.netty.handler.codec.serialization.ObjectDecoder.class org.jboss.netty.util.internal.ConcurrentHashMap$HashIterator.class org.jboss.netty.util.internal.jzlib.Tree.class org.jboss.netty.util.internal.ConcurrentIdentityWeakKeyHashMap$Segment.class org.jboss.netty.handler.logging.LoggingHandler.class org.jboss.netty.channel.ChannelHandlerLifeCycleException.class org.jboss.netty.util.internal.ConcurrentIdentityHashMap$ValueIterator.class org.jboss.netty.util.internal.ConcurrentIdentityWeakKeyHashMap$Values.class org.jboss.netty.util.internal.UnterminatableExecutor.class org.jboss.netty.handler.codec.compression.ZlibDecoder.class org.jboss.netty.handler.codec.rtsp.RtspHeaders$Values.class org.jboss.netty.handler.codec.replay.ReplayError.class org.jboss.netty.buffer.HeapChannelBufferFactory.class
……
其实还有很多,这里列出的只是冰山一角。
当然,如果全限定名相同,类中的代码也完全相同,那么用着也行。问题是如果『全限定名相同』,但是『代码不同』,那可太坑了。我们随便找一个来看看:
坐标信息:org.jboss.netty:netty:jar:3.2.10.Final
4、解决办法
#①概述
很多情况下常用框架之间的整合容易出现的冲突问题都有人总结过了,拿抛出的异常搜索一下基本上就可以直接找到对应的 jar 包。我们接下来要说的是通用方法。
不管具体使用的是什么工具,基本思路无非是这么两步:
- 第一步:把彼此冲突的 jar 包找到
- 第二步:在冲突的 jar 包中选定一个。具体做法无非是通过 exclusions 排除依赖,或是明确声明依赖。
#②IDEA 的 Maven Helper 插件
这个插件是 IDEA 中安装的插件,不是 Maven 插件。它能够给我们罗列出来同一个 jar 包的不同版本,以及它们的来源。但是对不同 jar 包中同名的类没有办法。
#③Maven 的 enforcer 插件
使用 Maven 的 enforcer 插件既可以检测同一个 jar 包的不同版本,又可以检测不同 jar 包中同名的类。
#[1]引入 netty 依赖
这里我们引入两个对 netty 的依赖,展示不同 jar 包中有同名类的情况。
<dependencies>
<dependency>
<groupId>org.jboss.netty</groupId>
<artifactId>netty</artifactId>
<version>3.2.10.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
<version>3.9.2.Final</version>
</dependency>
</dependencies>
[2]配置 enforcer 插件
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
<executions>
<execution>
<id>enforce-dependencies</id>
<phase>validate</phase>
<goals>
<goal>display-info</goal>
<goal>enforce</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.codehaus.mojo</groupId>
<artifactId>extra-enforcer-rules</artifactId>
<version>1.0-beta-4</version>
</dependency>
</dependencies>
<configuration>
<rules>
<banDuplicateClasses>
<findAllDuplicates>true</findAllDuplicates>
</banDuplicateClasses>
</rules>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
[3]测试
执行如下 Maven 命令:
mvn clean package enforcer:enforce
部分运行结果:
[INFO] --- maven-enforcer-plugin:1.4.1:enforce (default-cli) @ pro32-duplicate-class ---
[WARNING] Rule 0: org.apache.maven.plugins.enforcer.BanDuplicateClasses failed with message:
Duplicate classes found:Found in:
io.netty:netty:jar:3.9.2.Final:compile
org.jboss.netty:netty:jar:3.2.10.Final:compile
Duplicate classes:
org/jboss/netty/channel/socket/ServerSocketChannelConfig.class
org/jboss/netty/channel/socket/nio/NioSocketChannelConfig.class
org/jboss/netty/util/internal/jzlib/Deflate.class
org/jboss/netty/handler/codec/serialization/ObjectDecoder.class
org/jboss/netty/util/internal/ConcurrentHashMap$HashIterator.class……
TIP
最后,问你一个问题:解决 jar 包冲突问题这么麻烦,是不是不该用 Maven?
第三节 体系外 jar 包引入
#1、提出问题
『体系外 jar 包』这个名字是我起的,来源是这样——目前来说我们在 Maven 工程中用到的 jar 包都是通过 Maven 本身的机制导入进来的。
而实际开发中确实有可能用到一些 jar 包并非是用 Maven 的方式发布,那自然也没法通过 Maven 导入。
此时如果我们能够拿到该 jar 包的源码那还可以自己建一个 Maven 工程,自己打包。可是如果连源码都没有呢?
这方面的例子包括一些人脸识别用的 jar 包、海康视频监控 jar 包等等。
#2、解决办法
#①准备一个体系外 jar 包
我们通过学 Maven 以前的方式创建一个 Java 工程,然后导出 jar 包即可用来测试。
下面具体步骤看视频:
172-生产实践-体系外 jar 包导入_ev_哔哩哔哩_bilibili
下载课件:略