其它工具 Gradle 在大型 Java 项目上的应用

scmroad · 发布于 2013年7月08日 · 224 次阅读
96

[i=s] 本帖最后由 scmroad 于 2013-7-8 15:29 编辑

作者 何海洋 在Java构建工具的世界里,先有了Ant,然后有了Maven。Maven的CoC[1]、依赖管理以及项目构建规则重用性等特点,让Maven几乎成为Java构建工具的事实标准。然而,冗余的依赖管理配置、复杂并且难以扩展的构建生命周期,都成为使用Maven的困扰。

Gradle作为新的构建工具,获得了2010 Springy大奖,并入围了2011的Jax最佳Java技术发明奖。它是基于Groovy语言的构建工具,既保持了Maven的优点,又通过使用Groovy定义的DSL[2],克服了 Maven中使用XML繁冗以及不灵活等缺点。在Eugene Dvorkin撰写的文章《最让人激动的5个Java项目》中,他是这样介绍Gradle的:

“[b]工程自动化是软件项目成功的必要条件,而且它应该实现起来简单、易用、好玩。[/b]构建没有千篇一律的方法,所以Gradle没有死板的强加方法于我们,尽管你会认为查找和描述方法很重要,然而Gradle对于如何描述有着非常好的支持。[b]我不认为工具能够拯救我们,但是Gradle能给你所需要的自由,你可以利用Gradle构建易描述的、可维护的、简洁的、高性能项目[/b]”。

在最近半年里,我在使用Gradle作为构建脚本的大型Java项目上工作,更深切体会到Gradle在项目构建过程中是如此的简单、易用。

  1. 多Module的项目

Hibernate项目负责人Steve Ebersole在Hibernate将构建脚本从Maven换成Gradle时,专门写了一篇文章《Gradle: why?》,文中提到[b]Maven的一个缺点就是:Maven不支持多module的构建。[/b]在Micro-Service[3]架构风格流行的今天,在一个项目里面包含多个Module已成为一种趋势。Gradle天然支持多module,并且提供了很多手段来简化构建脚本。在Gradle中,一个模块就是它的一个子项目(subproject),所以,我使用父项目来描述顶级项目,使用子项目来描述顶级项目下面的模块。

1.1 配置子项目

在多模块的项目中,Gradle遵循惯例优于配置 (Convention Over Configuration)原则。

在父项目的根目录下寻找settings.gradle文件,在该文件中设置想要包括到项目构建中的子项目。在构建的初始化阶段(Initialization),Gradle会根据settings.gradle 文件来判断有哪些子项目被include到了构建中,并为每一个子项目初始化一个Project对象,在构建脚本中通过project(‘:sub-project-name’)来引用子项目对应的Project对象。

通常,多模块项目的目录结构要求将子模块放在父项目的根目录下,但是如果有特殊的目录结构,可以在settings.gradle文件中配置。

我所在的项目包括:

[list] []一个描述核心业务的core模块 [] 一个遗留的Enterprise Java Bean(enterprise-beans)模块 [] 两个提供不同服务的Web项目(cis-war和admin-war) [] 一个通过schema生成jaxb对象的jaxb项目以及一个用来用来打ear包的ear项目 [*] 一个用于存放项目配置文件相关的config子目录。它不是子模块,所以 config不应该被加到项目的构建中去。 [/list]

它们都放置在根项目目录下。我们通过如下的settings.gradle来设置项目中的子项目:

我们将需要加入到项目构建中的子项目配置在settings.gradle文件中,而没有加入不需要的config子目录。

共收到 4 条回复
96
scmroad · #1 · 2013年7月08日

[i=s] 本帖最后由 scmroad 于 2013-7-8 15:45 编辑

1.2 共享配置

在大型Java项目中,子项目之间必然具有相同的配置项。我们在编写代码时,要追求代码重用和代码整洁;而在编写Gradle脚本时,同样需要保持代码重用和代码整洁。Gradle 提供了不同的方式使不同的项目能够共享配置。

allprojects:allprojects是父Project的一个属性,该属性会返回该Project对象以及其所有子项目。在父项目的build.gradle脚本里,可以通过给allprojects传一个包含配置信息的闭包,来配置所有项目(包括父项目)的共同设置。通常可以在这里配置IDE的插件,group和version等信息,比如:

根据我对Gradle的使用经验,对于子项目少,配置简单的小型项目,推荐使用第一种方式配置,这样就可以把所有的配置信息放在同一个build.gradle文件里。例如我同事郑晔的开源项目moco。它只有两个子项目,因而就使用了第一种方式配置,在项目根目录下的build.gradle文件中设置项目相关的配置信息。但是,若是对于子项目多,并且配置复杂的大型项目,使用第二种方式对项目进行配置会更好。因为,第二种配置方式将各个项目的配置分别放到单独的build.gradle文件中去,可以方便设置和管理每个子项目的配置信息。

96
scmroad · #2 · 2013年7月08日

1.4 其他共享

在Gradle中,除了上面提到的配置信息共享,还可以共享方法以及Task。可以在根目录的build.gradle文件中添加所有子项目都需要的方法,在子项目的build.gradle文件中调用在父项目build.gradle脚本里定义的方法。例如我定义了这样一个方法,它可以从命令行中获取属性,若没有提供该属性,则使用默认值:

