熟悉
maven
的同学肯定搭建过基于<parent>
标签配置的子父级依赖项目。今天,我来介绍下如何利用gradle构建一个子父级嵌套的项目,避免多个相似子级项目中出现重复模块依赖配置、重复代码。
目录
手机用户请
横屏
获取最佳阅读体验,REFERENCES
中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。
平台 | 地址 |
---|---|
CSDN | https://blog.csdn.net/sinat_28690417 |
简书 | https://www.jianshu.com/u/3032cc862300 |
个人博客 | https://yiyuery.github.io/NoteBooks/ |
做什么?
- 解决Gradle根模块下配置所有子模块的依赖和版本配置
- 解决Gradle快速构建模块间互相依赖的问题
- 解决Gradle模块管理中的聚合模块的配置方式
- 解决Gradle配置项目多模块嵌套的项目配置
- 解决多模块之间依赖和功能分层处理方式
- 避免日常代码学习和项目搭建中重复造*的行为
准备工作
先看下后期搭建完后实现的项目树结构
> Task :spring-security-sso:spring-security-resources:printTree
+--- ishare-project
| -- common-dependencies
| +---- common-dependency
| \---- common-template
| -- spring-security-sso
| +---- spring-security-auth
| \---- spring-security-resources
编写了一个脚本程序,把项目的结构树打印了出来。其中,
-
ishare-project
为整个工程的根目录; -
common-dependencies
为整个工程的基础依赖模块 -
common-dependency
、common-template
为基础大模块下的多个个基础模块,该基础模块的一些公共配置可以放在common-dependencies
进行配置 -
spring-security-sso
为开发的应用模块,此处可以看成是一个大的模块,该模块下面可以细分更多的小模块 -
spring-security-auth
、spring-security-resources
为第三层级的模块,主要通过依赖spring-security-sso
的依赖配置来完成依赖配置的简化
根目录配置
开始搭建根目录模块
-
build.gradle
全局项目插件和依赖管理
configure(allprojects) { project ->
group = 'com.example'
version = '0.0.1-SNAPSHOT'
apply from: "${rootDir}/dependency.gradle"
apply plugin: 'maven'
ext.gradleScriptDir = "${rootProject.projectDir}/gradle"
apply from: "${gradleScriptDir}/task.gradle"
}
def holderProjects = Arrays.asList('spring-security-sso', 'common-dependencies')
configure(subprojects) { subproject ->
//此处主要为了过滤容器模块中的插件配置,容器模块的主要用来管理下属部分的模块,无需添加依赖和插件
if (!holderProjects.contains(subproject.name)) {
apply plugin: 'java'
sourceCompatibility = 1.8
targetCompatibility = 1.8
[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'
apply from: "${rootDir}/dependency.gradle"
repositories {
maven { url = 'https://maven.aliyun.com/repository/jcenter' }
maven { url = 'https://oss.sonatype.org/content/repositories/snapshots/' }
maven { url = "https://plugins.gradle.org/m2/" }
jcenter()
}
dependencies {
implementation libs['commons-lang3']
//lombok
annotationProcessor libs['lombok']
compileOnly libs['lombok']
testAnnotationProcessor libs['lombok']
testCompileOnly libs['lombok']
testImplementation libs['junit']
}
}
}
此处主要为了过滤容器模块中的插件配置,容器模块的主要用来管理下属部分的模块,无需添加依赖和插件spring-security-sso
、common-dependencies
都是容器模块
-
dependency.gradle
全局依赖和设计版本的常量定义
// when add a new dependency, gradle not download that package
// just when module build.gradle use this dependency, then download and build project
ext {
ver = [
utils : [
guava : "26.0-jre",
lombok : "1.18.2",
common_lang3 : "3.8",
commons_io : "2.4",
commons_collections4: "4.2",
fastjson : "1.2.47",
junit : "4.12",
joda_time : "2.10",
groovy_all : "2.4.13"
],
swagger: [
swagger2markup : "1.3.3",
swagger_annotations: "1.5.22"
],
spring : [
boot : "2.1.0.RELEASE",
spring_version : "5.1.2.RELEASE",
spring_jdbc_version: "4.3.18.RELEASE"
],
cloud : [
spring_cloud_start_alibaba_nacos_config: "0.2.1.RELEASE"
],
nacos : [
nacos_config_spring_boot_starter: "0.2.1"
],
common : [
//数据库
postgresql : "42.2.5",
mysql : '8.0.13',
durid : '1.1.14',
mybatis_plus_boot_starter : "3.0.6",
mybatis : "3.4.6",
mybatis_generator : "1.3.7",
mapper : "4.1.2",
mybatis_spring_boot_starter: "1.3.2",
velocity_engine_core : "2.0",
freemarker : "2.3.28",
pagehelper : "5.1.8",
//日志
slf4j_version : "1.7.25",
logback_version : "1.2.3"
]
]
libs = [
//UTILS
"lombok" : "org.projectlombok:lombok:$ver.utils.lombok",
"commons-lang3" : "org.apache.commons:commons-lang3:$ver.utils.common_lang3",
"commons-io" : "commons-io:commons-io:$ver.utils.commons_io",
"commons-collections4" : "org.apache.commons:commons-collections4:$ver.utils.commons_collections4",
"guava" : "com.google.guava:guava:$ver.utils.guava",
"fastjson" : "com.alibaba:fastjson:$ver.utils.fastjson",
"joda-time" : "joda-time:joda-time:$ver.utils.joda_time",
"groovy-all" : "org.codehaus.groovy:groovy-all:$ver.utils.groovy_all",
//TEST
"junit" : "junit:junit:$ver.utils.junit",
//SPRING-BOOT
"spring-boot-gradle-plugin" : "org.springframework.boot:spring-boot-gradle-plugin:$ver.spring.boot",
"jdbc" : "org.springframework.boot:spring-boot-starter-jdbc:$ver.spring.boot",
"spring-security-oauth2" : "org.springframework.security.oauth:spring-security-oauth2:$ver.spring.boot",
//SPRING
"spring-context" : "org.springframework:spring-context:$ver.spring.spring_version",
"spring-web" : "org.springframework:spring-web:$ver.spring.spring_version",
"spring-jdbc" : "org.springframework:spring-jdbc:$ver.spring.spring_jdbc_version",
//DB
"postgresql" : "org.postgresql:postgresql:$ver.common.postgresql",
"mysql-connector-java" : "mysql:mysql-connector-java:$ver.common.mysql",
"druid" : "com.alibaba:druid:$ver.common.druid",
// MYBATIS 核心库
"mybatis-plus-boot-starter" : "com.baomidou:mybatis-plus-boot-starter:$ver.common.mybatis_plus_boot_starter",
"mybatis-plus" : "com.baomidou:mybatis-plus:$ver.common.mybatis_plus",
"mybatis-plus-generator" : "com.baomidou:mybatis-plus-generator:$ver.common.mybatis_plus",
"mybatis" : "org.mybatis:mybatis:$ver.common.mybatis",
"mybatis-generator-core" : "org.mybatis.generator:mybatis-generator-core:$ver.common.mybatis_generator",
"mapper" : "tk.mybatis:mapper:$ver.common.mapper",
"mybatis-spring-boot-starter" : "org.mybatis.spring.boot:mybatis-spring-boot-starter:$ver.common.mybatis_spring_boot_starter",
//模板引擎,需要指定 mpg.setTemplateEngine(new FreemarkerTemplateEngine());
"freemarker" : "org.freemarker:freemarker:$ver.common.freemarker",
"velocity-engine-core" : "org.apache.velocity:velocity-engine-core:$ver.common.velocity_engine_core",
"pagehelper" : "com.github.pagehelper:pagehelper:$ver.common.pagehelper",
//LOG
"slf4j-api" : "org.slf4j:slf4j-api:$ver.common.slf4j_version",
"log4j-over-slf4j" : "org.slf4j:log4j-over-slf4j:$ver.common.slf4j_version",
"jcl-over-slf4j" : "org.slf4j:jcl-over-slf4j:$ver.common.slf4j_version",
"jul-to-slf4j" : "org.slf4j:jul-to-slf4j:$ver.common.slf4j_version",
"logback-classic" : "ch.qos.logback:logback-classic:$ver.common.logback_version",
"logback-core" : "ch.qos.logback:logback-core:$ver.common.logback_version",
//SWAGGER
"swagger2markup" : "io.github.swagger2markup:swagger2markup:$ver.swagger.swagger2markup",
//热部署
"spring-boot-devtools" : "org.springframework.boot:spring-boot-devtools:$ver.spring.boot",
//Spring Cloud
"spring-cloud-starter-alibaba-nacos-config": "org.springframework.cloud:spring-cloud-starter-alibaba-nacos-config:$ver.cloud.spring_cloud_start_alibaba_nacos_config",
//Nacos
"nacos-config-spring-boot-starter" : "com.alibaba.boot:nacos-config-spring-boot-starter:$ver.nacos.nacos_config_spring_boot_starter",
"nacos-config-spring-boot-actuator" : "com.alibaba.boot:nacos-config-spring-boot-actuator:$ver.nacos.nacos_config_spring_boot_starter",
//Swagger
"swagger-annotations" : "io.swagger:swagger-annotations:$ver.swagger.swagger_annotations"
]
}
- 根目录模块配置
settings.gradle
,此文件只需要存在在根目录中,其余子模块不需要此配置文件
if ( !JavaVersion.current().java8Compatible ) {
throw new GradleException( "Gradle must be run with Java 8" )
}
include ':common-dependencies:common-dependency'
include ':common-dependencies:common-template'
include ':spring-security-sso:spring-security-auth'
include ':spring-security-sso:spring-security-resources'
/**
* 递归检查build.gradle文件是否根据模块名生成
* @param dirs
* @return
*/
def assertProjectBuildFile(Set<ProjectDescriptor> dirs){
if(dirs.size()>0){
dirs.each { project ->
project.buildFileName = "${project.name}.gradle"
assert project.projectDir.isDirectory()
assert project.buildFile.exists()
assert project.buildFile.isFile()
}
if(dirs.children.size()>0){
dirs.children.each {project->
assertProjectBuildFile(project)
}
}
}
}
assertProjectBuildFile(rootProject.children)
配置文件中的assertProjectBuildFile
函数主要为了检查模块下是否含有对应模块名的gradle配置文件,
在编译的时候可以选择指定编译模块,以每个子模块为最小配置单元,注释掉对应模块的include…即可,例如
//include ':common-dependencies:common-template'
这样,可以加速主要模块的构建和编译速度
容器模块和子模块配置
配置全局基础依赖模块单元
common-dependency
common-dependency.gradle
dependencies {
/** 开源的Java工具库*/
compile libs["guava"]
compile libs["commons-lang3"]
compile libs["commons-collections4"]
compile libs["fastjson"]
compile libs["joda-time"]
compile libs["guava"]
compile libs["commons-io"]
/*分页和mapper插件*/
compile libs["pagehelper"]
compile libs["mapper"]
//mybatis核心库
compile libs["mybatis"]
/**数据库驱动*/
compile libs["postgresql"]
compile libs["mysql-connector-java"]
compile libs["spring-context"]
compile libs["spring-web"]
compile libs["spring-jdbc"]
compile libs["slf4j-api"]
compile libs["log4j-over-slf4j"]
compile libs["jcl-over-slf4j"]
compile libs["jul-to-slf4j"]
compile libs["logback-classic"]
compile libs["logback-core"]
compile group: 'io.swagger', name: 'swagger-annotations', version: '1.5.22'
compile group: 'com.alibaba', name: 'druid', version: '1.1.14'
testCompile libs["junit"]
}
common-template.gradle
容器模块下子模块间互相依赖配置方式如下
dependencies {
compile project(':common-dependencies:common-dependency')
}
一般我们在开发项目的时候为了测试往往会写个Hello World
的请求测试地址或页面,在common-template
子模块中我便定义了这样的一个接口,一些注解或是类的依赖通过引入common-dependency
基础依赖来实现
/*
* @ProjectName: 编程学习
* @Copyright: 2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
* @address: http://xiazhaoyang.tech
* @date: 2019/3/26 06:59
* @email: xiazhaoyang@live.com
* @description: 本内容仅限于编程技术学习使用,转发请注明出处.
*/
package com.example.template;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* <p>
*
* </p>
*
* @author xiazhaoyang
* @version v1.0.0
* @date 2019/3/26 06:59
* @modificationHistory=========================逻辑或功能性重大变更记录
* @modify By: {修改人} 2019/3/26
* @modify reason: {方法名}:{原因}
* ...
*/
@RestController
@RequestMapping("/template")
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "common-template request to `/template/hello`:" +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())+"\n";
}
}
特别需要注意的是:common-dependencies
作为容器模块,基础项目依赖我已经在根目录中定义过了,所以并未定义对应的脚本或是函数,所以common-dependencies.gradle
中是空的。
这样做的原因是,如果是一些构建任务脚本,我在根目录下新建了一个gradle文件夹,里面放置了很多我需要执行的xxx.gradle
构建任务脚本,这是为了脚本的统一管理,这样做对代码复用和脚本管理是很有帮助的。例外,项目模块的基础依赖的定义,一个根目录下的build.gradle
已经够用了,没有必要在每个容器模块中定义子模块的依赖了。要不然,common-dependencies
的配置就显得很多余了。而且我的初衷就是通过common-dependencies
下定义不同类型项目需要的项目依赖。
然后在根目录的build.gradle
文件中定义一些基础构建规则和模块依赖
基础模块的使用
前文说了配置了基础依赖的模块和含有测试模板的模块,那么,如何引入并使用它们呢?
spring-security-resources.gradle
buildscript {
repositories {
maven { url = "https://plugins.gradle.org/m2/" }
maven { url = "http://maven.aliyun.com/nexus/content/groups/public/" }
jcenter()
}
dependencies {
classpath libs["spring-boot-gradle-plugin"]
}
}
apply plugin: "io.spring.dependency-management"
apply plugin: "org.springframework.boot"
bootJar {
baseName = 'spring-security-resources'
version = '1.0.0'
}
dependencies {
compile project(':common-dependencies:common-template')
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
此处是个boot项目,启动类配置下,因为我们引入了基础模块的依赖
/*
* @ProjectName: 编程学习
* @Copyright: 2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
* @address: http://xiazhaoyang.tech
* @date: 2019/5/13 22:59
* @email: xiazhaoyang@live.com
* @description: 本内容仅限于编程技术学习使用,转发请注明出处.
*/
package com.example.res;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
/**
* <p>
*
* </p>
*
* @author xiazhaoyang
* @version v1.0.0
* @date 2019/5/13 22:59
* @modificationHistory=========================逻辑或功能性重大变更记录
* @modify By: {修改人} 2019/5/13
* @modify reason: {方法名}:{原因}
* ...
*/
@SpringBootApplication
@ComponentScan(basePackages = {"com.example"})
public class SpringSecurityResourceApp {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityResourceApp.class, args);
}
}
server:
port: 8080
servlet:
context-path: /spring-security-resources
以jar方式启动后,请求http://localhost:8080/spring-security-resources/template/hello
如此一来,对于项目模块中常用的一些测试模块和工具类,我们就可以很轻松的管理起来了,每次搭建项目也不用重复写一次啦。。。。相较于Maven
的方式,个人感觉是简化了很多很多,尤其是脚本函数的定义,可以使用java的语法轻松应对,上手很简单。
最后分享下我的gradle文件夹(脚本管理)中项目树的打印脚本
/**
* 自动根据层级深度生成占位符
* @param level
* @return
*/
static def generatorTreeNodePrefix(int level, String startStr) {
def s = '| '
for (int i = 0; i < level; i++) {
s += ((0 == i) && level != 1 ? startStr : '--')
}
return s+' '
}
/**
* 打印工程目录树
* @param dirs
* @return
*/
def printProjectTree(Map<String, Project> dirs) {
if (dirs.size() > 0) {
def len = dirs.values().size()
def index = 0
dirs.values().each { e ->
def startStr = (index++ + 1 == len) ? '\\--' : '+--'
println generatorTreeNodePrefix(e.depth, startStr) + e.name
if (e.childProjects.values().size() > 0) {
printProjectTree(e.childProjects)
}
}
}
}
task printTree << {
println '+--- ' + rootProject.name
printProjectTree(rootProject.childProjects)
}
Tips
: 引入任务脚本的方式在文章开头的根目录配置文件中哦!apply from: "${gradleScriptDir}/task.gradle"
REFRENCES
- Gradle(入门)- 云栖社区
- Gradle项目树 - CSDN
- java – 具有共享依赖项的多模块项目的Gradle配置
- 你真的了解 Gradle 吗?
- Gradle初探(一):创建一个Gradle项目
- Gradle学习系列之八——构建多个Project - 无知者云
更多
扫码关注“架构探险之道”,回复
文章名称
获取更多源码和文章资源
知识星球(扫码加入获取源码和文章资源链接)