Chapter 11. Build Tools and IDEs

In this book, we have been working with java and javac directly on a command line. This is not how most applications are built today. Most projects are built using tools such as Maven or Gradle. These build tools can take care of concerns such as managing the classpath during compilation, dependency management, and building artifacts such as JAR files. On top of that, most developers use an IDE such as Eclipse, IntelliJ IDEA, or NetBeans. IDEs make development easier by providing features such as code completion, error highlighting, refactoring, and code navigation.

Both build tools and IDEs need to know what types are available in a given context. Tools typically interact with the classpath to accomplish this. This significantly changes with the introduction of the Java module system. The classpath is no longer the (only) mechanism that controls which types are available. Tools now have to consider the module path as well. Moreover, there might be a mix of explicit modules, the classpath, and automatic modules. At the time of writing, the tool ecosystem is still working hard on Java 9 support. This chapter introduces some of the available tools, and discusses how they support the Java module system or likely will in the near future.

Apache Maven

Building a single module project with Maven is trivial. We will go over the steps to do this now, but we will not present the code or configuration. An example is included in the GitHub repository if you want to try it out: ➥ chapter11/single-module.

Place module-info.java in the project’s src/main/java directory, and Maven will set up the compiler correctly to use the module source path. Dependencies are always put on the module path, even when the dependency isn’t modularized yet. This means dependencies that are not modules yet are always handled as automatic modules. This is different from what we did in Chapter 8 and Chapter 9, where we used a mix of classpath and module path. Both approaches are fine, although putting everything on the module path might hide some future problems. Besides the fact that the output of the project is now a modular JAR, there’s really not much else to see. Maven takes care of this nicely.

Although there’s not a lot to see on the surface, a lot is happening internally. Apache Maven now has to take the rules of the Java module system into account. The most important changes made to Apache Maven for support of the Java module system are as follows:

  • Uses the module path during compilation

  • Supports a mix of explicit modules and automatic modules as dependencies

Interestingly enough, the list doesn’t include anything about integrating the POM with module-info.java, although there is clearly a relationship between dependencies in a POM and requires in module-info.java. It’s not as strange as it might first look. Think about it this way: Apache Maven configures only the module path and classpath. The Java compiler takes this input and uses it to compile sources (including module-info.java). Apache Maven replaces the shell scripts we have been using in this book; it doesn’t replace the Java compiler. Clearly, we need both, but why doesn’t Maven generate a module-info.java for us? This has a lot to do with naming of modules.

There are three names in play:

  • The module’s name defined in module-info.java

  • The Maven project name defined in pom.xml

  • The name of the JAR file generated by Maven

We will use the module name when we reference to the module from other module-info.java files—for example, to require the module. The Maven name is used when adding a dependency to the module on the Maven level, in pom.xml. Finally, the JAR file generated by the Maven build is what will be shipped for deployment.

In Maven, a module name, also known as the Maven coordinate, has three parts: groupId : artifactId : version. The groupId is used for namespacing. Many projects of multiple modules exist, and the groupId logically brings them together. Usually the groupId is the reverse domain name of the project. The artifactId is the name of the module. Unfortunately, different projects use different naming strategies. Sometimes the project name is included in the artifactId, sometimes not. Finally, a Maven module is versioned.

A module in the Java module system doesn’t have a groupId, and doesn’t use version information. For public modules, it is recommended to include the reverse domain name of the project in the module name. In any case, the Maven module name, the Java module system module name, and the Maven artifact name will likely be somewhat related, but not the same. Figure 11-1 describes this.

Module and Maven artifact naming
Figure 11-1. Artifact naming

Adding dependencies also requires a two-step approach. First, a dependency needs to be added to the Maven artifact that represents the module, using its Apache Maven coordinates in the form of groupname:artifactname:version. This isn’t any different than it was for Apache Maven in a pre–Java module system world. Second, the dependency needs to be added as a requires statement in module-info.java to make the types exported by that module available to your code. If you didn’t add the dependency in the POM file, the compiler would fail on the requires statement because the module couldn’t be found. If you didn’t add the dependency to module-info.java, the dependency would remain effectively unused.

The fact that we reference the dependency in two places also makes it possible that the name of a module isn’t necessarily the same as the Apache Maven group:artifact:version coordinate. A dependency may or may not be an explicit module. If no module-info.class is found, the dependency becomes an automatic module. This is transparent to the user: there is no difference in using an explicit module or automatic module from the perspective of an Apache Maven user.

