Chapter 15. Packages and Packaging

15.0 Introduction

One of the better aspects of the Java language is that it has defined a very clear packaging mechanism for categorizing and managing its large API. Contrast this with most other languages, where symbols may be found in the C library itself or in any of dozens of other libraries, with no clearly defined naming conventions.1 APIs consist of one or more packages; packages consist of classes; classes consist of methods and fields. Anybody can create a package, with one important restriction: you or I cannot create a package whose name begins with the four letters java. Packages named java. or javax. are reserved for use by Oracle’s Java developers, under the management of the Java Community Process (JCP). When Java was new, there were about a dozen packages in a structure that is very much still with us, though it has quadrupled in size; some of these are shown in Table 15-1.

Table 15-1. Java packages basic structure
Name Function

java.awt

Graphical User Interface

java.io

Reading and writing

java.lang

Intrinsic classes (String, etc.)

java.lang.annotation

Library support for annotation processing

java.math

Math library

java.net

Networking (sockets)

java.nio

“New” I/O (not new anymore): Channel-based I/O

java.sql

Java database connectivity

java.text

Handling and formatting/parsing dates, numbers, messages

java.time

Java 8: Modern date/time API (JSR-311)

java.util

Utilities (collections, date)

java.util.regex

Regular Expressions

javax.naming

JNDI

javax.print

Support for printing

javax.script

Java 6: Scripting engines support

javax.swing

Modern Graphical User Interface

Many packages have been added over the years, but the initial structure has stood the test of time fairly well. In this chapter, I show you how to create and document your own packages, and then discuss a number of issues related to deploying your package in various ways on various platforms.

This chapter also covers the more traditional meaning of “packaging”, as in, creating a package of your program for others to use. This covers the Java Platform Modules System (JPMS) introduced in Java 9. We also cover jlink, a tool for creating a mini-Java distribution containing your application and only the parts of the JDK that you actually use. We do not yet cover the jpackage tool for packaging applications, because it’s not yet in the JDK - it may arrive with Java 14 or 15.

15.1 Creating a Package

Problem

You want to be able to import classes and/or organize your classes, so you want to create your own package.

Solution

Put a package statement at the front of each file, and recompile with -d or a build tool or IDE.

Discussion

The package statement must be the very first noncomment statement in your Java source file—preceding even import statements—and it must give the full name of the package. Package names are expected to start with your domain name backward; for example, my Internet domain is darwinsys.com, so most of my packages begin with com.darwinsys and a project name. The utility classes used in this book and meant for reuse are in one of the com.darwinsys packages listed in Recipe 1.6, and each source file begins with a statement, such as:

package com.darwinsys.util;

The demonstration classes in the JavaSrc repository do not follow this pattern; they are in packages with names related to the chapter they are in or the java.* package they relate to; for example, lang for basic Java stuff, structure for examples from the data structuring chapter (Chapter 7), threads for the threading chapter (Chapter 16), and so on. It is hoped that you will put them in a “real” package if you reuse them in your application!

Once you have package statements in place, be aware that the Java runtime, and even the compiler, will expect the compiled .class files to be found in their rightful place (i.e., in the subdirectory corresponding to the full name somewhere in your CLASSPATH settings). For example, the class file for com.darwinsys.util.FileIO must not be in the file FileIO.class in my CLASSPATH but must be in com/darwinsys/util/FileIO.class relative to one of the directories or archives in my CLASSPATH. Accordingly, if you are compiling with the command-line compiler, it is customary (almost mandatory) to use the -d command-line argument when compiling. This argument must be followed by the name of an existing directory (often . is used to signify the current directory) to specify where to build the directory tree. For example, to compile all the .java files in the current directory, and create the directory path under it (e.g., create ./com/darwinsys/util in the example):

javac -d . *.java

This creates the path (e.g., com/darwinsys/util/) relative to the current directory, and puts the class files into that subdirectory. This makes life easy for subsequent compilations, and also for creating archives, which is covered in Recipe 15.5.

Of course, if you use a build tool such as Maven (see Recipe 1.7), this will be done correctly by default (Maven), so you won’t have to remember to keep doing it!

Note that in all modern Java environments, classes that do not belong to a package (the “anonymous package”) cannot be listed in an import statement, although they can be referred to by other classes in that package. They also cannot become part of a JPMS module.

