四、依赖冲突调解
4.1 最短路径优先原则
依赖冲突产生的原因——依赖传递
假设你的项目依赖jar包A,jar包A又依赖jar包B。当添加jar包A时,Maven会把jar包B也自动加入到项目中。比如刚刚我们添加了junit依赖,junit又依赖hamcrest,所以Maven会将junit和hamcrest都加入项目中。
这时就可能会产生依赖冲突问题,比如依赖A会引入依赖C,依赖B也会引入依赖C。如果不进行调解则会引入两个依赖C,那么Maven是如何解决依赖冲突问题的呢?
我们以Spring依赖为例,spring-webmvc依赖spring-aop,spring-context也依赖spring-aop,如果两个同时引入,会引入哪个版本的spring-aop呢?
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.2.4.RELEASE</version> </dependency>
为啥导入的依赖是5.2.12不是4.2.4呢?这是因为jar包有自己的添加规则。
Maven调解依赖冲突的第一原则是最短路径优先原则:
也就是说项目依赖关系树中路径最短的版本会被使用。例如,假设有几个jar包之间的依赖关系是:A->B->C->D(2.0)
和E->F->D(1.0)
,如果同时引入A和E,那么D(1.0)会被使用,因为E到D的路径更短。
spring-context到spring-aop的路径如下:
spring-webmvc到spring-aop的路径如下:
可以看到,spring-webmvc到spring-aop的路径为:
spring-webmvc
->spring-context
->spring-aop
而spring-context到spring-aop的路径为:
spring-context
->spring-aop
spring-context到spring-aop的路径更短,所以spring-aop会按照spring-context的版本引入。
4.2 最先声明原则
最短路径优先原则不能解决所有问题,比如这样的依赖关系:A–>B–>C(1.0)
和D–>E–>C(2.0)
,同时引入A和D之后,C(1.0)和C(2.0)的依赖路径长度都为2。此时第一原则将不能解决问题
Maven调解依赖冲突的第二原则是最先声明原则:
在依赖路径长度相等的前提下,在POM中依赖声明的顺序靠前的会被解析使用。比如:以上案例中,spring-webmvc和spring-context到spring-core的路径都为1。谁声明在上方,spring-core会按照谁的版本引入。
4.3 排除依赖、锁定版本
如果不想使用Maven默认的冲突调解方式,有两种方式可以手动进行冲突调解。
排除依赖
比如以上案例中,想使用spring-webmvc的spring-aop包,那么可以让spring-context引入时排除引入spring-aop包,这样就可以使用spring-webmvc的spring-aop包了,写法如下:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.12.RELEASE</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> </exclusion> </exclusions> </dependency>
锁定版本
在Maven中为某个jar包配置锁定版本后,不考虑依赖的声明顺序和依赖路径,以锁定的版本的为准添加到工程中,此方法在企业开发中常用。以下可以直接配置spring-aop锁定的版本。
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.2.4.RELEASE</version> </dependency> </dependencies> </dependencyManagement>
五、Maven聚合开发
之前我们在Idea中开发时会将项目的所有包放在同一个工程当中。
- domain(pojo):定义实体类
- dao(mapper):操作数据库
- service:具体的业务逻辑,需要调用dao的方法。
- controller:前后端交互,需要调用service的方法。
- webapp:存放前端资源
假如我们现在写了两个项目分别是电商卖家端和电商买家端,两个项目都需要调用serive层查询订单的方法。原来的写法如下:
重复编码造成开发效率降低。
而使用maven后,我们可以把之前的项目按需拆分成一个个小项目,之后将小项目发布到仓库中,小项目之间也可以互相引用,并且在我们的项目中引入需要的小项目即可。
Maven将一个大项目分成一个个小项目开发,最后打包时会将这些小的项目打成一个完整的war包独立运行。
5.1 继承关系
Maven中的继承是针对于父工程和子工程。父工程定义的依赖和插件子工程可以直接使用。注意父工程类型一定为POM类型工程。父工程的作用是整合子工程,不实现具体的业务。
多继承(了解,很少用)
在Maven中对于继承采用的也是单继承,也就是说一个子项目只能有一个父项目。但我们可以在<dependencyManagement>
配置多继承。写法如下:
<dependencyManagement> <dependencies> <!--父项目a--> <dependency> <groupId>com.itbaizhan</groupId> <artifactId>parent_a</artifactId> <version>1.0-SNAPSHOT</version*> <type>pom</type> <!-- 引入父项目,scope的值为import --> <scope>import</scope> </dependency> <!--父项目b--> <dependency> <groupId>com.itbaizhan</groupId> <artifactId>parent_b</artifactId> <version>1.0-SNAPSHOT</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
5.2 搭建父工程
1、创建一个Maven工程,创建时不需要选择模板。
2、由于父工程是虚拟工程,不需要写逻辑代码,所以删除父工程的src目录,注意设置父工程的打包方式为pom。
3、由于父工程的依赖和插件子工程都能继承,可以将需要的依赖和插件都配置在父工程中。
<?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.zj</groupId> <artifactId>maven2</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <dependencies> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--注意使用范围是在编译时使用--> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <!-- jdbc驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> <!-- jstl --> <dependency> <groupId>org.apache.taglibs</groupId> <artifactId>taglibs-standard-spec</artifactId> <version>1.2.5</version> </dependency> <dependency> <groupId>org.apache.taglibs</groupId> <artifactId>taglibs-standard-impl</artifactId> <version>1.2.5</version> </dependency> </dependencies> <build> <plugins> <!-- tomcat插件 --> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.1</version> <configuration> <port>8080</port> <path>/</path> <uriEncoding>UTF-8</uriEncoding> <server>tomcat7</server> <systemProperties> <java.util.logging.SimpleFormatter.format>%1$tH:%1$tM:%1$tS %2$s%n%4$s: %5$s%6$s%n</java.util.logging.SimpleFormatter.format> </systemProperties> </configuration> </plugin> </plugins> </build> </project>
IDEA中工程和模块有什么区别?
没什么区别,只是新工程会占据一个新窗口,我们一般写父工程时定义为新工程,写子工程时定义为新模块。