大多数java应用源码构建和依赖管理是使用maven来实现的,maven也是java构建和依赖管理的事实上的标准。我们的应用系统也都是基于maven构建的,maven虽然在依赖管理方面确实很牛叉,但是并不能很优雅地解决所有依赖的问题,比如此次谈及的“全局排除”功能。
之前包括现在都在经历这样的事情,想禁止一个依赖被依赖进来,如果这个依赖属于冷门的依赖,很少类库会间接依赖它,那么进行一次排除完全OK,但是如果一个依赖是热门依赖,比如常用的apache的commons系列工具库,单独排除也可以实现,只是比较啰嗦,而且以后引入新的依赖就要时刻关心是否会带来不被允许的依赖,对维护人员来说简直是灾难。
首先谈下为什么有些依赖是一定不能允许的。在日志详解的博文中已经提到了依赖的一个典型特性——互斥,就是说有的依赖之间是不能共存的,比如提到过的slf4j-log4j和logback,guava和google-collection等等,一旦应用选择了使用logback就不能再引入slf4j-log4j依赖,原因可以看日志详解,而guava和google-collection会存在jar冲突,这样的例子还有很多。所以对于经常碰到这种冲突的开发人员来说,强烈希望改善这种局面。
想象下这样一个场景:你的应用不能依赖slf4j-log4j,别的开发不清楚,引入了其他类库,间接引入了这个依赖,之后应用除了问题,你负责去排查,看到了这个问题,排除了slf4j-log4j,收工。那下次出现了,你再去排除一次,那下下次,嗯哼......对了,我们需要全局声明下,这个依赖不能进来,好想法,只是......可惜......maven目前还不支持,虽然承诺未来会支持(最新的3.1.0依然未提供)。那能不能让我做了一次排除之后可以有个地方记录下确实不能有这个依赖进来,如果出现的话,构建神马的操作就报错提示。嗯,好想法,maven插件可以这个,写个吧。好在maven提供了相应的插件——maven-enforcer-plugin,去尝试帮助开发人员解决这个问题,其中一项比较有用的功能是bannedDependencies,可以设置依赖黑白名单,如果有依赖匹配了黑名单中的依赖设置,那么maven会停止(可以配置)当前操作(打包构建,甚至是mvn eclipse:eclipse),打印错误日志提示,配置的样式如下:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <executions> <execution> <id>enforce-versions</id> <goals> <goal>enforce</goal> </goals> <configuration> <rules> <requireMavenVersion> <version>2.1.0</version> </requireMavenVersion> <requireJavaVersion> <version>1.6</version> </requireJavaVersion> </rules> </configuration> </execution> <execution> <id>enforce-banned-dependencies</id> <goals> <goal>enforce</goal> </goals> <configuration> <rules> <bannedDependencies> <excludes> <exclude>junit:junit</exclude> <exclude>org.testng:testng</exclude> <exclude>com.google.collections:google-collections</exclude> <exclude>commons-logging:commons-logging</exclude> </excludes> <includes> <include>junit:junit:4.8.2:jar:test</include> <include>cglib:cglib-nodep:jar:2.2</include> </includes> </bannedDependencies> </rules> <fail>true</fail> </configuration> </execution> </executions> </plugin>
从这个插件配置上可以看出,还可以限制java版本以及maven版本。重点看看对依赖黑白名单的生命,黑名单中规定不能引入commons-logging,那么一旦依赖了这个,操作会提示,至于是不是会停止操作,取决于下边fail标签中的配置。比较特殊的是黑名单中排除了junit,而白名单中更加详细地描述junit,这个可以这么解读:不允许依赖junit,除了版本是4.8.2的scope为test的junit,从此可以看出,白名单是对黑名单的补充,这样更加灵活。当我看到这个插件的时候,它还在襁褓中(beta版),但是强烈吸引到了我的注意,这娃必成大器。使用这个,就可以只排除一次冲突,并记录到黑白名单,下次被破坏的时候,自然会提示信息,这样算是把经验总结下来,一次辛苦,万世留名,我们系统到现在还在使用这个,利器。
到此,看起来我们解决这个全局排除的难题,真的吗?仔细想想,这个也只是一个防御的方式,正像之前说的一样,一个热门的依赖会经常被间接依赖进来,那是不是会经常就构建失败了,有木有一种方式更加彻底(让不允许被依赖的jar直接进不来)呢?当然,方法是有的。不过在将这个之前,还是得穿插一下maven依赖仲裁的原则:maven在解析依赖的时候,有两个原则,第一原则是路径最短有限原则,例如A-->B-->C-1.0(A依赖B,B依赖C的1.0版本),同时A-->D-->E-->C-2.0,那么从A来看,最终会依赖C的1.0版本进来,因为路径最短,最可信,这个例子也推翻了“高版本覆盖低版本”的错误言论。第二原则是优先声明原则(pom中的声明顺序),这是对第一原则的补充,就是路径长度相同(第一原则好无力)的情况下,第二原则开始决策微调,不过这个原则是在maven2.1.0才加入的,之前的版本如果第一原则无力的情况下,就是不可调控的,所以码农门升级吧。原则清楚之后,我们就拿阐述第一原则的例子开始,加入A就是你的应用,C就是不允许被依赖进来的一个依赖,咋办?排除&加入黑名单。嗯,很好。那如果我根据第一原则,在A的pom中直接声明C呢?啥?你疯啦?不允许依赖,你还直接声明之?嗯,我确实不想活了,看下声明
<dependency> <groupId>C</groupId> <artifactId>C</artifactId> <version>2.0</version> </dependency>
这样是不是意味着,间接依赖的C都不顶用了,但是直接依赖了C,还是违背了不能依赖C的大前提,好,继续看下,把依赖声明改下
<dependency> <groupId>C</groupId> <artifactId>C</artifactId> <version>2.0</version> <scope>provided</scope> </dependency>
啊哈,这样C就不会打进最终的war包啦,也就间接起到了“全局排除”的目的。但是这样会给人造成迷惑,会什么是2.0,其实这个版本号已经没有任何含义了。但是问题还是在的,虽然声明了provided,但是编译时,这个jar依然还会出现在classpath下的,那代码依然开始可以引用的。那就再进一步,弄一个C的空壳,里边啥都没有,比如可以自己新建一个空的maven工程,C:C:empty_version,然后上传到私服(一般公司都会有自己的私服的)上,引用的地方改成把版本改成empty_version。这样对于A来说,因为是直接依赖C的empty_version版本,那么间接依赖全部自动被仲裁掉了,以后也不用担心别人引入依赖间接导致问题了。这样的做法并不完美,毕竟需要在私服上上传一个“垃圾”依赖,应用代码还需要直接依赖这个“垃圾”依赖。
综合黑白名单的方式和最后一种方式,总结下,如何结合使用。对于不那么热门的依赖,建议走黑白名单,毕竟很少出现,出现一次,手工排除下很简单。对于热门的依赖,也分下情况,像guava&google-collections这种情况,因为前者的代码是后者的超集,所以当依赖了guava的情况下,可以直接声明google-collections为provided,不需要上传一个空包,但是假如guava删掉了一些api,即它不再是超集的时候,就会出现问题。像logback&slf4j-log4j&slf4j-jdk14这种依赖,没有谁是谁的子集的说法,所以建议直接上传空包排除即可。当然这些原则还是需要开发者自己去权衡的。一定不是“一刀切”的原则,一刀切固然可以很好执行,但是往往不是最优的,两者或者多者结合,取长补短才是合理的,但是也会带来迷惑,因为有了选择。不过可以参考上边的总结,我们的应用也是两者结合的方式,选择的原则也是按照上边描述的,当然,最终的选择权还是交给你,enjoy it。
相关推荐
对于包含多个模块的Maven聚合项目,`Maven Helper`可以一次性分析所有模块的依赖,提供全局视图,便于整体把控项目结构。 ### 6. **运行配置** 此外,`Maven Helper`还支持自定义Maven运行配置。你可以直接在IDEA...
- excludes和optional元素:用于排除不需要的依赖或标记可选依赖。 3. Maven的构建过程: - 编译:mvn compile命令将源代码编译为字节码,存放在target/classes目录下。 - 测试:mvn test运行单元测试,生成测试...
1. **Maven依赖管理** Maven的`dependency`标签用于在`pom.xml`文件中声明项目所依赖的外部库。这些依赖可以是JAR文件,也可以是其他Maven项目。依赖管理包括指定依赖的`groupId`(组织ID)、`artifactId`(项目ID...
settings.xml用于配置用户和全局的Maven属性,如仓库位置;pom.xml则是项目的聚合和构建描述文件。 **3. 支持更多Plugin语言** Maven2扩展了Plugin语言支持,包括Java、BeanShell和Ant,这增强了插件的灵活性和可...
7. **Maven依赖管理**:通过声明依赖,Maven自动解决库的版本冲突,并下载所需的库到本地仓库,供项目使用。 8. **Maven仓库**:Maven使用仓库系统(如local repository, remote repository, and central ...
7. **排除依赖**:在依赖中使用`exclusions`标签可以排除特定的子依赖。 8. **多模块项目**:如果你的项目由多个模块组成,可以在每个模块的POM中指定父POM,实现共享配置。 9. **profiles**:配置不同的环境(如...
在实际开发中,有时我们可能需要排除或锁定某些依赖的版本,这时可以通过在POM文件中指定具体的版本号来实现。此外,也可以通过设置`dependencyManagement`标签来统一管理整个项目的依赖版本,确保所有模块的一致性...
在Maven 3.5.4中,依赖解析算法得到优化,能更准确地处理依赖树和排除规则。 3. **插件系统**: Maven的插件系统允许开发者扩展其功能,如编译、测试、打包、部署等。在`lib`目录下,你会找到这些插件的JAR文件,...
本文将深入解析Maven配置的核心要素,包括`settings.xml`文件的详细解读、代理设置的全面指导、依赖排除的实用技巧以及Maven命令行参数的应用示例,帮助读者掌握Maven配置的精髓。 ### Maven配置之`settings.xml` ...
- 当出现依赖冲突时,可以通过 `<dependencyManagement>` 标签进行统一管理,或者使用 `<exclusions>` 标签排除不需要的子依赖。 8. Maven的命令行工具: - `mvn clean` 清理项目。 - `mvn compile` 编译源代码...
3. **排除依赖 (Exclusions)**: 在POM中可以排除某个依赖的子依赖,避免版本冲突。 ### Maven与Eclipse集成 1. **Maven for Eclipse插件**: 安装M2Eclipse插件,使Eclipse支持Maven项目。 2. **导入Maven项目**: ...
Maven的`dependencyManagement`部分可以用来全局控制依赖版本,避免冲突。 3. **缓存管理**: Maven有一个本地仓库(默认在用户主目录的`.m2\repository`下),用于存储已下载的JARs。当再次构建项目时,Maven会...
5. **排除依赖**:在依赖中使用`exclusions`标签排除不需要的子依赖。 **五、Maven实战应用** 在实际开发中,Maven不仅用于简单的Java项目,还可以应用于Spring Boot、Spring Cloud等框架的构建。配合持续集成工具...
- **settings.xml**:这是Maven的个人配置文件,可以在这里定义本地仓库路径、远程仓库镜像、全局的依赖排除等。 - 本地仓库:默认为`.m2\repository`,用于存储下载的依赖库。 - 镜像设置:如果你在中国,可能...
通过 Maven 可以轻松地引入这些框架的依赖,并通过配置实现不同组件之间的集成。 #### Maven 工程目录结构 Maven 工程遵循固定的目录结构,便于构建和管理: 1. **src/main/java**:存放主应用程序代码。 2. **...
6. **注意排除jar中的依赖**:如果下载的jar包有额外的依赖,你可以在`<dependency>`标签内添加`<exclusions>`标签来排除不必要的依赖。 7. **配置Maven的远程仓库**:如果Maven中央仓库没有你需要的jar,你可以在`...
- **前提条件**:Maven依赖于Java运行环境,因此首先需要安装JDK并配置JAVA_HOME环境变量。 - **安装步骤**:下载并安装JDK,确保版本满足Maven的需求。 - **配置JAVA_HOME**:设置JAVA_HOME环境变量指向JDK安装...
3. **排除依赖中的特定模块**:通过exclusions元素排除不需要的子模块。 4. **管理依赖范围**:如compile、test、runtime等,控制依赖的生效范围。 总的来说,Maven通过其强大的依赖管理和自动化构建功能,极大地...
在POM中声明依赖后,Maven会自动解决依赖关系,并按特定的依赖管理策略(如范围、排除规则等)处理依赖冲突。 3. **插件系统**:Maven拥有丰富的插件系统,这些插件可以执行各种任务,如编译、测试、打包、部署等。...