15.2 Documenting Classes with Javadoc

Problem

You have heard about this thing called “code reuse” and would like to promote it by allowing other developers to use your classes.

Solution

Use Javadoc. Write the comments when you write the code.

Discussion

Javadoc is one of the great inventions of the early Java years. Like so many good things, it was not wholly invented by the Java folks; earlier projects such as Knuth’s Literate Programming had combined source code and documentation in a single source file. But the Java folks did a good job on it and came along at the right time. Javadoc is to Java classes what “man pages” are to Unix, or what Windows Help is to Windows applications: it is a standard format that everybody expects to find and knows how to use. Learn it. Use it. Write it. Live long and prosper (well, perhaps that’s not guaranteed). But all that HTML documentation that you learned from writing Java code, the complete reference for the JDK—did you think they hired dozens of tech writers to produce it? Nay, that’s not the Java way. Java’s developers wrote the documentation comments as they went along, and when the release was made, they ran javadoc on all the zillions of public classes and generated the documentation bundle at the same time as the JDK. You can, should, and really must do the same when you are preparing classes for other developers to use.

All you have to do to use javadoc is to put special “javadoc comments” into your Java source files. These are similar to multiline Java comments, but begin with a slash and two stars, and end with the normal star-slash. Javadoc comments must appear immediately before the definition of the class, method, or field that they document; if placed elsewhere, they are ignored.

A series of keywords, prefixed by the at sign (@), can appear inside doc comments in certain contexts. Some are contained in braces (“{…}”). The keywords as of Java 8 are listed in Table 15-2.

Table 15-2. Javadoc keywords
Keyword Use

@author

Author name(s)

{@code text}

Displays text in code font without HTML interpretation

@deprecated

Causes deprecation warning

{@docroot}

Refers to the root of the generated documentation tree

@exception

Alias for @throws

{@inheritDoc}

Inherits documentation from nearest superclass/superinterface

@link

Generates inline link to another class or member

@linkplain

As @link but displays in plain text

{@literal text}

Displays text without interpretation

@param name description

Argument name and meaning (methods only)

@return

Return value

@see

Generate Cross-reference link to another class or member

@serial

Describes serializable field

@serialData

Describes order and types of data in serialized form

@serialField

Describes serializable field

@since

JDK version in which introduced (primarily for Sun use)

@throws

Exception class and conditions under which thrown

{@value [ref]}

Displays values of this or another constant field

@version

Version identifier

Example 15-1 is a somewhat contrived example that shows some common javadoc keywords in use. The output of running this through javadoc is shown in a browser in Figure 15-1.

Example 15-1. main/src/main/java/javadoc/JavadocDemo.java
public class JavadocDemo extends JPanel {

    private static final long serialVersionUID = 1L;

    /**
     * Construct the GUI
     * @throws java.lang.IllegalArgumentException if constructed on a Sunday.
     */
    public JavadocDemo() {
        // We create and add a pushbutton here,
        // but it doesn't do anything yet.
        Button b = new Button("Hello");
        add(b);                        // connect Button into component
        // Totally capricious example of what you should not do
        if (Calendar.getInstance().get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) {
            throw new IllegalArgumentException("Never On A Sunday");
        }
    }

    /** paint() is an AWT Component method, called when the
     *  component needs to be painted. This one just draws colored
     * boxes in the window.
     *
     * @param g A java.awt.Graphics that we use for all our
     * drawing methods.
     */
    public void paint(Graphics g) {
        int w = getSize().width, h = getSize().height;
        g.setColor(Color.YELLOW);
        g.fillRect(0, 0, w/2, h);
        g.setColor(Color.GREEN);
        g.fillRect(w/2, 0, w, h);
        g.setColor(Color.BLACK);
        g.drawString("Welcome to Java", 50, 50);
    }
}

The javadoc tool works fine for one class but really comes into its own when dealing with a package or collection of packages. You can provide a package summary file for each package, which will be incorporated into the generated files. Javadoc generates thoroughly interlinked and crosslinked documentation, just like that which accompanies the standard JDK. There are several command-line options; I normally use -author and -version to get it to include these items, and often -link to tell it where to find the standard JDK to link to.