We will look at a complete code example of a multimodule project in the next section.

Multimodule Projects

Before the Java module system, it was already common practice to create multimodule projects with Apache Maven. Even without the much stronger constraints that the Java module system brings, this is a great start for modular projects. Each module in a multimodule project has its own POM, a Maven-specific build descriptor in XML format. In the POM, the module’s dependencies are configured. This includes dependencies to external libraries as well as dependencies to other modules in the project. Every type used in a module must be part of the module itself, the JDK, or an explicitly configured dependency. Conceptually, this is not very different from what you’ve seen with the Java module system.

Although multimodule projects are common with Apache Maven, you might wonder what a module exactly is in a pre–Java 9 world. The representation of a module is a JAR file, which is the common artifact produced by Apache Maven. At compile-time, Maven configures the classpath such that it contains only the JAR files that are configured as dependencies. This way, it can emulate similar behavior as what the Java module system enforces by using requires in a module descriptor.

Without the Java module system, Apache Maven doesn’t support strong encapsulation of packages. If a dependency is configured to another module, every type in that module can be read.

EasyText with Apache Maven

This section walks through migrating EasyText to Maven, the example application that we introduced in Chapter 3. The code itself is unchanged and will not be listed here.

First of all, the EasyText directory structure is changed to conform with the standard Apache Maven directory structure. Each module has its own directory, with an src/main/java directory that contains the module’s source files (including module-info.java), and pom.xml at the root of the module. Also note the pom.xml at the root of the project. This is a parent POM that makes it possible to compile all the modules with a single command. Here’s the directory structure:

├── algorithm.api
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
├── algorithm.coleman
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
├── algorithm.kincaid
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
├── algorithm.naivesyllablecounter
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
├── algorithm.nextgensyllablecounter
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
├── cli
│   ├── pom.xml
│   └── src
│       └── main
│           ├── java
│           └── resources
├── gui
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
└── pom.xml

The parent POM contains references to its subprojects, the actual modules. It also configures the compiler plug-in to use Java 9. Example 11-1 is a snippet with the most interesting parts of the pom.xml file.

Example 11-1. pom.xml (➥ chapter11/multi-module)
  <modules>
    <module>algorithm.api</module>
    <module>algorithm.coleman</module>
    <module>algorithm.kincaid</module>
    <module>algorithm.naivesyllablecounter</module>
    <module>algorithm.nextgensyllablecounter</module>
    <module>gui</module>
    <module>cli</module>
  </modules>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.6.1</version>
          <configuration>
            <release>9</release>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>

Note that the modules section isn’t new and isn’t directly related to the Java module system. The modules themselves all have their own pom.xml file as well. In it, the module’s Apache Maven coordinates are specified (group name, artifact name, and version), as well as its dependencies. Let’s take a look at two of them, easytext.algorithm.api and easytext.algorithm.kincaid. The API module doesn’t have any dependencies, so its pom.xml is straightforward, as shown in Example 11-2.

Example 11-2. pom.xml (➥ chapter11/multi-module/algorithm.api)
  <parent>
    <groupId>easytext</groupId>
    <artifactId>parent</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <artifactId>algorithm.api</artifactId>

  <name>Algorithm API</name>

In its module-info.java, the module is defined as in Example 11-3.

Example 11-3. module-info.java (➥ chapter11/multi-module/algorithm.api)
module easytext.algorithm.api {

   exports javamodularity.easytext.algorithm.api;

}

Note that technically the group/artifact names are not tied to the module name specified in the module’s module-info.java. They could be completely different. In any case, just remember that when you’re creating a dependency in a pom.xml file, you will need to use the Apache Maven coordinates. When you require a module in module-info.java, you use the name specified in the other module’s module-info.java; the Apache Maven coordinates don’t play any role on that level. Moreover, also notice that the directory name is not required to be the same as the module name.

Running a build will produce target/algorithm.api-1.0-SNAPSHOT.jar.

Now let’s move on to a module that uses the easytext.algorithm.api. In this case, we will have to add a dependency in the module’s pom.xml, and add a requires statement in its module-info.java, as shown in Example 11-4.

Example 11-4. pom.xml (➥ chapter11/multi-module/algorithm.kincaid)
  <groupId>easytext</groupId>
  <artifactId>algorithm.kincaid</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>algorithm.kincaid</name>

  <parent>
    <groupId>easytext</groupId>
    <artifactId>parent</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <dependencies>
    <dependency>
        <groupId>easytext</groupId>
        <artifactId>algorithm.api</artifactId>
        <version>${project.version}</version>
    </dependency>
  </dependencies>
