简述
java开发中可以使用maven来管理依赖,引入依赖,构建最终jar文件,当然其中也可能需要解决依赖冲突问题。
- 管理依赖:通过
<dependencyManagement>
,声明依赖版本,进行依赖的版本控制的。 - 引入依赖:通过
<dependency>
,进行依赖的实际引入。 - 构建jar包:在需要打包的模块中添加
<build>
并加入定制插件plugin
进行jar生成。 - 依赖冲突:通过工具或者命令行排查冲突的依赖后,使用
exclusion
来排出冲突的依赖。
注:
- 依赖冲突可以使用idea的
mavenhelper
插件来查看,简单直观,也可以命令行使用mvn dependency:tree -Dverbose > tree.txt
,在文件中检索conflict
关键字。
maven使用前,需要设置好setting.xml
配置文件,如镜像仓库。
- maven的打包命令
mvn clean package -Dmaven.test.skip=true
- 指定配置文件打包
mvn clean package -s setting.xml -Dmaven.test.skip=true
- maven的仲裁机制:路径最近者优先,路径相同第一声明者优先(路径距离是从打包模块的pom开始算,第一声明是pom中声明的前后顺序)
maven常用标签的使用
的基本使用
主要讲解的内容:依赖常用的两种引用方式,依赖的排除方式,依赖的作用域,<option>
标签。
- 仓库引入
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
复制代码
- 本地引入
- 注:需要在
<build>
中<plugin>
中需添加<includeSystemScope>true</includeSystemScope>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>kingbase8-hibernatedialect</artifactId>
<version>5.2.17.Finaldialect</version>
<scope>system</scope>
<systemPath>${project.basedir}/../../../lib/hibernate-5.2.17.Finaldialect.jar</systemPath>
</dependency>
复制代码
- 依赖排除
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
<!--需要排除的依赖项-->
<exclusions>
<!--排除的依赖无需声明版本号-->
<exclusion>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</exclusion>
</exclusions>
</dependency>
复制代码
- 依赖的作用域标签
<scope>
,其默认值为compile
。
<!-- Lombok -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<scope>provided</scope>
</dependency>
复制代码
拓展:<scope>import</scope>
只能在<dependencyManagement>
模块中使用,用于解决maven的单继承问题。
<option>
标签,固定值为true
<option>
表示这个依赖是需要选择是否引入的,如果需要引入则需要显示声明。 举例:下方代码块是B模块的pom文件,A项目将B项目作为依赖后,这些带<option>
的依赖并不会被引入,不会打进jar包,如果需要引入则显示的添加声明。
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
<optional>true</optional>
<dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>8.4-701.jdbc3</version>
<optional>true</optional>
<dependency>
</dependencies>
复制代码
- 拓展知识:
<classifier>
标签,提供一个新的维度定义jar,一般为不同jdk版本生成jar文件
maven-resp (maven仓库)
└── org
└── apache
└── maven
├── maven-artifacr-3.8.1-calss1.jar (jdk8生成一个jar)
└── maven-artifacr-3.8.1-calss2.jar (jdk21生成一个jar)
在pom文件中区分环境的中添加<classifier>calss1</classifier>或<classifier>calss1</classifier>则不同环境生成不同jar文件
样例:
<properties>
<classifier>default</classifier>
</properties>
<profiles>
<profile>
<id>jdk8</id>
<properties>
<classifier>calss1</classifier>
</properties>
</profile>
<profile>
<id>jdk21</id>
<properties>
<classifier>calss2</classifier>
</properties>
</profile>
</profiles>
<bulid>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>default-jar</id>
<goals>
<goal>jar<goal>
</goals>
<configuration>
<classifier>${classifier}</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
复制代码
项目A,依赖B项目,并且需要mysql驱动
<dependencies>
<dependency>
<groupId>project-b</groupId>
<artifactId>project-b</artifactId>
<version>b-version</version>
<dependency>
<!--因为B模块中mysql驱动是可选的,所以A模块需要mysql驱动得显示引入-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
<dependency>
</dependencies>
复制代码
的基本使用
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<!--1.在原始Maven打包形成的jar包基础上,进行重新打包,
新形成的jar包不但包含应用类文件和配置文件,
而且还会包含应用所依赖的jar包以及Springboot启动相关类(loader等),
以此来满足Springboot独立应用的特性;
2.将原始Maven打包的jar重命名为XXX.jar.original作为原始文件;-->
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<!--将本地jar包打入构建生成的jar文件中-->
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
复制代码
微服务的目录结构
以现在广泛流行的springboot多个微服务来作为介绍模板,一个微服务可以作为一个单体项目看待。
├── common (公共模块)
│ └── pom.xml
├── services(微服务模块)
│ ├── common(微服务之间的公共模块)
│ │ └── pom.xml
│ ├── service1(某微服务1的聚合模块)
│ │ ├──api(提供调用接口的模块)
│ | | └── pom.xml
│ | ├──specific-service(实现具体功能的模块)
│ | | └── pom.xml
│ │ └── pom.xml
│ ├── service2(某微服务2的聚合模块)
│ │ ├──api(提供调用接口的模块)
│ | | └── pom.xml
│ | ├──specific-service(实现具体功能的模块)
│ | | └── pom.xml
│ │ └── pom.xml
│ └── pom.xml
├── settings.xml (maven的配置文件)
└── pom.xml
复制代码
结构划分:聚合模块,依赖模块,实现模块
聚合模块
作用:聚合用于快速构建maven工程,一次性构建多个项目/模块。 常用标签
<packaging>
:这是聚合模块必须要有的标签内容为pom
,表示这是一个聚合模块(没有代码),用来管理多个模块。<module>
: 标签用于指定管理的那些模块,不记录模块管理的模块(浅引用)。<dependencyManagement>
:声明依赖版本,子模块引入依赖无需写入版本号,统一管理依赖版本。<pluginManagement>
,声明插件,管理插件配置,子项目直接继承,无需重复编写配置规则。<properties>
声明变量,用于对变量的管理,如版本号,通过${}
取值。
依赖模块和实现模块
如api和common只提供工具和功能支持的模块称其依赖模块,而实现模块则是一个用于打包部署运行的具体微服务模块。 依赖模块常用标签
<dependency>
:引入所需依赖packaging
: 需要把代码打包,一般为jar
微服务模块常用标签
<dependency>
:引入所需依赖packaging
: 需要把代码打包,一般为jar(放在web容器则为war)<build>
:对于一个springboot项目,具体微服务模块需要通过build
指定一个构建方式,如指定springboot-maven-plugin
来进行构建。
JVM加载类
JVM会根据顺序加载class文件,如果全限定名重复,后面的class将会被忽略。 如果存在同类名的class可以考虑:
- 移除重复的class
- 写类加载器来加载特定的class
- 改变classpath里的顺序
springboot生成jar结构目录
阿里的一些开发规范
【强制】禁止在子项目的 pom 依赖中出现相同的 GroupId,相同的 ArtifactId,但是不同的 Version。
说明:在本地调试时会使用各子项目指定的版本号,但是合并成一个 war,只能有一个版本号出现在最后的 lib 目录 中。曾经出现过线下调试是正确的,发布到线上却出故障的先例。
【推荐】所有 pom 文件中的依赖声明放在语句块中,所有版本仲裁放在 语句块中。 说明:里只是声明版本,并不实现引入,因此子项目需要显式的声明依赖,version 和 scope 都读取自父 pom。而所有声明在主 pom 的里的依赖都会自动引入,并默 认被所有的子项目继承。 【强制】二方库的新增或升级,保持除功能点之外的其它 jar 包仲裁结果不变。如果有改变,必须明确评 估和验证。 说明:在升级时,进行 dependency:resolve 前后信息比对,如果仲裁结果完全不一致,那么通过 dependency:tree 命 令,找出差异点,进行排除 jar 包。 **【强制】依赖于一个二方库群时,必须定义一个统一的版本变量,避免版本号不一致。 ** 说明:依赖 springframework-core,-context,-beans,它们都是同一个版本,可以定义一个变量来保存版本: ${spring.version},定义依赖的时候,引用该版本。
开发中遇见的问题
某些包没有打入生成jar中
- 在idea开发完成后,生成镜像上云中发现微服务j启动ar报错,找不某个类,查看jar包发现某个依赖未打入jar包,jar为
spring-boot-configuration-processor
,检索引入地方,发现其引入作用域<scope>option<scope>
,将其注释后,查看idea侧边maven也确实通过common引入,打包后依然未打入jar包,将此依赖直接引入微服务打包后依赖引入成功,但各个微服务都有引入,需要都复制一份有些麻烦,后将其从父工程根pom引入,打包后依赖引入成功。只有注释掉作用域也不生效,其原因未知。 - 另一次就是pom引入本地jar包,但打包未添加
<includeSystemScope>true</includeSystemScope>
导致打包未打入jar。
jar包冲突
mybatis-plus-boot-starter
中引入了jsqlparser
依赖,但是mavenhelper未检测到,idea每次编译都会产生一个低版本的jsqlparser
,由于开始不清楚低版本的来处,导致浪费了很多精力。jsqlparser
与pagehelper
版本不匹配导致,方法不存在报错,最终通过查询到一个匹配版本解决。
某些二次封装的包与以前包全限定类名一致并且接口内容不一致产生错误
- 如对一些常用依赖进行了二次封装,但是接口方法有差别,导致编译时出现错误。
莫名其妙的循环依赖问题:添加了依赖或者改了一点无关的代码,循环依赖报错就会出现,按道理低版本springboot可以通过三级缓存来解决循环依赖,但是并不生效。
抱着疑问,我找到了程序员导师:Google来求助,最终兜兜转转找到了github里spring-framework的一个issue,提的就是这个问题: github.com/spring-proj… 可以看到这个issue从2016年首次被提出,到2019年reopen,实际上一直都没有找到过原因。issue里好几个人遇到了和我一样的问题:一样的代码,在不同的环境上编译,出来的jar包有的能运行,有的却报错。 spring的维护人员可能是觉得循环依赖不应当在程序中出现,甚至目前springboot2.6版本已经完全不允许循环依赖了,所以对这个issue也就没有动力去解决。