Run javadoc -help for a complete list of options, or see the full documentation online at Oracle’s website. Figure 15-1 shows one view of the documentation that the class shown in Example 15-1 generates when run as:

$ javadoc -author -version JavadocDemo.java

If you run this with Java 9+, this will also include a fully functional search box, shown in the upper right of Figure 15-1. This is implemented in JavaScript so it should work in any modern browser.

jcb4 1501
Figure 15-1. Javadoc Opened In Browser

Be aware that quite a few files are generated, and one of the generated files will have the same name as each class, with the extension .html. If you happened to have an HTML file documenting the class, and generate javadoc in the source directory, the .html file is silently overwritten with the javadoc output. If you wish to avoid cluttering up your source directories with the generated files, the -d __directorypath option to JavaDoc++ allows you to place the generated files into the specified directory.

See Also

Javadoc has numerous other command-line arguments. If documentation is for your own use only and will not be distributed, you can use the -link option to tell it where your standard JDK documentation is installed so that links can be generated to standard Java classes (like String, Object, and so on). If documentation is to be distributed, you can omit -link or use -link with a URL to the appropriate Java API page on Oracle’s website. See the online tools documentation for all the command-line options.

The output that javadoc generates is fine for most purposes. It is possible to write your own Doclet class to make the javadoc program into a class documentation verifier, a Java-to-other-format (such as Java-to-RTF) documentation generator, or whatever you like. Those are actual examples; see the javadoc tools documentation that comes with the JDK for documents and examples, or go to Oracle’s website. Visit Doclet for a somewhat dated but useful collection of Doclets and other javadoc-based tools.

Javadoc Versus JavaHelp

Javadoc is for programmers using your classes; for a GUI application, end users will probably appreciate standard online help. This is the role of the Java Help API, which is not covered in this book but is fully explained in Creating Effective JavaHelp (O’Reilly), which every GUI application developer should read. JavaHelp is another useful specification that was somewhat left to coast during the Sun sellout to Oracle; it is now hosted on java.net at JavaHelp.

15.3 Beyond Javadoc: Annotations/Metadata

Problem

You want to generate not just documentation, but also other code artifacts, from your source code. You want to mark code for additional compiler verification.

Solution

Use the Java Annotations or “Metadata” facility.

Discussion

The continuing success of the open source tool XDoclet—originally used to generate the tedious auxiliary classes and deployment descriptor files for the widely criticized EJB2 framework—led to a demand for a similar mechanism in standard Java. Java Annotations were the result. The annotation mechanism uses an interface-like syntax, in which both declaration and use of Annotations use the name preceded by an at character (@). This was chosen, according to the designers, to be reminiscent of “Javadoc tags, a preexisting ad hoc annotation facility in the Java programming language.” Javadoc is ad hoc only in the sense that its @ tags were never fully integrated into the language; most were ignored by the compiler, but @deprecated was always understood by the compiler (see Recipe 1.9).

Annotations can be read at runtime by use of the Reflection API; this is discussed in Recipe 17.10, where I also show you how to define your own annotations. Annotations can also be read post–compile time by tools such as code generators (and others to be invented, perhaps by you, gentle reader!).

Annotations are also read by javac at compile time to provide extra information to the compiler.

For example, a common coding error is overloading a method when you mean to override it, by mistakenly using the wrong argument type. Consider overriding the equals method in Object. If you mistakenly write:

public boolean equals(MyClass obj) {
    ...
}

then you have created a new overload that will likely never be called, and the default version in Object will be called. To prevent this, an annotation included in java.lang is the Override annotation. This has no parameters but simply is placed before the method call. For example:

/**
 * AnnotationOverrideDemo - Simple demonstation of Metadata being used to
 * verify that a method does in fact override (not overload) a method
 * from the parent class. This class provides the method.
 */
abstract class Top {
    public abstract void myMethod(Object o);
}

/** Simple demonstation of Metadata being used to verify
 * that a method does in fact override (not overload) a method
 * from the parent class. This class is supposed to do the overriding,
 * but deliberately introduces an error to show how the modern compiler
 * behaves
 */
class Bottom {

    @Override
    public void myMethod(String s) {    // EXPECT COMPILE ERROR
        // Do something here...
    }
}

Attempting to compile this results in a compiler error that the method in question does not override a method, even though the annotation says it does; this is a fatal compile-time error:

C:> javac AnnotationOverrideDemo.java
AnnotationOverrideDemo.java:16: method does not override a method
            from its superclass
        @Override public void myMethod(String s) {     // EXPECT COMPILE ERROR
         ^
1 error
C:> 

15.4 Preparing a Class as a JavaBean

Problem

You have a class that you would like to use as a JavaBean.

Solution

Make sure the class meets the JavaBeans requirements; optionally: create a JAR file containing the class, a manifest, and any ancillary entries.

Discussion

Several kinds of Java components are called either Beans or JavaBeans:

  • Visual components for use in GUI builders, as discussed in this recipe.

  • Plain Old Java Objects (POJOs), or components meant for reuse.

  • Java Enterprise has “Enterprise JavaBeans” (EJBs), “JSP JavaBeans,” “JSF managed Beans,” and “CDI Beans,” containing features for building enterprise-scale applications. Creating and using Java EE components is more involved than regular JavaBeans and would take us very far afield, so they are not covered in this book. When you need to learn about enterprise functionality, turn to Java EE 7 Essentials by Arun Gupta (O’Reilly).

  • The Spring Framework also uses the term “Beans” (or “Spring Beans”) for the objects it manages.

What all these types of beans have in common are certain naming paradigms. All public properties should be accessible by get/set accessor methods. For a given property Prop of type Type, the following two methods should exist (note the capitalization):

public Type getProp( );
public void setProp(Type)

For example, the various AWT and Swing components that have textual labels all have the following pair of methods:

public String getText( );
public void setText(String newText);

One commonly permitted variance to this pattern is that, for boolean or Boolean arguments, the “get” method is usually called isProp() rather than getProp().

You should use this set/get design pattern (set/get methods) for methods that control a bean. Indeed, this technique is useful even in nonbean classes for regularity. The “bean containers” for the APIs listed at the start of this section, generally use Java introspection (see Chapter 17) to find the set/get method pairs, and some use these to construct properties editors for your bean. Bean-aware IDEs, for example, provide editors for all standard types (colors, fonts, labels, etc.). You can supplement this with a BeanInfo class to provide or override information.

The bare minimum a class requires to be usable as a JavaBean is the following:

  • The class must have a no-argument constructor.

  • The class should use the set/get paradigm.

  • The class must implement java.io.Serializable, although many containers don’t enforce this.

  • Depending on the intended use, the class file might need to be packaged into a JAR file (see Recipe 15.5).

Note that a JavaBean with no required inheritance or implements is also called a POJO, or “Plain Old Java Object.” Most new Java frameworks accept POJO components, instead of (as in days of yore) requiring inheritance (e.g., Struts 1 org.struts.Action class) or implementation of interfaces (e.g., EJB2 javax.ejb.SessionBean interface).

Here is a sample JavaBean that might have been a useful addition to one’s Java GUI toolbox, the LabelText widget. It combines a label and a one-line text field into a single unit, making it easier to compose GUI applications. A demo program in the online source directory sets up three LabelText widgets, as shown in Figure 15-2.

jcb4 1502
Figure 15-2. LabelText bean

The code for LabelText is shown in Example 15-2. Notice that it is serializable and uses the set/get paradigm for most of its public methods. Most of the public set/get methods simply delegate to the corresponding methods in the label or the text field. There isn’t really a lot to this bean, but it’s a good example of aggregation, in addition to being a good example of a bean.

Example 15-2. darwinsys-api/src/main/java/com/darwinsys/swingui/LabelText.java
// package com.darwinsys.swingui;
public class LabelText extends JPanel implements java.io.Serializable {

    private static final long serialVersionUID = -8343040707105763298L;
    /** The label component */
    protected JLabel theLabel;
    /** The text field component */
    protected JTextField theTextField;
    /** The font to use */
    protected Font myFont;

    /** Construct the object with no initial values.
     * To be usable as a JavaBean there must be a no-argument constructor.
     */
    public LabelText() {
        this("(LabelText)",  12);
    }

    /** Construct the object with the label and a default textfield size */
    public LabelText(String label) {
        this(label, 12);
    }

    /** Construct the object with given label and textfield size */
    public LabelText(String label, int numChars) {
        this(label, numChars, null);
    }

    /** Construct the object with given label, textfield size,
     * and "Extra" component
     * @param label The text to display
     * @param numChars The size of the text area
     * @param extra A third component such as a cancel button;
     * may be null, in which case only the label and textfield exist.
     */
    public LabelText(String label, int numChars, JComponent extra) {
        super();
        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
        theLabel = new JLabel(label);
        add(theLabel);
        theTextField = new JTextField(numChars);
        add(theTextField);
        if (extra != null) {
            add(extra);
        }
    }

    /** Get the label's horizontal alignment */
    public int getLabelAlignment() {
        return theLabel.getHorizontalAlignment();
    }

    /** Set the label's horizontal alignment */
    public void setLabelAlignment(int align) {
        theLabel.setHorizontalAlignment(align);
    }

    /** Get the text displayed in the text field */
    public String getText() {
        return theTextField.getText();
    }

    /** Set the text displayed in the text field */
    public void setText(String text) {
        theTextField.setText(text);
    }

    /** Get the text displayed in the label */
    public String getLabel() {
        return theLabel.getText();
    }

    /** Set the text displayed in the label */
    public void setLabel(String text) {
        theLabel.setText(text);
    }

    /** Set the font used in both subcomponents. */
    public void setFont(Font f) {
        // This class' constructors call to super() can trigger
        // calls to setFont() (from Swing.LookAndFeel.installColorsAndFont),
        // before we create our components, so work around this.
        if (theLabel != null)
            theLabel.setFont(f);
        if (theTextField != null)
            theTextField.setFont(f);
    }

    /** Adds the ActionListener to receive action events from the textfield */
    public void addActionListener(ActionListener l) {
        theTextField.addActionListener(l);
    }

    /** Remove an ActionListener from the textfield. */
    public void removeActionListener(ActionListener l) {
        theTextField.removeActionListener(l);
    }
}

Once it’s compiled, it’s ready to be packaged into a JAR. Most build tools such as Maven will do this work for you.

15.5 Archiving with jar

Problem

You want to create a Java archive (JAR) file from your package (or any other collection of files).

Solution

Use jar.

Discussion

The jar archiver is Java’s standard tool for building archives. Archives serve the same purpose as the program libraries that some other programming languages use. Java normally loads its standard classes from archives, a fact you can verify by running a simple Hello World program with the -verbose option:

java -verbose HelloWorld

Creating an archive is a simple process. The jar tool takes several command-line arguments: the most common are c for create, t for table of contents, and x for extract. The archive name is specified with -f and a filename. The options are followed by the files and directories to be archived. For example:

jar cvf /tmp/MyClasses.jar .

The dot at the end is important; it means “the current directory.” This command creates an archive of all files in the current directory and its subdirectories into the file /tmp/MyClasses.jar.

Most applications of JAR files depend on an extra file that is always present in a true JAR file, called a manifest. This file always lists the contents of the JAR and their attributes; you can add extra information into it. The attributes are in the form name: value, as used in email headers, properties files (see Recipe 7.10), and elsewhere. Some attributes are required by the application, whereas others are optional. For example, Recipe 15.6 discusses running a main program directly from a JAR; this requires a Main-Program header. You can even invent your own attributes, such as:

MySillyAttribute: true
MySillynessLevel: high (5'11")

You store this in a file called, say, manifest.stub,2 and pass it to jar with the -m switch. jar includes your attributes in the manifest file it creates:

jar -cv -m manifest.stub -f /tmp/com.darwinsys.util.jar .

The jar program and related tools add additional information to the manifest, including a listing of all the other files included in the archive.

Tip

If you use a tool like Maven (see Recipe 1.7), it will automatically create a JAR file from your source project just by saying mvn package.

15.6 Running a Program from a JAR

Problem

You want to distribute a single large file containing all the classes of your application and run the main program from within the JAR.

Solution

Create a JAR file with a Main-Class: line in the manifest; run the program with the java -jar option.

Discussion

The java command has a -jar option that tells it to run the main program found within a JAR file. In this case, it will also find classes it needs to load from within the same JAR file. How does it know which class to run? You must tell it. Create a one-line entry like this, noting that the attribute fields are case-sensitive and that the colon must be followed by a space:

Main-Class: com.somedomainhere.HelloWorld

Place that in a file called, say, manifest.stub, and assuming that you want to run the program HelloWorld from the given package. You can then use the following commands to package your app and run it from the JAR file:

C:> javac HelloWorld.java
C:> jar cvmf manifest.stub hello.jar HelloWorld.class
C:> java -jar hello.jar
Hello, World of Java
C:>

You can now copy the JAR file anywhere and run it the same way. You do not need to add it to your CLASSPATH or list the name of the main class.

On GUI platforms that support it, you can also launch this application by double-clicking the JAR file. This works at Mac OS X, Microsoft Windows, and many X Windows desktops.

In real life you would probably automate this with Maven, where your POM file would contain, among other things,

<project ...>
    ...
    <packaging>jar</packaging>
    ...
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <mainClass>${main.class}</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

With this in place, mvn package will build a runnable JAR file. However, if your class has external dependencies, the above steps will not package them, and you will get a missing class exception when you run it. For this, you need to use the Maven “assembly” plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive>
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <mainClass>${main.class}</mainClass>
                <!-- <manifestFile>manifest.stub</manifestFile> -->
            </manifest>
            <manifestEntries>
                <Vendor-URL>http://YOURDOMAIN.com/SOME_PATH/</Vendor-URL>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

Now, the invocation mvn package assembly:single will produce a runnable Jar with all dependencies. Note that your target folder will contain both foo-0.0.1-SNAPSHOT.jar and foo-0.0.1-SNAPSHOT-jar-with-dependencies.jar; the latter is the one you need.

There is a plan to add a new tool jpackage which will do much the same as assembly:single. This will probably appear in Java 14. If you have it, use it.

15.7 Packaging Web Tier Components into a WAR File

Problem

You have some web-tier resources and want to package them into a single file for deploying to the server.

Solution

Use jar to make a web archive (WAR) file. Or, as mentioned earlier, use Maven with packaging=war.

Discussion

Servlets are server-side components for use in web servers. They can be packaged for easy installation into a web server. A web application in the Servlet API specification is a collection of HTML and/or JSP pages, servlets, and other resources. A typical directory structure might include the following:

Project Root Directory
├── README.asciidoc
├── index.html - typical web pages
|── signup.jsp - ditto
├── WEB-INF Server directory
    ├── classes - Directory for individual .class files
    ├── lib    - Directory for Jar files needed by app
    └── web.xml - web app Descriptor ("Configuration file")

Once you have prepared the files in this way, you just package them up with a build tool. Using Maven, with <packaging>war</packaging>, your tree might look like this:

Project Root Directory
├── README.asciidoc
├── pom.xml
└── src
    └── main
        ├── java
        │   └── foo
        │       └── WebTierClass.java
        └── webapp
            ├── WEB-INF
            │   ├── classes
            │   ├── lib
            │   └── web.xml
            ├── index.html
            └── signup.jsp

Then mvn package will compile things, put them in place, and create the war file for you, leaving it under target. Gradle users would use a similar directory structure.

You could also invoke jar manually, though this has little to recommend it. You then deploy the resulting WAR file into your web server. For details on the deployment step, consult the documentation on the particular server you’re using.

See Also

For more information on signing and permissions, see Java Security by Scott Oaks (O’Reilly). For more information on the JDK tools mentioned here, see the documentation that accompanies the JDK you are using.

15.9 Using JPMS to Create a Module

Problem

You want your packaged archive to work smoothly with the Java modules system (JPMS).

Solution

Create a module-info.java file in the root of the source directory.

Discussion

The file module-info.java was introduced in Java 9 to provide the compiler and tools with information about your library’s needs and what it provides. Note that this is not even a valid Java class filename as it contains a minus sign. The module also has a group of pseudo-keywords, which only have their special meaning inside a module file. The simplest module-info is the following:

module foo {
    // Empty
}

But just as a Java class with no members won’t get you very far in the real world, neither will this empty module file. We need to provide some additional information. For this example, I will modularize my darwinsys-api, a collection of forty or so randomly-accumulated classes that I re-use sometimes. Remember that Jigsaw (the module system’s early name) as initially proposed as a way of modularizing the overgrown JDK itself. Most applications will need either the module java.base (which is always included). If they need AWT, Swing, or certain other desktop-application-related classes, they also need java.desktop. Thus I add the line

require java.desktop

into the module definition.

This code also has some JUnit-annotated classes and makes use of JavaMail API, so we need those as well. JUnit, however, is only needed at test time. While Maven offers scopes for compile, test, and runtime, the modules system does not. Thus we could omit JUnit from the POM file, and add it to Eclipse. But then maven test will not work.

And unfortunately, as of this writing, there does not appear to be modularized version of JavaMail either. Fortunately, there is a feature known as “automatic modules”, by which if you place a Jar file on the Module Path that doesn’t declare a module, its Jar file name will be used as the basis of an automatically-generated module. So we’ll also add:

requires mail;

Unfortunately, when we compile, Maven’s Java Compiler module spits out this scary-looking warning:

[WARNING] ********************************************************************************************************************
[WARNING] * Required filename-based automodules detected. Please don't publish this project to a public artifact repository! *
[WARNING] ********************************************************************************************************************

Given that there are so many public Java API libraries out there, and that most of them depend on other libraries in turn, I wonder: how is that state supposed to end? Nontheless, I have heeded that warning, and so people will continue to use the auto-module version of com.darwinsys.api until I stumble across modularized JavaMail and JUnit4 APIs.

The module-info also lists any packages that your module desires to make available, i.e., its public API. So we need a series of export commands:

exports com.darwinsys.calendar;
exports com.darwinsys.csv;
exports com.darwinsys.database;
...

By default, packages that are exported can not be examined using the Reflection API. To allow a module to introspect (use the Reflection API) on another, say, a domain model used with JPA, use opens.

One of the points of Java interfaces is to allow multiple implementations of a service. This is supported in JPMS by the “service” feature. Where an API is defined as one or more interfaces in one module, and multiple implementations are provided, each in its own module, the implementation module(s) can define an implementation using “provides with”, as in:

requires com.foo.interfacemodule;
provides com.foo.interfacemodule.Interface with com.foo.implmodule.ImplClass;

The completed module info for the darwinsys-api module is show in Example 15-3.

Example 15-3. DarwinSys-API Module-info
module com.darwinsys.api {

    requires java.desktop;
    requires java.persistence;
    requires java.prefs;
    requires java.sql;
    requires java.sql.rowset;
    requires javax.servlet.api;
    requires mail;
    requires junit;

    exports com.darwinsys.calendar;
    exports com.darwinsys.csv;
    exports com.darwinsys.database;
    exports com.darwinsys.diff;
    exports com.darwinsys.formatting;
    exports com.darwinsys.locks;
    provides com.darwinsys.locks.LockManager
        with com.darwinsys.locks.LockManagerImpl;
    exports com.darwinsys.model;
    opens com.darwinsys.model;
    // another dozen and a half packages...

}

A module wanting to use the lock interface feature would need a requires com.darwinsys and might do something like this in code:

import java.util.ServiceLoader;
import java.util.Optional;

Optional<LockManager> opt = ServiceLoader.load(LockManager.class).findFirst();
if (!opt.isPresent()) {
    throw new RuntimeException("Could not find implementation of LockManager");
}
LockManager mgr = opt.get();

The Optional interface is described in Recipe 8.5.

See Also

JPMS is relatively new, and library providers are still learning to use it properly. An early posting was https://openjdk.java.net/projects/jigsaw/quick-start. A plan for migrating to modules appears at http://tutorials.jenkov.com/java/modules.html#migrating-to-java-9. A discussion of preparing a multi-module Maven application is at https://www.baeldung.com/maven-multi-module-project-java-jpms. The book Java 9 Modularity: Patterns and Practices for Developing Maintainable Applications by Sander Mak and Paul Bakker (O’Reilly 2017) is probably the most comprehensive treatment of JPMS.

1 This is not strictly true. On Unix in C, at least, there is a distinction between normal include files and those in the sys subdirectory, and many structures have names beginning with one or two letters and an underscore, like pw_name, pw_passwd, pw_home, and so on in the password structure. But this is nowhere near as consistent as Java’s java.* naming conventions.

2 Some people like to use names like MyPackage.mf so that it’s clear which package it is for; the extension .mf is arbitrary, but it’s a good convention for identifying manifest files.

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

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