gradle构建spring源码 gradle构建项目
在Gradle多项目构建中,当一个子项目依赖于另一个子项目时,可能会遇到依赖传递性问题,导致上游项目的某些依赖在下游项目中无法被识别。本文将深入探讨实现和api的区别,并提供两个纵向项目的关键类型依赖改为api,提供从供应性;二是直接在下游项目中重新声明其他的外部依赖,以确保编译和运行的正确性。1. 问题背景:Gradle多项目依赖的可见性挑战
在复杂的Gradle多项目结构中,例如一个根项目包含CommonUtils、Interceptor和SearchService等子项目,当Interceptor项目依赖于CommonU时tils 项目时,如果CommonUtils内部使用的某些外部库(如com.google.gson.Gson或com.rometools.rome.feed.rss.Channel)在Interceptor中直接引用,而这些库在CommonUt ils中实现方式声明的,那么Interceptor在编译时将无法找到这些类,导致编译失败。
这是因为Gradle的实现配置不具有提交性。当一个模块(如CommonUtils)使用实现声明其依赖时,这些依赖只在其自身模块的依赖和运行时可见,而不会自动依赖给它的其他模块(如拦截器)。这种设计旨在优化依赖速度并减少不必要的依赖关联,但同时也要求开发者明确管理跨模块的依赖可见性。2. 解决方案一:利用 api 配置实现依赖交付
解决上述问题的首选方法之一,只要 CommonUtils 中那些需要被Interceptor直接使用的外部依赖,从实现配置更改为配置。2.1 api 与实现的区别implementation:依赖仅对当前模块的编译和运行时可见,不会传递给当前依赖模块的其他模块。这有助于构建更小、更快的类路径,并减少模块间的连接。api:依赖对当前模块的编译和运行时可见,并且会提供依赖当前模块的其他模块。这意味着如果模块A通过api依赖了库X,而模块B又依赖了模块A,那么模块B也可以直接使用库X中的类。2.2 示例代码:修改CommonUtils的build.gradle
为了让Interceptor能够访问Gson和Rome相关的类,我们需要在CommonUtils的build.gradle文件中,将对应的依赖声明从implementation修改为api。
// CommonUtils/build.gradleplugins { id 'org.springframework.boot' version '2.2.0.RELEASE' id 'io.spring.dependency-management' version '1.0.8.RELEASE' id 'java'}// ... 其他配置 ...dependency { // 将需要传递给其他项目的依赖从 'implementation' 改为 'api' api 'com.google.code.gson:gson:2.8.2' api 'com.rometools:rome:1.18.0' // 优先使用新版本 // api 'rome:rome:1.0' // 如果旧版本也需要,同样改为 api // 其他不需要传递的依赖保持 'implementation'implementation 'com.itextpdf:itextpdf:5.5.13.3'implementation 'org.springframework.boot:spring-boot-starter-web' // ... 其他实现依赖 ... // 示例:fileTree 可以考虑其内部 jar 的可视性 // api fileTree(dir: 'libs',同样: '*.jar') // 如果libs下的jar也需要提交 // ... 其他依赖 ...}// ... 其他配置配置...登录后复制
注意事项:使用api会增加依赖当前模块的其他模块的编译类路径,可能导致编译时间略有增加。过度使用api可能会导致不必要的依赖提交和依赖地狱问题。应仅将那些模块API直接公开或作为公共契约被部分的依赖声明为api。对于像lombok这样的仅在编译阶段或注解处理阶段需要的工具,通常保留为compileOnly或annotationProcessor,不宜为api。3. 解决方案二:在下游项目重新声明外部依赖
另一种解决依赖性问题的方法是,直接在需要使用这些外部依赖的下游项目(本例中是Interceptor)中,重新这些声明依赖。3.1适用场景
当上游(CommonUtils)的职责是提供内部工具类,而不希望其内部依赖暴露给所有消费者时,或者当某些外部依赖仅在下游项目中的特定场景下才需要时,此项目更加合适。3.2 示例代码:修改Interceptor的build.gradle
在Interceptor的build.gradle文件中,直接添加Gson和Rome的依赖声明。
// Interceptor/build.gradleplugins { id 'org.springframework.boot' version '2.2.0.RELEASE' id 'io.spring.dependency-management' version '1.0.8.RELEASE' id 'java'}// ... 其他配置 ...dependencies { // 声明对 CommonUtils 项目的依赖实现 project(':CommonUtils') // 重新声明 Interceptor 自己需要的外部依赖实现'com.google.code.gson:gson:2.8.2' 实现 'com.rometools:rome:1.18.0' // 注意版本与 CommonUtils 保持一致或兼容实现 'io.jsonwebtoken:jjwt-api:0.11.5' 实现 'org.apache.commons:commons-io:1.3.2' 实现'org.springframework.boot:spring-boot-starter-security' 实现'org.springframework.boot:spring-boot-starter-web'compileOnly 'javax.servlet:javax.servlet-api:3.1.0'}// ... 其他配置...登录后复制
注意事项:此方法要求在多个地方相同的依赖版本,增加了维护。成本建议使用Gradle的版本管理机制(如ext块或平台依赖)来统一管理版本。它使依赖关系更加明确,每个模块都明确声明了自己直接需要的外部库。4. 总结与最佳实践
在Gradle多中处理依赖交付性问题时,理解api和实现的区别至关重要。何时使用api:当一个模块的公共API直接暴露了某个外部库的类型,或者该外部库项目是该模块构建公共契约的一部分时,应使用api。例如,如果CommonUtils返回Gson对象或接受Channel对象作为参数,那么Gson和Rome就声明应该为api。何时使用实现:对于模块实现内部细节所依赖的库,不希望暴露给外部消费者时,使用实现。这有助于保持模块的封装性,减少编译时间,并避免不必要的依赖冲突。当重新声明依赖时: 当下游模块不愿承担特定的外部依赖的责任,而下游模块确实需要该依赖关系时,可以在下游模块中明确声明。增加了依赖的显着式性,但可能因为引入版本不一致的风险。
在实际开发中,建议优先考虑使用api来依赖的传递问题,能够更好地反映模块间的实际API依赖。同时,结合Gradle的依赖管理工具(如dependencyManagement或platform)来统一管理版本,还有可能的冲突和版本问题。
在IntelliJ IDEA等IDE中,进行Gradle项目同步(Gradle -gt;重新导入所有Gradle项目)通常是解决IDE无法识别依赖的有效步骤。
以上就是Gradle多项目构建中依赖传递性问题的解析及解决方案的详细内容,更多请关注乐哥常识网其他相关文章!
