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.
Name | Function |
---|---|
|
Graphical User Interface |
|
Reading and writing |
|
Intrinsic classes (String, etc.) |
|
Library support for annotation processing |
|
Math library |
|
Networking (sockets) |
|
“New” I/O (not new anymore): Channel-based I/O |
|
Java database connectivity |
|
Handling and formatting/parsing dates, numbers, messages |
|
Java 8: Modern date/time API (JSR-311) |
|
Utilities (collections, date) |
|
Regular Expressions |
|
JNDI |
|
Support for printing |
|
Java 6: Scripting engines support |
|
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.
You want to be able to import classes and/or organize your classes, so you want to create your own package.
Put a package
statement at the front of each file, and recompile with -d
or a build tool or IDE.
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.
You have heard about this thing called “code reuse” and would like to promote it by allowing other developers to use your classes.
Use Javadoc. Write the comments when you write the code.
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.
Keyword | Use |
---|---|
|
Author name(s) |
|
Displays text in code font without HTML interpretation |
|
Causes deprecation warning |
|
Refers to the root of the generated documentation tree |
|
Alias for |
|
Inherits documentation from nearest superclass/superinterface |
|
Generates inline link to another class or member |
|
As |
|
Displays text without interpretation |
|
Argument name and meaning (methods only) |
|
Return value |
|
Generate Cross-reference link to another class or member |
|
Describes serializable field |
|
Describes order and types of data in serialized form |
|
Describes serializable field |
|
JDK version in which introduced (primarily for Sun use) |
|
Exception class and conditions under which thrown |
|
Displays values of this or another constant field |
|
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.
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.
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.
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 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.
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.
Use the Java Annotations or “Metadata” facility.
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:>
You have a class that you would like to use as a JavaBean.
Make sure the class meets the JavaBeans requirements; optionally: create a JAR file containing the class, a manifest, and any ancillary entries.
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 implement
s 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.
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.
// 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.
You want to create a Java archive (JAR) file from your package (or any other collection of files).
Use jar.
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.
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
.
You want to distribute a single large file containing all the classes of your application and run the main program from within the JAR.
Create a JAR file with a Main-Class
: line in the manifest; run the program with the java -jar
option.
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.
You have some web-tier resources and want to package them into a single file for deploying to the server.
Use jar to make a web archive (WAR) file. Or, as mentioned earlier, use Maven with packaging=war
.
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.
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.
You are distributing your application to end users, and want to minimize the size of your download.
Modularize your application (see Recipe 15.9,
use jdeps
to get a complete list of the modules it uses,
then use jlink
to create the mini-java,
and distribute that to your users.
jlink
is a command-line tool introduced in Java 9 that can make
up a “mini-Java” distribution containing only your application and
the JDK classes it uses. That is, it omits any of the thousands of
JDK classes that your app will never use.
First, you need to compile and package your module-info and your application code. You can use maven or gradle, or just use the JDK tools directly:
$ javac -d . src/*.java $ jar cvf demo.jar module-info.class demo
If you wish to see the list of modules that will be included, you can
optionally run the jdeps
tool to get this list.
$ jdeps --module-path . demo.jar demo [file:///Users/ian/workspace/javasrc/jlink/./] requires mandated java.base (@11.0.2) demo -> java.base demo -> java.io java.base demo -> java.lang java.base
Once the classes have been compiled, you can run the jlink
tool to build a
mini-java distribution with your demo app imbedded:
jlink --module-path . --no-header-files --no-man-pages --compress=2 --strip-debug --launcher rundemo=demo/demo.Hello --add-modules demo --output mini-java
The --launcher name=module/main
argument asks jlink to create
a script file named name to run the application.
If you got no errors, you should be able to run it either with the java
command
or with the generated shell script:
$ mini-java/bin/java demo.Hello Hello, world. $ mini-java/bin/rundemo Hello, world. $
You might want to copy the entire mini-java folder to a machine that doesn’t have a regular Java installation, and run it there, to be sure you don’t have any missing dependencies.
The concept of a mini-distribution is appealing, but you must consider these issues:
There is no upgrade mechanism for such mini-Javas. These are quite suitable for e.g., micro-service deployments where you rebuild often. For applications shipped to customers, though, you’d have to regenerate them and get your customers to download and re-install, and on short notice whenever there’s a security update.
Disk space is generally no longer expensive relative to the cost of your time in maintaining such a distribution.
Thus, you have to decide if this is worthwhile for your application.
You want your packaged archive to work smoothly with the Java modules system (JPMS).
Create a module-info.java file in the root of the source directory.
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 scope
s 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.
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
;
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.
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.
3.145.166.149