这里定义了一个Sync类型的Task,会将父项目的根目录下的config文件夹的所有properties和xml文件使用从loadGroovyConfig()方法中加载出来的配置替换,并将替换之后的文件放到build文件夹下的resource/main目录中。再让打包的Task依赖这个Task,就会把替换之后的配置文件打到包中。

2.3 更复杂的情况

上面介绍了在项目中如何使用Gradle处理 properties和xml文件中具有相同配置,但其中的一些值并不相同的情况 。然而,在有些项目中不同的环境配置之间变化的不仅是值,很有可能整个配置文件都不相同;那么,使用上面替换的处理方式就无法满足要求了。

在我所在的项目中,我们需要依赖一个外部的Web Service。在开发环境上,我们使用了Stub来模拟和Web Service之间的交互,为开发环境提供测试数据,这些数据都放置在一个Spring的配置文件中;而在测试和产品环境上,又要使用对应的测试和产品环境的Web Service。这时,开发、测试与产品环境的配置完全不同。对于这种复杂的情况,Gradle可以在构建过程中为不同的环境指定不同的资源文件夹,在不同的资源文件夹中包含不同的配置文件。

96
scmroad · #3 · 2013年7月08日

例如,在我们项目的config目录下包含了application文件夹,定义了不同环境所需的不同配置文件,其目录结构如下图所示:

[attach]2144[/attach]

在构建脚本中,根据从命令行读入的-P参数,使用不同的资源文件夹,其代码如下:

还可以通过checkstyle设置CheckStyle插件的其他配置。

3.2 FindBugs

FindBugs 是一个静态分析工具,它检查类或者 JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题。Gradle使用如下的代码为项目的构建脚本添加FindBugs的插件:

apply plugin: 'findbugs' 同样也可以在FindBugs的配置阶段(Configuration)设置其相关的属性,比如Report的输出目录、检查哪些sourceSet等。

3.3 JDepend

在开发Java项目时经常会遇到关于包混乱的问题, JDepend工具可以帮助你在开发过程中随时跟踪每个包的依赖性(引用/被引用),从而设计高维护性的架构,不论是在打包发布还是版本升级都会更加轻松。在构建脚本中加入如下代码即可:

apply plugin: 'jdepend' 3.4 PMD

PMD是一种开源分析Java代码错误的工具。与其他分析工具不同的是,PMD通过静态分析获知代码错误,即在不运行Java程序的情况下报告错误。PMD附带了许多可以直接使用的规则,利用这些规则可以找出Java源程序的许多问题。此外,用户还可以自己定义规则,检查Java代码是否符合某些特定的编码规范。在构建脚本中加入如下代码:

apply plugin: 'pmd' 3.5 小结

上面提到的几种代码检查插件apply到构建脚本之后,可以运行:

gradle check 来执行代码质量检查。更详细的信息请查阅Gradle的官方文档。运行结束后会在对应的项目目录下的build文件夹下生成report。

对于Gradle没有提供的代码检查工具,我们可以有两种选择:第一就是自己实现一个Gradle插件,第二就是调用Ant任务,让Ant作为一个媒介去调用在Ant中已经有的代码检查工具,比如测试覆盖率的Cobertura。我们的项目使用了Ant来调用Cobertura,但是为了使用方便,我们将它封装为一个Gradle插件,这样就可以在不同的项目里重用。

96
scmroad · #4 · 2013年7月08日
  1. 依赖

几乎每个Java项目都会用到开源框架。同时,对于具有多个子模块的项目来说,项目之间也会有所依赖。所以,管理项目中对开源框架和其他模块的依赖是每个项目必须面对的问题。同时,Gradle也使用Repository来管理依赖。

4.1 Jar包依赖管理

Maven提出了使用Repository来管理Jar包,Ant也提供了使用Ivy来管理jar包。Gradle提供了对所有这些Respository的支持,可以从Gradle的官方文档上了解更详细的信息。

Gradle沿用Maven的依赖管理方法,通过groupId、name和version到配置的Repository里寻找指定的Jar包。同样,它也提供了和Maven一样的构建生命周期,compile、runtime、testCompile和testRuntime分别对应项目不同阶段的依赖。通过如下方式为构建脚本指定依赖:

在构建脚本中使用"${username} "就可以访问该文件中定义的相关值。

由于篇幅有限,本文只是我在一个大型Java项目上使用Gradle的部分经验,并未涵盖所有Gradle相关的知识,包括如何编写Gradle插件以及Gradle对其他语言的构建,读者可以通过阅读Gradle的官方文档(比起其他开源软件,Gradle的另一特点就是文档详细)来了解。另外,Gradle是基于Groovy的构建工具,在使用Gradle的时候也需要了解和使用Groovy。所以,在学习Gradle插件的过程中,也能学会Groovy相关的用法,可谓一举两得。

参考文献:

[1] CoC: http://en.wikipedia.org/wiki/Convention_over_configuration

[2] DSL: http://en.wikipedia.org/wiki/Domain-specific_language

[3] Micro Service Architecture: http://yobriefca.se/blog/2013/04/29/micro-service-architecture/

[4] Guava: https://code.google.com/p/guava-libraries/

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册