Application of Gradle in large Java projects

Source: Internet
Author: User
Tags jboss sql using checkstyle

In the world of Java building Tools, Ant is the first and then maven. MAVEN's coc[1], dependency management, and the reuse of project building rules make maven the de facto standard for Java build tools. However, redundant dependency management configurations, complex and hard-to-scale build lifecycles have become a problem with Maven.

As a new building tool, Gradle won the springy award and was incorporated into the 2011 Jax Best Java Technology Invention Award. It is a groovy language-based build tool that preserves the benefits of MAVEN and overcomes the drawbacks of using XML cumbersome and inflexible in Maven by using Groovy's defined dsl[2]. In the article "the most exciting 5 Java projects" written by Eugene Dvorkin, he introduced Gradle in this way:

"Engineering automation is a necessary condition for the success of a software project, and it should be simple, easy to use and fun to implement." There is no stereotyped approach to building, so Gradle has no rigid methods to impose on us, although you would think that finding and describing methods is important, yet Gradle has very good support for how to describe them. I don't think the tools will save us, but Gradle can give you the freedom you need, and you can use Gradle to build easy-to-describe, maintainable, concise, high-performance projects. "

In the last six months, I worked on a large Java project using Gradle as a build script, and I realized that Gradle was so simple and easy to use during the project building process.

1. Multi-module projects

Hibernate project leader Steve Ebersole wrote an article "Gradle:why" when Hibernate changed the build script from Maven to Gradle. One drawback of Maven is that MAVEN does not support multi-module builds. In micro-service[3] architectural style popular today, in a project containing multiple module has become a trend. Gradle naturally supports multi-module and provides a number of tools to simplify build scripts. In Gradle, a module is one of its sub-items (subproject), so I use the parent project to describe the top-level project, using the subproject to describe the module under the top-level project.

1.1 Configuring sub-projects

In a multi-module project, Gradle follows the convention better than the configured (Convention over configuration) principle.

Look for the Settings.gradle file under the root of the parent project, where you set up the sub-projects that you want to include in the project build. During the initialization phase of the build (initialization), Gradle will determine which sub-projects are contained in the build based on the Settings.gradle file, and initialize a project object for each subproject. Project objects corresponding to the subproject are referenced in the build script through Project (': Sub-project-name ').

Typically, the directory structure of a multi-module project requires that the submodule be placed at the root of the parent project, but if there is a special directory structure, it can be configured in the Settings.gradle file.

My projects include:

    • A core module that describes a central business
    • A Legacy Enterprise Java Bean (Enterprise-beans) module
    • Two Web projects that provide different services (Cis-war and Admin-war)
    • A JAXB project that generates a JAXB object from a schema and an ear project to use to hit the ear package
    • A config subdirectory that is used to store the project configuration file. It is not a submodule, so config should not be added to the project's build.

They are all placed under the root project directory. We set up sub-projects in the project by using the following settings.gradle:

Include ' core ', ' Enterprise-beans ', ' Cis-war ', ' Admin-war ', ' jaxb ', ' ear '

We will need to add a subproject to the project build configuration in the Settings.gradle file instead of adding the unwanted config subdirectory.

1.2 Shared configuration

In a large Java project, the sub-projects must have the same configuration items. When we write code, we pursue code reuse and code cleanliness, while writing Gradle scripts also requires code reuse and code cleanliness. Gradle provides different ways to enable different projects to share configurations.

  • Allprojects:allprojects is a property of the parent project that returns the project object and all its child items. In the parent project's Build.gradle script, you can configure common settings for all projects, including the parent project, by sending Allprojects a closure that contains configuration information. You can usually configure the IDE's plugins, group and version information, such as:
    allprojects {    Apply plugin: ' Idea '    }

    This will apply the idea plugin to all projects, including the current project and its sub-projects.

  • Subprojects:subprojects, like Allprojects, is also a property of the parent project, which returns all child items. In the parent project's build.gradle script, give subprojects a closure that contains configuration information, and you can configure settings that are common to all sub-projects, such as shared plug-ins, repositories, dependent versions, and dependent configurations:
    subprojects {    Apply plugin: ' java '    repositories {        mavencentral ()    }    ext {          guavaversion = ' 14.0.1 '          junitversion = ' 4.10 '    }     dependencies {        compile (                "com.google.guava:guava:${ Guavaversion} "        )        testcompile (                " junit:junit:${junitversion} "        )    }}

    This will set up Java plug-ins on all sub-projects, use Mavencentral as repository for all sub-projects, and project Dependencies on Guava[4] and JUnit. In addition, the version of the dependent package is configured in Ext, which makes it easy to upgrade dependent versions later.

  • Configure: In a project, not all sub-projects will have the same configuration, but there will be some sub-projects with the same configuration, such as in my project except Cis-war and Admin-war are Web projects, other sub-projects are not. So you need to add a war plugin to these two sub-projects. The Configure of Gradle can pass in the array of sub-items and set the related configuration for those sub-projects. Use the following configuration in my project:
    Configure (Subprojects.findall {it.name.contains (' War ')}) {    Apply plugin: ' War '    }

    Configure needs to pass in an array of Project objects and set the war plug-in for all project names that contain the war's sub-items.

1.3 Exclusive Configuration

In a project, each subproject has its own unique configuration in addition to setting up a common configuration. For example, each subproject has a different dependency and a special task for each sub-project. Gradle provides two ways to set up unique configurations for each subproject individually.

    • Set the configuration of the corresponding subproject in the parent project's Build.gradle file through Project (': Sub-project-name '). For example, in the subproject core needs hibernate dependency, you can add the following configuration in the parent project's Build.gradle file:
      Project (': Core ') {      ext{                   hibernateversion = ' 4.2.1.Final '      }dependencies {     compile ' org.hibernate: Hibernate-core:${hibernateversion} "}}

      Note that there is a colon (:) in front of the subproject name. In this way, the corresponding subproject is specified and configured.

    • We can also build our own build scripts in each sub-project's directory. In the example above, you can create a build.gradle file for it under the sub-project core directory and configure all the configurations required for the core subproject in the build script. For example, add the following configuration to the Build.gradle file:
      ext{       hibernateversion = ' 4.2.1.Final '      }dependencies {     compile ' org.hibernate:hibernate-core:${ Hibernateversion} "}

Based on my experience with Gradle, for small projects with fewer sub-projects and simple configurations, the first approach is recommended, so that you can put all the configuration information in the same Build.gradle file. For example, my colleague Zheng's Open source project Moco. It has only two sub-projects, so the first configuration is used to set the project-related configuration information in the Build.gradle file in the project root directory. However, it is better to configure the project in the second way if there are many sub-projects and large projects with complex configurations. Because the second configuration puts the configuration of each project into a separate build.gradle file, it is convenient to set up and manage the configuration information for each sub-project.

1.4 Other shares

In Gradle, in addition to the configuration information share mentioned above, you can also share methods and tasks. You can add methods that are required for all child projects in the root directory's Build.gradle file, and invoke the methods defined in the parent project Build.gradle script in the subproject's Build.gradle file. For example, I define a method that gets the property from the command line and uses the default value if the property is not provided:

Def defaultproperty (PropertyName, defaultvalue) {    return Hasproperty (PropertyName)? Project[propertyname]: DefaultValue}

Note that this script is a groovy code that is very readable.

This method can also be called in the Build.gradle file of a subproject because the Defaultproperty method is defined in the parent project.

2. Configuration of the environment

To facilitate the deployment of applications to different environments, such as development, testing, and products, Gradle provides several different ways to package different environments, enabling different environments to use different configuration files. In addition, it provides a simple way to initialize the database easily.

2.1 Properties Configuration

To provide configuration information differently for different environments, Maven chooses to use profile, while Gradle provides two ways to provide a properties configuration for the build script:

  • The first approach is to use the traditional properties file, and then load different properties files by passing in different parameters when using Gradle. For example, we can provide development.properties, test.properties, and Production.properties in the project. When the project runs, use-pprofile=development to specify the configuration to load the development environment. The code for loading the properties file in the build script is as follows:
    Ext {Profile    = project[' profile ']}def loadProperties () {    def props = new Properties ()    new File ("${ Rootproject.projectdir}/config/${profile}.properties ")            . Withinputstream {                Stream--props.load (stream)            }    Props

    When you run the script, the incoming-pprofile=development can specify which configuration file to use for the running environment. The code uses project[' profile ' to read-P passed in from the command line, and Gradle will go to the Config folder in the parent project root directory to find the corresponding properties file.

  • The other way of
  • is to use groovy's syntax to define a more readable configuration file. For example, you can define the Config.groovy configuration file in the project as follows:
     environments {development {jdbc {URL = ' development ' user = ' xxxx ' password = ' xxxx '}} Test {jdbc {url = ' Test ' u            Ser = ' xxxx ' password = ' xxxx '}} production {jdbc {url = ' production '    user = ' xxxx ' password = ' xxxx '}} 

    This defines the different database configurations in three environments, which are loaded in the build script using the following code:

     Ext { Profile = project[' profile ']}def Loadgroovy () {def configfile = file (' Config.groovy '), new Configslurper (profile). Parse (Configfile.tourl ()). Toproperties ()} 

    Here, in the constructor of Configslurper, pass in the parameters of-p from the command line. Call the Loadgroovy method to load the Config.groovy file under the project root and return as a map so that the value of the URL can be obtained by Jdbc.url.

In terms of readability and the cleanliness of the code (the configuration file also requires code cleanliness), I recommend configuring it in a second way, because this approach has a clear structure. As the example above, you can put database-related information in the JDBC of this large node, rather than a flat structure like the properties file. However, for some legacy projects that already use the properties file to provide configuration information for different environments, there is no problem with the properties file.

2.2 Replacement

When you load configurations of different environments in different ways, you need to replace them with placeholders for configuration files. Use @[email protected] in the configuration file to label the location to be replaced, such as having a jdbc.properties file in the Config folder, with the following contents:

[Email protected]@[email protected]@[email protected]@

During the Gradle build process, there is a processresources task that modifies the configuration of the task to replace the placeholders in the resource file during the build process:

processresources {from    (sourceSets.main.resources.srcDirs) {        filter ( Org.apache.tools.ant.filters.ReplaceTokens,                tokens:loadgroovyconfig ())    }}

The above approach is used to work with the resource files under the Subproject Src/main/resources folder, so this code needs to be placed in a separate configuration file for the sub-project.

In some complex projects, the configuration files are often placed in a directory for unified management. For example, in my project, I specifically provided a config subdirectory, which contains all the configuration information. When working with these resource files, the processresources provided by the Gradle default is not enough, and we need to define a task in the Gradle script to replace the configuration file that contains the placeholders, but This task is dependent upon the task of the package or deploy. The code for the task is as follows:

Task Replace (Type:sync) {            def confighome = "${project.rootdir}/config" from    (confighome) {        include ' **/*. Properties '        include ' **/*.xml '        filter org.apache.tools.ant.filters.ReplaceTokens, Tokens:loadgroovyconfig ()    } into    "${builddir}/resources/main"}

This defines a sync-type task that replaces all properties and XML files of the Config folder in the parent project's root directory with the configuration loaded from the Loadgroovyconfig () method. and place the replaced file in the Resource/main directory under the Build folder. Once the packaged task relies on the task, the replacement configuration file is then hit into the package.

2.3 More complex situations

The above describes how to use Gradle in a project to handle properties and XML files with the same configuration, but some of these values are not the same. However, in some projects it is not only the value that changes between different environment configurations, it is likely that the entire configuration file is not the same;

In my project, we need to rely on an external web Service. In the development environment, we used stubs to simulate interactions with Web service, to provide test data for the development environment, to be placed in a spring configuration file, and to use the corresponding test and product environment Web service for testing and product environments. At this point, development, testing and product environment configuration is completely different. For this complex situation, Gradle can specify different resource folders for different environments during the build process, including different configuration files in different resource folders.

For example, the Config directory for our project contains the application folder, which defines the different configuration files required for different environments, as shown in the directory structure:

In the build script, use a different resource folder according to the-p parameter read from the command line, with the following code:

Sourcesets {    Main {        Resources {            srcdir ' config/application/spring/${profile} ',                         ' config/application /properties/${profile} "}}    }

In this way, you can use the properties and XML files under the resource folder for the parameters passed to-p as the configuration file for the project during the packaging process.

2.4 Initializing the database

In the project development process, in order to facilitate the construction of the same database and data for different environments, we usually need to create a table of the database and insert some initialization data. Gradle currently does not provide the relevant task or plugin, but we can create our own task to run SQL to initialize the databases on each environment.

As mentioned earlier, Gradle is the DSL defined by groovy, so we can use groovy's code to execute SQL script files in Gradle. In the Gradle script, after using groovy to load the database's driver, you can use the SQL class provided by groovy to execute SQL to initialize the database. The code is as follows:

GROOVY.SQL.SQL oraclesql = sql.newinstance (Props.getproperty (' Database.connection.url '),                props.getproperty (' Database.userid '),                props.getproperty (' Database.password '),                props.getproperty (' Database.connection.driver ')) try {        new File (script). Text.split (";"). Each {            logger.info it            oraclesql.execute (it)        }    } catch (Exception e) {}

This code initializes the GROOVY.SQL.SQL object that executes the SQL and then splits each SQL in the SQL script file by a semicolon (;) and executes. For some SQL files that must run successfully, you can abort the initialization of the database by throwing an exception in the catch block. It is important to note that the driver of the database needs to be loaded into the classpath in order to execute correctly.

Because Ant is included in Gradle, we can use Ant's SQL task to execute SQL script files in addition to executing SQL using the API provided by groovy. But without special circumstances, I don't recommend using ant tasks, which is irrelevant to this article and is no longer detailed here.

3. Code Quality

Code quality is part of the quality of software development, and in addition to manual code reviews, you should use the Automatic Check tool to automatically check your code to ensure the code quality of your project before committing your code to the code base. Here's a look at the plug-in that Gradle provides to support code checking.

3.1 CheckStyle

Checkstyle is a project under SourceForge that provides a tool to help Java developers comply with certain coding specifications. It automates the process of code specification checking, freeing developers from this important but tedious task. Gradle officially provides a plugin for Checkstyle, which only needs to be applied in the Gradle build script:

Apply plugin: ' Checkstyle '

By default, the plug-in will find/config/checkstyle/checkstyle.xml as the Checkstyle configuration file, which can be configured in the Checkstyle plug-in configuration phase (config) To set the configuration file for Checkstyle:

Checkstyle{configfile = File (' Config/checkstyle/checkstyle-main.xml ')}

Additional configuration of the Checkstyle plug-in can also be set through Checkstyle.

3.2 FindBugs

FindBugs is a static analysis tool that examines a class or JAR file to compare bytecode with a set of defect patterns to identify possible problems. Gradle Add the FindBugs plugin for the project's build script using the following code:

Apply plugin: ' FindBugs '

It is also possible to set its associated properties in the FindBugs configuration phase (config), such as the output directory of the report, which sourceset to check.

3.3 JDepend

When developing Java projects, you often encounter problems with package clutter, and the Jdepend tool helps you keep track of the dependencies (references/references) of each package during development, so you can design a highly maintainable architecture that is easier to package and release. Add the following code to the build script:

Apply plugin: ' Jdepend '
3.4 PMD

PMD is a tool for open source parsing of Java code errors. Unlike other analysis tools, PMD learns code errors through static analysis, which is to report errors without running Java programs. PMD comes with many rules that can be used directly, using these rules to identify many problems with Java source programs. In addition, users can define their own rules to check whether the Java code conforms to certain coding specifications. Add the following code to the build script:

Apply plugin: ' PMD '
3.5 Summary

Several of the code-checking plugins mentioned above apply to the build script and can be run:

Gradle Check

To perform a code quality check. For more detailed information, please refer to Gradle's official documentation. The report is generated under the Build folder under the corresponding project directory at the end of the run.

For the code-checking tool that Gradle does not provide, we can have two options: the first is to implement a Gradle plug-in, and the second is to call the ant task, and let Ant act as a medium to invoke the code-checking tools already in ant, such as the test coverage Cobertura. Our project uses ant to invoke Cobertura, but for ease of use, we encapsulate it as a gradle plugin so that it can be reused in different projects.

4. Reliance

The open source framework is used in almost every Java project. Also, for projects with multiple sub-modules, there is a dependency between projects. Therefore, managing a project's reliance on open source frameworks and other modules is a problem that every project must face. At the same time, Gradle also uses repository to manage dependencies.

4.1 Jar Package Dependency Management

MAVEN proposes using repository to manage jar packages, and Ant also provides the use of Ivy to manage jar packages. Gradle provides support for all of these respository, which can be learned in more detail from Gradle's official documentation.

Gradle follows Maven's dependency management approach, looking for the specified jar package through GroupID, name, and version to the configured repository. Similarly, it provides the same build lifecycle as MAVEN, compile, runtime, Testcompile, and Testruntime, respectively, corresponding to the different phases of the project. Specify dependencies for the build script in the following ways:

dependencies {    Compile group: ' Org.hibernate ', Name: ' Hibernate-core ', version: ' 3.6.7.Final '    testcompile Group: ' JUnit ', Name: ' JUnit ', Version ' 4.11 '}

The group, name, and version are specified here, but Gradle provides an easier way to specify dependencies:

dependencies {    compile ' org.hibernate:hibernate-core:3.6.7.final '    testcompile ' junit:junit:4.11 '}

This is much simpler than MAVEN using XML to manage dependencies, but it can be simpler. In fact, compile and testcompile here are the methods that groovy provides to Gradle, which can pass in multiple parameters, so when compile has multiple jar dependencies, it can be assigned to compile at the same time, the code is as follows:

Compile (       ' org.hibernate:hibernate-core:3.6.7.final ',            ' org.springframework:spring-context:3.1.4.release ‘)

In addition, when Respository cannot find a jar package (such as the database's driver), you can place the jar packages in a subdirectory of the project and then make the project management dependent. For example, we can create a Lib folder under the project's root directory to hold these jar packages. Use the following code to add it to your project dependencies:

dependencies {    compile (       ' org.hibernate:hibernate-core:3.6.7.final ',            ' org.springframework: Spring-context:3.1.4.release ',       filetree (dir: "${rootproject.projectdir}/lib", include: ' *.jar ')}
4.2 Dependencies between sub-projects

For multi-module projects, some modules in the project need to be dependent on other modules, mentioned earlier in the initialization phase, Gradle creates a project object for each module, and can be referenced to the object by its name. When you configure dependencies between modules, you can use this method to tell Gradle which submodules the current module depends on. For example, in our project, Cis-war relies on the core subproject to add the following code to the Cis-war build script:

dependencies {    compile (       ' org.hibernate:hibernate-core:3.6.7.final ',             project (': Core ')}

Using Project (': Core ') to refer to the core sub-project, Gradle will add the core to Classpath when building the Cis-war.

4.3 Dependency of the build script

In addition to the dependencies that the project requires, the build script itself can have its own dependencies. When using a non-Gradle official plugin, it is necessary to specify its dependencies in the build script and, of course, to specify the repository of the plugin. In Gradle, use the Buildscript block to configure dependencies for the build script.

For example, using CUCUMBER-JVM as a project BDD tool in a project, and Gradle not providing its plugins, the open source community has a cucumber plugin. Add the following code to the build script:

Buildscript {    repositories {        mavencentral ()    }    dependencies {        classpath "Gradle-cucumber-plugin : gradle-cucumber-plugin:0.2 "    }}apply plugin:com.excella.gradle.cucumber.CucumberPlugin
5. Other 5.1 apply other gradle files

When a project is complex, the Gradle script can also be complex, in addition to moving the configuration of the subproject to the build script of the corresponding project, it is possible to split the complex build script into small build scripts according to different functions, and then use the apply from in the Build.gradle. Introduce these small build scripts into the overall build script. For example, in a project that uses both jetty and cargo plug-ins to start JBoss, you can refer to Jetty.gradle and Jboss.gradle respectively, and then use the following code in Build.gradle to bring them in:

Apply from: "Jetty.gradle" Apply from: "Jboss.gradle"
5.2 Catalog for Project

In the script file, you need to access all levels of the directory structure in your project. Gradle defines some properties for the project object to point to the root of the item, which is easy to reference in the script.

    • RootDir: This property allows access to the root project path in a subproject's script file.
    • Rootproject: In a subproject, you can get the project object for the parent project through this property.
5.3 Using wrapper to specify Gradle version

To unify the version of Gradle in your project, you can define a wrapper task in the build script and specify the version of Gradle in that task and the location where the gradle is stored.

Task Wrapper (type:wrapper) {    gradleversion = ' 1.0 '    archivebase = ' PROJECT '    archivepath = ' gradle/dists '}

Running Gradle Wrapper will create a wrapper folder under the root project directory that will contain the wrapper jar package and the properties file. You can then use Gradlew to run the task. The first time you use Gradlew to execute a task, the Gradle version you specified is downloaded under gradle/dists under the project root directory. This way, when the project is built, the gradle in that directory is used to ensure that the entire team uses the same gradle version.

5.4 Using the Gradle.properties file

The Gradle build script will automatically find the Gradle.properties file in its sibling directory, where it can define some property for use by the build script. For example, the repository we want to use needs to provide a username and password that can be configured in Gradle.properties. This allows each team member to modify the profile without having to upload it to the code base to affect other members of the team. You can use the following code definitions:

Username=userpassword=password

Use "${username}" in the build script to access the related values defined in the file.

Due to space limitations, this article is only part of my experience with using Gradle on a large Java project and does not cover all gradle-related knowledge, including how to write Gradle plugins and Gradle for other languages. Readers can read Gradle's official documentation (compared to other open source software, another feature of Gradle is document detail) to understand. In addition, Gradle is a groovy-based build tool that also needs to know and use groovy when using Gradle. Therefore, in the process of learning Gradle plug-ins, can also learn groovy related usage, is double benefit.

Reference documents:

[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/

Application of Gradle in large Java projects

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.