Reprint: This article is based on Bole Online - JustinWu Translation. No reprinting without permission! English Origin: Petri Kainulainen . Welcome to join us Translation Group.

In real life, it is not impossible to create an application without any external dependencies, but it is also extremely challenging. This is why dependency management is a vital part of every software project.

This tutorial focuses on how to use Gradle to manage the dependencies of our project. We will learn how to configure the application warehouse and the dependencies we need. We will also integrate theory with practice to implement a simple demonstration program.

Let's get started.

Introduction to Warehouse Management

Essentially, a warehouse is a container for storing dependencies. Each project has one or more warehouses.

Gradle supports the following warehouse formats:

Let's see how we should configure each warehouse type in our build.

Adding Ivy Warehouse to Construction

We can add Ivy repositories to our build by using URL addresses or local file system addresses.

If you want to add an Ivy repository through the URL address, we can add the following code fragments to the build.gradle file:

repositories {
    ivy {
        url "http://ivy.petrikainulainen.net/repo"
    }
}

If you want to add an Ivy repository through the local filesystem address, we can add the following code fragments to the build.gradle file:

repositories {
    ivy {       
        url "../ivy-repo"
    }
}

Tip: If you want more information about Ivy warehouse configuration, you can refer to the following resources:

Let's move on, here's how to add the Maven repository to the build.

Add Maven Warehouse in Construction

Similar to the Ivy repository, we can add the Maven repository to our build through a URL address or a local file system address.

If you want to add a Maven repository through the URL address, we can add the following code fragments to the build.gradle file:

repositories {
    maven {
        url "http://maven.petrikainulainen.net/repo"
    }
}

If you want to add a Maven repository through the local filesystem address, we can add the following code fragments to the build.gradle file:

repositories {
    maven {       
        url "../maven-repo"
    }
}

When we joined the Maven warehouse, Gradle provided three "aliases" for us to use. They were:

  • The mavenCentral() alias indicates that dependencies are obtained from the Central Maven 2 repository.
  • jcenter() alias to indicate that dependencies are obtained from the Bintary's JCenter Maven repository.
  • The mavenLocal() alias indicates that dependencies are obtained from the local Maven repository.

If we want to add the Central Maven 2 repository to the build, we must add the following code fragments to the build.gradle file:

repositories {
    mavenCentral()
}

Tip: If you want more information about Maven warehouse configuration, you can refer to this article: section 50.6.4 Maven Repositories of the Gradle User Guide

Let's move on, here's how to add Flat Directory repository to the build.

Add Flat Directory Warehouse in Construction

If we want to use the Flat Directory repository, we need to add the following code fragments to the build.gradle file:

repositories {
    flatDir {
        dirs 'lib'
    }
}

This means that the system will search for dependencies in the lib directory. Similarly, you can add multiple directories if you like. The code snippets are as follows:

repositories {
    flatDir {
        dirs 'libA', 'libB'
    }
}

Tip: If you want more information about Flat Directory repository configuration, you can refer to the following resources:

Let's move on. Here's how to use Gradle to manage dependencies in a project.

Brief Introduction to Dependency Management

After configuring the project repository, we can declare the dependencies. If we want to declare a new dependency, we can take the following steps:

  1. Specify the configuration of dependencies.
  2. Declare the required dependencies.

Let's look at the detailed steps:

Dependency Classification in Configuration

In Gradle, dependencies are categorized by a given name. These classifications are called configuration items. We can use configuration items to declare the external dependencies of the project.

The Java plug-in specifies several dependency configurations It is described as follows:

  • When the source code of the project is compiled, the dependencies in the compile configuration item are required.
  • Dependencies contained in runtime configuration items are required at run time.
  • The dependencies contained in the testCompile configuration item are necessary when compiling the test code for the project.
  • The dependencies contained in the testRuntime configuration item are necessary when running test code.
  • The archives configuration item contains files generated by the project (such as Jar files).
  • The default configuration item contains the dependencies that the runtime must have.

Let's move on, here's how to declare dependencies in a project.

Declare project dependencies