</project>

In module-info.java, we see the expected requires, as shown in Example 11-5.

Example 11-5. module-info.java (➥ chapter11/multi-module/algorithm.kincaid)
module easytext.algorithm.kincaid {

   requires easytext.algorithm.api;

   provides javamodularity.easytext.algorithm.api.Analyzer
       with javamodularity.easytext.algorithm.kincaid.KincaidAnalyzer;

   uses javamodularity.easytext.algorithm.api.SyllableCounter;
}

Removing either the dependency in pom.xml or the requires in module-info.java results in a compilation error. The exact reasons are subtly different. Removing the dependency from pom.xml results in the following error:

module not found: easytext.algorithm.api

Removing the requires statement results in the following error:

package javamodularity.easytext.algorithm.api is not visible

As discussed previously, Apache Maven configures only the module path. An Apache Maven dependency works at a different level than a requires statement in module-info.java.

If you’ve used Apache Maven before, the pom.xml files should look familiar. No new syntax or configuration is required in Apache Maven when it comes to working with the Java module system.

This example shows that a properly modularized Apache Maven application is easy to migrate to the Java module system. Essentially, the addition of module-info.java files is all that’s required. In return, we get encapsulation and a much stronger run-time model.

Running a Modular Application with Apache Maven

We have our example project configured to be built by Apache Maven, but how do we run it? Maven is only a build tool and doesn’t have a role at run-time. Maven builds the artifacts we want to run, but in the end we still have to configure the Java runtime to run with the correct module path and/or classpath.

Configuring the module path manually is a lot easier than the classpath, but it would still be duplicate work because the information is in the pom.xml files already. Maven has an exec plug-in that helps with this process. Remember that this configures only the module path; it doesn’t have a run-time presence. The module path will be configured based on the dependencies listed in pom.xml. We need to configure the plug-in only with a module and main class to execute. Example 11-6 provides the configuration for the CLI module.

Example 11-6. pom.xml (➥ chapter11/multi-module/algorithm.cli)
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.6.0</version>
        <executions>
          <execution>
            <goals>
              <goal>exec</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <executable>${JAVA_HOME}/bin/java</executable>
          <arguments>
            <argument>--module-path</argument>
            <modulepath/>
            <argument>--module</argument>
            <argument>easytext.cli/javamodularity.easytext.cli.Main</argument>
            <argument>${easytext.file}</argument>
          </arguments>
        </configuration>
      </plugin>
    </plugins>
  </build>

We can execute the plug-in by using the exec command to start the application:

mvn exec:exec

Gradle

At the time of writing, unfortunately, Gradle has no official Java module system support yet. Support is expected and will probably be similar to Maven’s. When it comes to modularizing code, Gradle is already in excellent shape. Support for multimodule projects is good, which is a great start to prepare for the Java module system.

IDEs

IDEs such as IntelliJ, Eclipse, and NetBeans all have support for the Java module system, even before the official release of Java 9. The most important feature for an IDE to support the Java module system is understanding requires and exports in module-info.java files. These keywords control what types are available to a module, and an IDE should use this for syntax completion, pointing out errors, and suggestions for module dependencies. All three IDEs support this. This is closely related to the way Java modules map to projects, workspaces, and modules in IDEs. Each IDE has its own structure, and Java modules have to be mapped to this structure.

In Eclipse, each project represents a module, assuming it contains a module-info.java. As always, projects are grouped in a workspace. Both IntelliJ and NetBeans already had their own concept of a module, and this now maps directly to Java module system modules.

Editing of module-info.java files is also supported by all three IDEs. This includes error highlighting and syntax completion on module names. Some IDEs even support showing a visual module graph based on a module descriptor.

Although this should be mostly transparent to users, clearly some duplication exists in IDEs when it comes to managing project structure. The IDEs have their own internal representation of modules (or projects, in the case of Eclipse). In the past, this model could be synchronized with an external model such as Maven or Gradle. The Java module system model is now a third level of module representation. Although the tools will hide this fact, it can still become somewhat confusing when you dig deeper. Figure 11-2 explains how both Maven and module-info.java are used to configure a project within the IDE. The same will likely be true for Gradle in the future.

Configuring visibility in the IDE.
Figure 11-2. Configuring visibility in the IDE

In future releases of tools, we can expect to see much better support for refactoring, hints, and migration to modules.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset