[Gradle] Gradle 聚合模块配置

2019-07-25 17:16:12 浏览数 (1)

[Gradle] Gradle 聚合模块配置

熟悉maven的同学肯定搭建过基于<parent>标签配置的子父级依赖项目。今天,我来介绍下如何利用gradle构建一个子父级嵌套的项目,避免每个子类都需要重复搭建相同的模块依赖、基础模块代码。

@TOC

手机用户请横屏获取最佳阅读体验,REFERENCES中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。

平台

地址

CSDN

https://blog.csdn.net/sinat_28690417

简书

https://www.jianshu.com/u/3032cc862300

个人博客

https://yiyuery.github.io/NoteBooks/

做什么?

  • 解决Gradle根模块下配置所有子模块的依赖和版本配置
  • 解决Gradle快速构建模块间互相依赖的问题
  • 解决Gradle模块管理中的聚合模块的配置方式
  • 解决Gradle配置项目多模块嵌套的项目配置
  • 解决多模块之间依赖和功能分层处理方式
  • 避免日常代码学习和项目搭建中重复造轮子的行为

准备工作

先看下后期搭建完后实现的项目树结构

代码语言:javascript复制
> 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-dependencycommon-template为基础大模块下的多个个基础模块,该基础模块的一些公共配置可以放在common-dependencies进行配置
  • spring-security-sso为开发的应用模块,此处可以看成是一个大的模块,该模块下面可以细分更多的小模块
  • spring-security-authspring-security-resources为第三层级的模块,主要通过依赖spring-security-sso的依赖配置来完成依赖配置的简化

根目录配置

开始搭建根目录模块

  • build.gradle 全局项目插件和依赖管理
代码语言:javascript复制
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-ssocommon-dependencies都是容器模块

  • dependency.gradle 全局依赖和设计版本的常量定义
代码语言:javascript复制
// when add a new dependency, gradle not download that package// just when module build.gradle use this dependency, then download and build projectext {
    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,此文件只需要存在在根目录中,其余子模块不需要此配置文件
代码语言:javascript复制
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..即可,例如

代码语言:javascript复制
//include ':common-dependencies:common-template'

这样,可以加速主要模块的构建和编译速度

容器模块和子模块配置

配置全局基础依赖模块单元common-dependency

  • common-dependency.gradle
代码语言:javascript复制
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

容器模块下子模块间互相依赖配置方式如下

代码语言:javascript复制
dependencies {
    compile project(':common-dependencies:common-dependency')
}

一般我们在开发项目的时候为了测试往往会写个Hello World的请求测试地址或页面,在common-template子模块中我便定义了这样的一个接口,一些注解或是类的依赖通过引入common-dependency基础依赖来实现

代码语言:javascript复制
/*
 * @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

代码语言:javascript复制
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项目,启动类配置下,因为我们引入了基础模块的依赖

代码语言:javascript复制
/*
 * @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);
    }
}
代码语言:javascript复制
server:
  port: 8080
  servlet:
    context-path: /spring-security-resources

以jar方式启动后,请求http://localhost:8080/spring-security-resources/template/hello

如此一来,对于项目模块中常用的一些测试模块和工具类,我们就可以很轻松的管理起来了,每次搭建项目也不用重复写一次啦。。。。相较于Maven的方式,个人感觉是简化了很多很多,尤其是脚本函数的定义,可以使用java的语法轻松应对,上手很简单。

最后分享下我的gradle文件夹(脚本管理)中项目树的打印脚本

代码语言:javascript复制
/**
 * 自动根据层级深度生成占位符
 * @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 - 无知者云

0 人点赞