The most common dependencies are called external dependencies, which are stored in external warehouses. An external dependency can be specified by the following attributes:

  • The group attribute specifies the dependent grouping (in Maven, groupId).
  • The name attribute specifies the name of the dependency (in Maven, artifactId).
  • The version attribute specifies the version of the external dependency (in Maven, version).

Tip: These attributes are necessary in Maven repositories. If you use other repositories, some attributes may be optional. For example, if you use the Flat directory repository, You may only need to specify the name and version..

Let's assume that we need to specify the following dependencies:

  • The dependent grouping is foo.
  • The name of the dependency is foo.
  • Dependent version is 0.1.
  • These dependencies are required at project compilation time.

We can add the following code fragments to build.gradle to make dependency declarations:

dependencies {
    compile group: 'foo', name: 'foo', version: '0.1'
}

We can also use a shortcut to declare dependencies: [group]:[name]:[version]. If we want to do this, we can add the following code snippets to build.gradle:

dependencies {
    compile 'foo:foo:0.1'
}

We can also add multiple dependencies to the same configuration item in the traditional way as follows:

dependencies {
    compile (
        [group: 'foo', name: 'foo', version: '0.1'],
        [group: 'bar', name: 'bar', version: '0.1']
    )
}

If a shortcut is used, it can be as follows:

dependencies {
    compile 'foo:foo:0.1', 'bar:bar:0.1'
}

Naturally, it is also possible to declare dependencies that belong to different configurations. For example, if we want to declare dependencies that belong to compile and testCompile configuration items, we can do this:

dependencies {
    compile group: 'foo', name: 'foo', version: '0.1'
    testCompile group: 'test', name: 'test', version: '0.1'
}

Similarly, the quicker way to give strength comes again.

dependencies {
    compile 'foo:foo:0.1'
    testCompile 'test:test:0.1'
}

Tip: You can go from This article Get more information about dependency declarations.

We've learned the basics of dependency management, so let's implement a demo program.

Create a demo program

The requirements of the demo program are as follows:

  • The build script for the demo program must use the Maven central repository.
  • The demo program must use Log4j to write to the log.
  • Demonstration programs must include unit tests to ensure correct information returns, and unit tests must be written using JUnit.
  • The demo program must create an executable Jar file.

Let's see how these requirements can be met.

Configuration warehouse

One requirement of our demo program is that the build script must use the Maven central repository. After we configure the build script using the Maven central repository, the source code is as follows:

apply plugin: 'java'
 
repositories {
    mavenCentral()
}
 
jar {
    manifest {
        attributes 'Main-Class': 'net.petrikainulainen.gradle.HelloWorld'
    }
}

Let's look again at how to make a dependency declaration for our demo program.

Dependency Statement

In the build.gradle file, we declare two dependencies:

After we declare these dependencies, the build.gradle file looks like this:

apply plugin: 'java'
 
repositories {
    mavenCentral()
}
 
dependencies {
    compile 'log4j:log4j:1.2.17'
    testCompile 'junit:junit:4.11'
}
 
jar {
    manifest {
        attributes 'Main-Class': 'net.petrikainulainen.gradle.HelloWorld'
    }
}

Let's go ahead and add a little bit of code.

Write code

In order to fulfill the requirements of our demo program, "we have to over-engineer it", we will follow the following steps to create the program:

  1. Create a MessageService class that returns the string "Hello World!" when its getMessage() method is called.
  2. Create a MessageService Test class to ensure that the getMessage() method in the MessageService class returns the string "Hello World!".
  3. Create the main class of the program, get information from the MessageService object, and write to the log using Log4j.
  4. Configure Log4j.

Let's do it step by step.

First, a new MessageService class is created and implemented in the src/main/java/net/petrikainulainen/gradle directory. The code is as follows:

package net.petrikainulainen.gradle;
 
public class MessageService {
 
    public String getMessage() {
        return "Hello World!";
    }
}

Secondly, a new MessageService Test class is created in the src/main/test/net/petrikainulainen/gradle directory, and the unit test is written. The code is as follows:

package net.petrikainulainen.gradle;
 
import org.junit.Before;
import org.junit.Test;
 
import static org.junit.Assert.assertEquals;
 
public class MessageServiceTest {
 
    private MessageService messageService;
 
    @Before
    public void setUp() {
        messageService = new MessageService();
    }
 
