Maven:Maven_02:依赖管理与冲突解决及项目继承聚合

Posted ABin-阿斌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Maven:Maven_02:依赖管理与冲突解决及项目继承聚合相关的知识,希望对你有一定的参考价值。

我是 ABin-阿斌:写一生代码,创一世佳话,筑一揽芳华。 如果小伙伴们觉得我的文章有点 feel ,那就点个赞再走哦。

Maven的依赖范围

maven的依赖范围包括:

  • compile: 编译范围的依赖会用在编译,测试,运行,由于运行时需要,所以编译范围的依赖会被打包(会被打包)
  • test: test范围依赖在编译和运行时都不需要,只在测试编译和测试运行时需要。
    • 例如: Junit,由于运行时不需要,所以 test 范围依赖不会被打包(不会打包)
  • provide: provide 依赖只有当 jdk 或者一个容器已提供该依赖之后才使用。provide 依赖在编译和测试时需要,在运行时不需要。
    • 例如: servletapiTomcat 容器提供了(不会打包)
  • runtime: runtime 依赖在运行和测试系统时需要,但在编译时不需要。
    • 例如: jdbc 的驱动包。由于运行时需要,所以 runtime 范围的依赖会被打包(会打包)
  • system: 一般不推荐使用: system 范围依赖与 provide 类似。但是必须显示的提供一个对于本地系统中 jar 文件的路径。

具体细节对比:

  • 代码展示:

		<!--导入mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
			<!--runtime表示编译器不使用它,运行期使用它-->
			<scope>runtime</scope>
            <version>8.0.17</version>
        </dependency>
        <!--导入servlet相关依赖,request不报错-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
			<!--provided的意思是编译时使用它,运行时不使用-->
            <scope>provided</scope>
        </dependency>
		<--jsp-->	
		<dependency>
		  <groupId>javax.servlet.jsp</groupId>
		  <artifactId>jsp-api</artifactId>
		  <version>2.0</version>
		  <scope>provided</scope>
		</dependency>
		<!--system 编译测试有用、不会运行打成jar-->
		<dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <scope>system</scope>
            <optional>true</optional>
            <version>${java.version}</version>
            <systemPath>${java.home}/../lib/tools.jar</systemPath>
        </dependency>

Maven的常用设置

全局变量:

  • Mavenpom.xml 文件中,properties 用于定义全局变量。POM 中通过 ${property_name} 的形式引用变量的值,定义全局变量。
	<properties>
		<spring.version>4.3.10.RELEASE</spring.version>
	</properties>

引用全局变量:

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${spring.version}</version>
	</dependency>

Maven系统采用的变量

<properties>
	<!--源码编译jdk版本-->		
	<maven.compiler.source>1.8</maven.compiler.source>
	<!--运行代码的jdk版本-->
	<maven.compiler.target>1.8</maven.compiler.target>
	<!--项目构建使用的编码,避免中文乱码-->
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<!--生成报告的编码-->
	<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

指定资源位置

  • src/main/java 和 src/test/java 这两个目录中的所有 *.java 文件会分别在 comile和test-comiple 阶段被编译,编译结果分别放到了 target/classes 和 targe/test-classes 目录中。
  • 但是,这两个目录中的其他文件都会被忽略掉。如果,需要把 src 目录下的文件包放到 target/classes 目录,作为输出的 jar 一部分,需要指定资源文件位置,以下内容放到 buid 标签中。

IDEA 默认情况下 在 classes 中没有 A.txt 文件出现

问题: 需求一:想要 A.txt 出现在 classes 中

  • 在 pom.xml 添加如下内容,然后重新打包即可
	<build>
	     <resources>
	         <resource>
	             <!--所在的目录-->
	             <directory>src/main/java</directory>
	             <includes>
	                 <!--包括目录下的.properties,.xml 文件都会扫描到-->
	                 <include>**/*.properties</include>
	                 <include>**/*.xml</include>
	                 <include>**/*.txt</include>
	             </includes>
	             <!--filtering 选项 false 不启用过滤器, *.property 已经起到过滤的作用了 -->
	             <filtering>false</filtering>
	         </resource>
	     </resources>
	 </build>

需求二: 在xml中配置如下配置

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.MF</include>
                    <include>**/*.XML</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <!--资源文件的路径,默认位于${basedir}/src/main/resources/目录下-->
                <directory>src/main/resources</directory>
                <!--一组文件名的匹配模式,被匹配的资源文件将被构建过程处理-->
                <includes>
                    <include>**/*</include>
                    <include>*</include>
                </includes>
                <!--filtering默认false,true表示通过参数对资源文件中的${key}
                在编译时进行动态变更。替换源可紧-Dkey和pom中的<properties>值
                或<filters>中指定的properties文件-->
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

Maven默认属性

	${basedir} 项目根目录  
	${version}表示项目版本;  
	${project.basedir}同${basedir};  
	${project.version}表示项目版本,与${version}相同;  
	${project.build.directory} 构建目录,缺省为target  
	${project.build.sourceEncoding}表示主源码的编码格式;  
	${project.build.sourceDirectory}表示主源码路径;  
	${project.build.finalName}表示输出文件名称;  
	${project.build.outputDirectory} 构建过程输出目录,缺省为target/classes 

    输出结果为:
	test=${pro.name}
	# F:\\\\mavenTest\\\\A 项目根目录
	basedir=${basedir}
	basedir2=${project.basedir}
	# version=1.0-SNAPSHOT
	version=${version}
	# project.build.directory=F:\\\\mavenTest\\\\A\\\\target 构建目录,缺省为target
	project.build.directory=${project.build.directory}
	# project.build.sourceDirectory=F:\\\\mavenTest\\\\A\\\\src\\\\main\\\\java
	# 表示主源码的编码格式
	project.build.sourceDirectory=${project.build.sourceDirectory}
	# project.build.finalName=A-1.0-SNAPSHOT 表示输出文件名称
	project.build.finalName=${project.build.finalName}
	# project.build.outputDirectory=F:\\\\mavenTest\\\\A\\\\target\\\\classes
	# 构建过程输出目录,缺省为target/classes
	project.build.outputDirectory=${project.build.outputDirectory}

Maven 项目依赖、依赖冲突

什么是依赖传递

  • maven 中,依赖是可以传递的。假设存在三个项目,分别是项目A、项目B以及项目C。假设C依赖B,B依赖A,那么我们可以根据 maven 项目依赖的特征不难推出项目 C 也依赖 A

  • 通过上面的图可以看到,我们的 web 项目直接依赖了spring-webmvc,而 spring-webmvc依赖了sping-aop、spring-beans等。

  • 最终的结果就是在我们的 web 项目中间接依赖了 spring-aop、spring-beans等

什么是依赖冲突

演示:加入如下坐标

  • 由于 spring-webmvc 中依赖了 spring-core,而 spring-core 中依赖了 commons-logging(1.1.3)。而我们又引入了 commons-loging1.2,就造成了冲突。

  • 根据​路径近者优先原则,我们项目中引入的 commons-logging 为1.2

如何解决依赖冲突

使用maven提供的依赖调解原则

  • 依赖调节原则一:
    • 第一声明优先原则(在 pom 文件中定义依赖,以先声明的依赖为准。其实就是根据坐标导入的顺序来确定最终使用哪个传递过来的依赖)
  • 总结: 通过上图可以看到,spring-aop 和 spring-webmvc 都传递过来了spring-beans,但是因为 spring-aop 在前面,所以最终使用的 spring-beans 是由 spring-aop 传递过来的,而 spring-webmvc 传递过来的 spring-beans 则被忽略了。
  • 依赖调节原则二:
    • 根据​路径近者优先原则 我们项目中引入的 commons-logging为1.2

    • 如果在同一个 pom 中引入了两个相同的 jar 包,以引入的最后一个为准,如下的配置引入的是1.2

    <dependencies>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.2</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>

可选依赖optional

  • 新建项目A(类似于mysql)、B(类似于oracle),项目C(类似于业务层,引入A、B工程),项目D(类似于逻辑工程,引入了C)
  • C项目 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>mavenTest</artifactId>
        <groupId>com.xiaozhi</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>C</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.xiaozhi</groupId>
            <artifactId>A</artifactId>
            <version>1.0-SNAPSHOT</version>
            <!-- optional=true,依赖不会传递,该项目依赖A
                之后依赖该项目的项目如果想要使用A,需要重新引入 -->
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.xiaozhi</groupId>
            <artifactId>B</artifactId>
            <version>1.0-SNAPSHOT</version>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

详细分析:

  • 由于 projectC 使用到了两个来自 projectA 的类(OptionalFeatureAClass) 和 projectB 的类(OptionalFeatureBClass) 如果projectC 没有依赖 packageA 和 packageB,那么编译将会失败。
  • projectD 依赖 projectC,但是对于 projectD 来说,类(OptionalFeatureAClass)和类(OptionalFeatureBClass)是可选的特性,所以为了让最终的 war/ejbpackage 不包含不必要的依赖,使用 optional 声明当前依赖是可选的。默认情况下也不会被其他项目继承(好比 Java 中的 final 类,不能被其他类继承一样)
    如果 projectD 确实需要用到 projectC 中的 OptionalFeatureAClass 怎么办呢?
    • 那我们就需要在 projectDpom.xml 中显式的添加声明 projectA 依赖。继续看下图 ProjectD 需要用到 ProjectA 的OptionalFeatureAClass,那么需要在 ProjectDpom.xml 文件中显式的添加对 ProjectA 的依赖。

总结:

  • 到这也就很好理解为什么 Maven 为什么要设计 optional 关键字了
  • 假设一个关于数据库持久化的项目 (ProjectC) 为了适配更多类型的数据库持久化设计,比如 Mysql 持久化设计(ProjectA)和 Oracle 持久化设计(ProjectB)。
  • 当我们的项目(ProjectD)要用的 ProjectC 的持久化设计,不可能既引入 l驱动又引入 oracle 驱动吧,所以我们要显式的指定一个,就是这个道理了。

Maven如何排除依赖

可以使用exclusions标签将传递过来的依赖排除出去

版本锁定

  • 采用直接锁定版本的方法确定依赖jar包的版本,版本锁定后则不考虑依赖的声明顺序或依赖的路径,以锁定的版本为准添加到工程中,此方法在企业开发中经常使用。

如何使用版本锁定方式:

  • dependencyManagement 标签中锁定依赖的版本

  • dependencies 标签中声明需要导入的 maven 坐标

分模块构建maven工程

  • 不管是下面那种拆分方式通常都会提供一个父工程,将一些公共的代码和配置提取到父工程中进行统一管理和配置。

  • 按照业务模块进行拆分,每个模块拆分成一个 maven 工程。例如: 将一个项目分为用户模块、订单模块、购物车模块等,每个模块都对应一个 maven 工程。

  • 按照层进行拆分,例如持久层、业务层、表现层等,每个层对应就是一个 maven 工程。

Maven工程的继承

  • 继承的目的是为了消除重复代码,在 Java 语言中,类之间是可以继承的,通过继承,子类就可以引用父类中非 private 的属性和方法。同样,在 maven 工程之间也可以继承,子工程继承父工程后,就可以使用在父工程中引入的依赖。

  • 被继承的 maven 工程通常被称为父工程, 父工程的打包方式必须为pom,所以我们区分某个 maven 工程是否为父工程就要看这个工程的打包方式是否为 pom

继承其他 maven 父工程的通常称为子工程,在 pom.xml 文件中通过 parent 标签进行父工程的继承

Maven工程的聚合

  • maven 工程的 pom.xml 文件中可以使用 <modules> 标签将其他maven工程聚合到一起,聚合的目的是为了进行统一操作

  • 例如: 拆分后的 maven 工程有多个,如果要进行打包,就需要针对每个工程分别执行打包命令,操作起来非常繁琐。

  • 这时就可以使用 modules 标签将这些工程统一聚合到 maven 工程中,需要打包的时候,只需要在此工程中执行一次打包命令,其下被聚合的工程就都会被打包了 。

以上是关于Maven:Maven_02:依赖管理与冲突解决及项目继承聚合的主要内容,如果未能解决你的问题,请参考以下文章

如何快速的解决Maven依赖冲突

如何快速的解决Maven依赖冲突

初识Maven与nexus,及nexus安装

Maven01_05_compile和test依赖范围Maven的一些依赖情况依赖冲突如果出现冲突就会采取就近原则可选依赖(optional)排除依赖(exclusions)

Maven依赖版本冲突的分析及解决小结

使用Maven Helper解决Maven依赖冲突