    @Test
    public void getMessage_ShouldReturnMessage() {
        assertEquals("Hello World!", messageService.getMessage());
    }
}

Thirdly, a new HelloWorld class is created in the src/main/java/net/petrikainulainen/gradle directory, which is the main class of the program. It obtains information from the MessageService object and writes the log using Log4j. The code is as follows:

package net.petrikainulainen.gradle;
 
import org.apache.log4j.Logger;
 
public class HelloWorld {
 
    private static final Logger LOGGER = Logger.getLogger(HelloWorld.class);
 
    public static void main(String[] args) {
        MessageService messageService = new MessageService();
 
        String message = messageService.getMessage();
        LOGGER.info("Received message: " + message);
    }
}

Fourthly, in the src/main/resources directory, log4j.properties are used to configure log4j. The log4j.properties file is as follows:

log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.Stdout.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n
 
log4j.rootLogger=DEBUG,Stdout

That's all right. Let's see how to execute the test.

Execute tests

We can execute the test by following commands.

gradle test

When the test passes, we can see the following output:

> gradle test
:compileJava
:processResources
:classes
:compileTestJava
:processTestResources 
:testClasses
:test
 
BUILD SUCCESSFUL
 
Total time: 4.678 secs

However, if the test fails, we will see the following output:

> gradle test
:compileJava
:processResources
:classes
:compileTestJava
:processTestResources
:testClasses
:test
 
net.petrikainulainen.gradle.MessageServiceTest > getMessage_ShouldReturnMessageFAILED
    org.junit.ComparisonFailure at MessageServiceTest.java:22
 
1 test completed, 1 failed
:test FAILED
 
FAILURE: Build failed with an exception.
 
* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///Users/loke/Projects/Java/Blog/gradle-examples/dependency-management/build/reports/tests/index.html
 
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
 
BUILD FAILED
 
Total time: 4.461 secs

As we can see, if the unit test fails, the following information will be described in the output message:

  • Which test failed?
  • Several tests were executed, several of which failed.
  • The location of the test report, which provides additional information about failed (or successful) tests.

When we execute unit tests, Gradle creates test reports in the appropriate directory:

  • The build/test-results directory contains the raw data for each test execution.
  • The build/reports/tests directory contains an HTML report that describes the results of the test.

HTML test report is a very useful tool because it describes the reasons for test failure. For example, if our unit test considers that the getMessage() method in the MessageService class returns the string "Hello Worl1d!", then the HTML report looks like the following:

Let's move on. Here's how to package and run our demo program.

Packaging and Running Procedures

We can use either of the following command packagers: gradle assembly or gradle build, which create dependency-management.jar files in the build/libs directory.

When we run the demo using the java-jar dependency-management.jar command, we can see the following output:

> java -jar dependency-management.jar
 
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/Logger
    at net.petrikainulainen.gradle.HelloWorld.<clinit>(HelloWorld.java:10)
Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Logger
    at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 1 more

The reason for throwing the exception is that when we run the program, Log4j's dependencies are not found in the classpath.

The easiest way to solve this problem is to create a so-called "fat" Jar file, which packages all the dependencies needed to run the program into the Jar file.

By consulting Gradle Cookbook In the tutorial, you can modify the build script as follows:

apply plugin: 'java'
 
repositories {
    mavenCentral()
}
 
dependencies {
    compile 'log4j:log4j:1.2.17'
    testCompile 'junit:junit:4.11'
}
 
jar {
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    manifest {
        attributes 'Main-Class': 'net.petrikainulainen.gradle.HelloWorld'
    }
}

Now we can run the demo (packaged) and everything is OK:

> java -jar dependency-management.jar 
INFO  - HelloWorld                 - Received message: Hello World!

These are what we have learned today. Let's summarize what we have learned.

summary

This tutorial teaches us four aspects:

  • We learned to configure the warehouse needed to build.
  • We learned how to declare the required dependencies and the classification of dependencies (grouping).
  • We learned that Gradle creates an HTML test report after the test is executed.
  • We learned to create a so-called "fat" Jar file.

If you want to play with the demo program covered in this tutorial, you can use GitHub That acquisition.

About the author: JustinWu