2018/10/13

Java SE 11 not mentioned in JEP

このエントリーをはてなブックマークに追加

Java SE 11 was release on 25th Sep.

Features of Java SE are proposed in JEP (JDK enhancement proposal), and Java SE 11 includes 17 JEPs.

However, there are many small updates not mentioned in JEP. So, I summarized these small updates, especially java.base module.

I excepted security features, because I don't have enough knowledge about security.

 

Removed API

Java is the language that give weight to compatibility, however Java compatibility policy has been changed since Java SE 9.

In fact, there are many APIs removed in Java SE 11.

Module

The biggest incompatibility of Java SE 11 is to remove modules about Java EE. This was proposed in JEP 320.

These modules were deprecated in Java SE 9, but we can still use them in Java SE 9 and 10.

However, the modules were removed finally in Java SE 11!

The removed modules are following:

  • java.se.ee
  • java.activation (JAF)
  • java.corba
  • java.transaction (JTA)
  • java.xml.bind (JAXB)
  • java.xml.ws (JAX-WS)
  • java.xml.ws.annotation (JAX-WS)

You can use JAF, JTA, JAXB and JAX-WS of Java EE (Jakarta EE). These modules are also registered in Maven central reposity. I wrote the article how to use JAXB and JAF in Maven project.

No alternative about CORBA, but I think no one uses CORBA any more.

In addition, JavaFX modules in Oracle JDK were removed (OpenJDK doesn't include JavaFX modules.)

  • javafx.base
  • javafx.graphics
  • javafx.controls
  • javafx.fxml
  • javafx.media
  • javafx.web
  • javafx.swing

JavaFX is developed by OpenJFX project in OpenJDK. JavaFX 11 is also released before Java SE 11.

New JavaFX Site was opened at the same time as JavaFX 11 release. You can download JavaFX 11 and refere Javadoc of JavaFX 11 at the site.

Class

There were many changes about security API. Following class was removed:

  • java.security.Policy

Method

forRemovel atribute of @Deprecated annotation was introduced in Java SE 9. Type of forRemoval is boolean, and true means that target API will be removed.

In Java SE 11, following methods that forRemoval value were true were removed:

  • java.lang.Runtime.runFinalizersOnExit(boolean)
  • java.lang.SecurityManager.checkAwtEventQueueAccess()
  • java.lang.SecurityManager.checkMemberAccess(java.lang.Class, int)
  • java.lang.SecurityManager.checkSystemClipboardAccess()
  • java.lang.SecurityManager.checkTopLevelWindow(java.lang.Object)
  • java.lang.System.runFinalizersOnExit(boolean)
  • java.lang.Thread.destroy()
  • java.lang.Thread.stop(java.lang.Throwable)

All of removed methods were that forRemoval value were true in Java SE 9.

APIs proposed for Removal

Follwoing APIs were proposed for removal in Java SE 11.

Class

  • java.util.jar.Pack200

Interface

  • java.util.jar.Pack200.Packer
  • java.util.jar.Pack200.Unpacker

Pack200 class was proposed by JEP 336.

All of deprecated API are listed in Deprecated in Javadoc.

 

Added APIs

java.io package

ByteArrayOutputStream class

  • void writeBytes(byte[] b)

This method is equivalent to write(b, 0, b.lenght).

FileReader class

Two constructors are introduced.

  • FileReader(java.io.File file, java.nio.charset.Charset charset)
  • FileReader(java.lang.String fileName, java.nio.charset.Charset charset)

Finally, we can specify charset to create FileReader object!

FileWriter class

Four constructers with charset are also introduced as FileReader.

  • FileWriter(java.io.File file, java.nio.charset.Charset charset)
  • FileWriter(java.io.File file, java.nio.charset.Charset charset, boolean append)
  • FileWriter(java.lang.String fileName, java.nio.charset.Charset charset)
  • FileWriter(java.lang.String fileName, java.nio.charset.Charset charset, boolean append)

InputStream class

  • static InputStream nullInputStream()
  • byte[] readNBytes(int len)

nullInputStream method is a factory method to create InputStream object that reads no bytes.

readNBytes method was introduced in Java SE 9. In Java SE 11, overload method is also introduced.

You need to specify byte array for readNBytes in Java SE 9, but no need byte array argument in Java SE 11. You only specigy lenght of read bytes.

However, I think the implementation is not so efficient at the present. Therefore, if reading data repeatedly in a loop, you should use a buffer byte array instead of readNBytes in Java SE 11. If reading all data at once, readAllbytes method is suitable.

OutputStream class

  • static OutputStream nullOutputStream()

nullOutputStream method is a factory method to create OutputStream object that writes no bytes as InputStream.

Reader class

  • static Reader nullReader()

Writer class

  • static Writer nullWriter()

java.lang package

CharSequence interface

  • static int compare(java.lang.CharSequence cs1, java.lang.CharSequence cs2)

compare method is a static method to compare two arguments.

I think it is a simple delegation method, but the implementation of compare methods compares two arguments character one by one.

Character class

  • static String toString(int codePoint)

toString method to create String object from char argument is existed, but no int argument until Java SE 11. It means we can use a codepoint of Unicode to create String object.

Character.UnicodeBlock class

Java SE 11 enable to use Unicode 10. With this update, 18 constans of Unicode block are added.

Character.UnicodeScript enum

With introducing Unicode 10, 10 script name of Unicode are added.

Class class

  • Class<?> getNestHost()

getNestHost method returns host class object of nested class. When class is non-nest, array, or primitive, it returns self class object.

  • Class<?>[] getNestMembers()

getNestMembers returns nested members.

  • boolean isNestmateOf(Class<?> c)

Comparing to the host class of argument Class object. When argument is array or primitive type, it returns false.

These three methods were porposed by JEP 181 Nest-Based Access Control.

String class

  • boolean isBlank()

isBlank return true when string is empty or only white space.

  • Stream<String> lines()

lines method divides string by line break, and makes Stream object.

  • String repeat(int count)

repeat method makes a concatenated string repeatedly by count argument value. For example, "a".repeat(3) returs "aaa".

  • String strip()
  • String stripLeading()
  • String stripTailing()

These three methods remove all white spaces of leading and trailing.

strip method removes leading and trailing white spaces, stripLeading method removes only leading, and stripTailing method removes only tailiing.

strip and stripLeading methods are similar to trim method. But, trim method removes only Latin-1 white space.

To remove non-Latin-1 white space such as U+3000, you should use strip/stripLeading/stripTailing methods.

StringBuffer class

StringBuilder class

These two classes now implement Comparable interface. Therefore, we can use compareTo method of these classes.

java.lang.invoke package

A class was added to java.lang.invoke package.

  • ConstantBootstraps class

boostrap of this class name means bootstrap method for InvokeDynamic bytecode. InvokeDynamic define how to process dynamically, and bootstrap is used for this purpose. InvokeDynamic is used for dynamic type JVM language such as JRuby and Lambda expression.

Java SE 11 introduced new bytecode, that is CONSTANT_Dynamic. CONSTANT_Dynamic is used for constant initialization, on the other hand InvokeDynamic is for method invoking.

CONSTANT_Dynamic also uses bootstrap method for defining constant initialization.

ConstantBootstraps is a utility class for bootstrap of CONSTANT_Dynamic.

By the way, javac of Java SE 11 doesn't use CONSTANT_Dynamic. In the future, javac will make class file including CONSTANT_Dynamic.

java.lang.ref package

Reference class

  • Object clone()

The reason clone method is overridden is to indicate Reference class can't clone. Therefore, the clone method always throws CloneNotSupportedException.

But, I think root cause is that clone method is defined in Object class. Although the root cause is clear, the cause can't be fixed anymore. So, clone method in Reference class is work-around.

java.nio package

ByteBuffer class

  • int mismatch(ByteBuffer that)

CharBuffer class

  • int mismatch(CharBuffer that)

DoubleBuffer class

  • int mismatch(DoubleBuffer that)

FloatBuffer class

  • int mismatch(FloatBuffer that)

IntBuffer class

  • int mismatch(IntBuffer that)

LongBuffer class

  • int mismatch(LongBuffer that)

ShortBuffer class

  • int mismatch(ShortBuffer that)

mismatch method compares this buffer and argument buffer, and returns the relative index that two buffers are diifferent. If the buffers are equal, it returns -1.

The relative means that mismatch method scans from the position to the limit of buffers.

Even if the first absolute mismatch index is before the position, mismatch method doesn't check it.

java.nio.chanels package

SelectionKey class

  • int interestOpsAnd(int ops)
  • int interestOpsOr(int ops)

SelectionKey class is used for non-blocking socket communication, and defines some constans of operations.

These constants are expressed a bit, and can do bit operations such as AND or OR. For example, OP_READ is 0b0001, and OP_WRITE is 0b0100.

We can get/set the present operation value by interestOps method.

Above two methods are combination methods of interestOps and AND or OR operation.

key.interestOpsAnd(ops) is equivalent to key.interestOps(key.interestOps() & ops). At the same way, key.interestOpsOr(ops) is same as key.interestOps(key.interestOps() | ops).

Selector class

Selector class is also used for non-blocking communitation. Now, we can describe the non-blocking process by lambda expression.

  • int select(java.util.function.Consumer<SelectionKey> action)
  • int select(java.util.function.Consumer<SelectionKey> action, long timeout)
  • int selectNow(java.util.function.Consumer<SelectionKey> action)

Until Java SE 11, we get SelectionKey object by selectedKeys method, and handle it according to the operation of SelectionKey object.

Now, we can describe like following:

    selector.select(key -> {
        try {
            if (key.isAcceptable()) {
                // OP_ACCEPT case
                ServerSocketChannel serverSocket = (ServerSocketChannel) key.channel();
                // change to Non-Blocking mode
                serverSocket.configureBlocking(false);
                // register to Selector
                serverSocket.register(selector, SelectionKey.OP_READ);
            } else if (key.isReadable()) {
                // OP_READ case
                SocketChannel socket = (SocketChannel) key.channel();
                // read contents...
            }
        } catch (IOException ex) {
            // exception handling
        }
    });

I don't like exception handling in lambda expression, but description is much easier than boefore.

java.nio.file package

Path interface

static Path of(java.lang.String first, java.lang.String... more)

static Path of(java.net.URI uri)

Two factory methods were added to Path interface.

The former factory method is equivalent to FileSystems.getDefault().getPath(first, more). It means the former factory method deal with phisical file system.

The latter factory method returns Path object of default phisical file system if schema of URI is "file". If not, it checks whether schema of URI is possible to deal with or not. If possible, the factory method calls getPath(uri) method of FileSystemProvider class.

Files class

  • static String readString(java.nio.file.Path path)
  • static String readString(java.nio.file.Path path, java.nio.charset.Charset cs)
  • static Path writeString(java.nio.file.Path path, java.lang.CharSequence csq, java.nio.file.OpenOption... options)
  • static Path writeString(java.nio.file.Path path, java.lang.CharSequence csq, java.nio.charset.Charset cs, java.nio.file.OpenOption... options)

Files class defines readAllLines method to read the contents at one time, but type of return value is List<String>. On the other hand, readString methods returns a String object of file contes including line break code.

In the same way, writeString writes a CharSequence object to path at one time.

java.util package

Collection interface

  • <T> T[] toArray(IntFunction<T[]> generator)

Collection intterface added an overload of toArray method. Argument of new toArray method is a generator function to allocate the returned array.

Optional class

OptionalDouble class

OptionalInt class

OptionalLong class

  • boolean isEmpty()

isEmpty method is reverse of isPresent method.

java.tuil.concurrent package

TimeUnit enum

long convert(java.time.Duration duration)

convert method converts duration to time unit. For example, TimeUnit.MILLISECONDS.convert(Duration.ofMinutes(1L)) returns 60000.

java.util.function package

Predicate interface

  • <T> Predicate<T> not(Predicate<? super T> target)

not method creates Predicate object that indicates negative condition of target.

java.util.regex package

Pattern class

  • Predicate<String> asMatchPredicate()

asPredicate method was introduced in Java SE 8. The asPredicate method creates Predicate object to use for an argument of fiter method, mid-operation of Stream pipeline.

However, Predicate object created by asPredicate is equivalent to s -> s.matcher().find(). It means partial check.

asMatchPredicate method also creates Predicate object, but the object checks whole string. It is equivalent to s -> s.matcher().matches().

    Pattern pattern = Pattern.compile("bc");
    List<String> list = List.of("abc", "bc", "bcd");

    list.stream()
        .filter(pattern.asPredicate())
        .forEach(System.out::println);

    list.stream()
        .filter(pattern.asMatchPredicate())
        .forEach(System.out::println);

The former result of above code is "abc", "bc", and "bcd", but the latter result is only "bc".

java.util.zip package

Deflater class

  • int deflate(java.nio.ByteBuffer output)
  • int deflate(java.nio.ByteBuffer output, int flush)
  • void setDictionary(java.nio.ByteBuffer dictionary)
  • void setInput(java.nio.ByteBuffer input)

Deflater class is used for ZLIB compress.

New four methods are all overloads. Argument of exsited methods are byte array, however one of new methods are ByteBuffer.

Inflater class

  • int inflate(java.nio.ByteBuffer output)
  • void setDictionary(java.nio.ByteBuffer dictionary)
  • void setInput(java.nio.
  • ByteBuffer input)

Inflater class is used for uncompressing ZLIB compressed contents.

As same as Deflater class, new methods enable to use ByteBuffer for argument.

2018/09/26

JEPでは語れないJava SE 11 - java.baseモジュール編

このエントリーをはてなブックマークに追加

今日の早朝、Java SE 11がリリースされました。

Java SEの機能はJEPでまとめられていますが、JEPに入っていないこまごまとした変更もいろいろ入ってます。ということで、Java SE 10に引き続き、Java SE 11についてもまとめてみます。

量が多いので、今回はjava.baseモジュールだけです。他のモジュールも引き続きまとめるつもりです。

基本的にはABC順にならんでいます。重要度順にしようかと思ったのですが、量が量なので...

また、セキュリティ系のAPIはちゃんと理解していないので、省略してます。

 

廃止になったAPI

Javaは互換性を重視する言語ですが、Java 9から使われなくなった機能はほんとに削除対象になっています。

Java SE 11でも多くのAPIが廃止になっています。

モジュール

Java 11で一番大きな非互換性となるのが、Java EE関連のモジュールの削除です。これはJEP 320で提案されていました。

これらのモジュールはJava 9/10でも標準モジュールパスからは外されていましたが、Java 11でほんとうに削除されてしまいました。

  • java.se.ee
  • java.activation (JAF)
  • java.corba
  • java.transaction
  • java.xml.bind (JAXB)
  • java.xml.ws (JAX-WS)
  • java.xml.ws.annotation

JAFやJAXB、JAX-WSはJava EEのものを使うようにすればOKです。Mavenのセントラルレポジトリにも登録されているので、MavenやGradleを使われている場合はそちらを使うのが便利です。

CORBAは代替のAPIはないのですが、今さら使う人はいないでしょう。

また、Oracle JDKに含まれていたJavaFXはOracle JDKからも削除されてしまいました。

  • javafx.base
  • javafx.graphics
  • javafx.controls
  • javafx.fxml
  • javafx.media
  • javafx.web
  • javafx.swing

JavaFXはOpenJDKのOpenJFXプロジェクトで開発が進められています。Java SE 11に先立って、JavaFX 11もリリースされています。

JavaFXはバイナリの配布やJavadocも含めて、新しいJavaFXのサイトで公開されています。

クラス

セキュリティ系のAPIはいろいろと変化があります。

クラスも廃止されています。

  • java.security.Policy

メソッド

@DeprecatedアノテーションでforRemovalがtrueのメソッドで、以下のメソッドが廃止されました。

  • java.lang.Runtime.runFinalizersOnExit(boolean)
  • java.lang.SecurityManager.checkAwtEventQueueAccess()
  • java.lang.SecurityManager.checkMemberAccess(java.lang.Class, int)
  • java.lang.SecurityManager.checkSystemClipboardAccess()
  • java.lang.SecurityManager.checkTopLevelWindow(java.lang.Object)
  • java.lang.System.runFinalizersOnExit(boolean)
  • java.lang.Thread.destroy()
  • java.lang.Thread.stop(java.lang.Throwable)

いずれもJava SE 9でforRemovalがtrueになったメソッドです。

廃止予定のAPI

Java SE 11では以下のAPIが廃止予定に追加されました。

クラス

  • java.util.jar.Pack200

インタフェース

  • java.util.jar.Pack200.Packer
  • java.util.jar.Pack200.Unpacker

Pack200クラスはJEP 336で提案されていました。

追加されたAPIも含めたDeprecatedなAPIはJavadocのDeprecatedで参照できます。

 

追加されたAPI

java.ioパッケージ

ByteArrayOutputStreamクラス

  • void writeBytes(byte[] b)

このメソッドはwrite(b, 0, b.lenght)と同じ処理になります。

FileReaderクラス

コンストラクタが2つ追加されました。

  • FileReader(java.io.File file, java.nio.charset.Charset charset)
  • FileReader(java.lang.String fileName, java.nio.charset.Charset charset)

やっと文字コードを指定できるようになりました。

FileWriterクラス

FileWriterクラスもFileReaderクラスと同様に、コンストラクタで文字コードを指定できるようになりました。

  • FileWriter(java.io.File file, java.nio.charset.Charset charset)
  • FileWriter(java.io.File file, java.nio.charset.Charset charset, boolean append)
  • FileWriter(java.lang.String fileName, java.nio.charset.Charset charset)
  • FileWriter(java.lang.String fileName, java.nio.charset.Charset charset, boolean append)

InputStreamクラス

  • static InputStream nullInputStream()
  • byte[] readNBytes(int len)

nullInputStreamメソッドは何も返さないInputStreamオブジェクトを作るためのファクトリメソッドです。

readNBytesメソッドはJava 9で追加されましたが、そのオーバーロードがさらに追加されました。

Java 9で追加されたreadNBytesメソッドは引数にバイト配列を指定する必要がありましたが、Java 11でのオーバーロードでは読み込み長だけ指定すれば、配列を返してくれます。

とはいうものの、ループなどで何度も読む場合はバッファを使いまわした方が効率がいいですし、一度ですべて読み込むのであればreadAllBytesメソッドを使うので、使いどころが難しいかもしれません。

OutputStreamクラス

  • static OutputStream nullOutputStream()

InputStreamクラスと同様に、OutputStreamクラスにも何もしないストリームを作るファクトリメソッドが追加されました。

Readerクラス

  • static Reader nullReader()

Writerクラス

  • static Writer nullWriter()

java.langパッケージ

CharSequenceインタフェース

  • static int compare(java.lang.CharSequence cs1, java.lang.CharSequence cs2)

staticメソッドで比較できるようになりました。

てっきり、シンタックスシュガー的なメソッドかと思ったら、1文字ずつ比較してましたよw

Characterクラス

  • static String toString(int codePoint)

char型変数からStringオブジェクトを生成するメソッドはありましたが、補助文字を使えるcodePointからもStringオブジェクトを作れるようになりました。

Character.UnicodeBlockクラス

Unicode 10に合わせて、ブロックを表す定数が18個追加されました。いっぱいあるので省略。

Character.UnicodeScript列挙型

こちらもUnicode 10に合わせた文字スクリプト名が10個追加されました。

Classクラス

  • Class<?> getNestHost()

入れ子になったクラスのホストクラスを返します。入れ子になっていないクラス、配列、プリミティブおよびvoidは自分自身を返します。

  • Class<?>[] getNestMembers()

入れ子になったメンバーを返します。

  • boolean isNestmateOf(Class<?> c)

引数で指定したクラスとホストクラスが同じであるかどうかを調べます。配列やプリミティブ型の場合はfalseが返るようです。

これらの3つのメソッドはJEP 181 Nest-Based Access Controlで提案されたものです。

Stringクラス

  • boolean isBlank()

文字列が空かホワイトスペースだけの場合、trueが返ります。

  • Stream<String> lines()

文字列を改行コードで区切って、ストリーム化します。

  • String repeat(int count)

引数で指定された回数だけ繰り返した文字列を作ります。たとえば、"a".repeat(3);だと"aaa"が返ります。

  • String strip()
  • String stripLeading()
  • String stripTailing()

文字列の先頭と末尾のホワイトスペースを取り除くためのメソッド。

striptメソッドが先頭と末尾、stripLeadingメソッドが先頭だけ、stripTailingメソッドは末尾だけ処理します。

先頭のホワイトスペースを取り除くtrimメソッドと動作が似ているのですが、trimメソッドはLatin-1のホワイトスペース(半角のスペースとタブ、改行)だけを取り除いているようです。

このため、全角のスペースなどを取り除くにはstripメソッドを使用する必要があります。

StringBufferクラス

StringBuilderクラス

この2つのクラスはComparableインタフェースを実装するようになりました。

このため、compareToメソッドが使えるようになっています。

java.lang.invokeパッケージ

java.lang.invokeパッケージではクラスが追加されました!!

Java 10ではクラスの追加はなかったので、久しぶりのクラスの追加です。

  • ConstantBootstrapsクラス

bootstrapというのは動的に処理を決めるInvokeDynamic命令で使われていた機構です。JRubyや、ラムダ式でInvokeDynamic命令が使われています。

InvokeDynamic命令はメソッドコールに使われていたのですが、Java 11では定数に対してInvokeDynamic命令と同様に使用できるCONSTANT_Dynamic命令(condyと略されることが多いです)が追加されました(JEP 309)。

ConstantBootstapsクラスは、このcondyのbootstap用のユーティリティクラスです。

このため、普通の開発者がこのクラスを使うことはまずないと思います。

なお、Java 11のjavacでは、condyを使ったバイトコードは生成されないです。実際に使うようになるのはJava 12以降ですね。

java.lang.refパッケージ

Referenceクラス

  • Object clone()

なぜ、cloneメソッドが追加されたかというと、Referenceクラスはクローンできないということを明示するためです。このため、Referenceクラスのcloneメソッドをコールすると必ずCloneNotSupportedException例外が投げられます。

そもそも、Objectクラスにcloneメソッドが定義されていることが間違いのもとなんですよね。

java.nioパッケージ

ByteBufferクラス

  • int mismatch(ByteBuffer that)

CharBufferクラス

  • int mismatch(CharBuffer that)

DoubleBufferクラス

  • int mismatch(DoubleBuffer that)

FloatBufferクラス

  • int mismatch(FloatBuffer that)

IntBufferクラス

  • int mismatch(IntBuffer that)

LongBufferクラス

  • int mismatch(LongBuffer that)

ShortBufferクラス

  • int mismatch(ShortBuffer that)

mismatchメソッドはバッファ同士を比較して、違いのあった相対的な位置を返します。同一の場合は-1が返ります。相対といっているのは、バッファを調べるときにpositionの位置からlimitまでを調べるからです。

positionよりも前の位置の値が異なっていても、mismatchメソッドではチェックしません。

java.nio.chanelsパッケージ

SelectionKeyクラス

  • int interestOpsAnd(int ops)
  • int interestOpsOr(int ops)

ノンブロッキング通信で使用するSelectionKeyクラスは、操作対象を表す定数が定義されています。この定数はビットで表されていて、ANDやORをとることができます。たとえば、OP_READは0b0001、OP_WRITEは0b0100になっています。

これらの現在の値はinterestOpsメソッドで取得/設定できますが、それに対しAND処理とOR処理を加えたのが上記の2種類のメソッドです。

interestOpsAnd(ops)メソッドはkey.interestOps(key.interestOps() & ops)と同じ処理になります。同様にinterestOpsOr(ops)はkey.interestOps(key.interestOps() | ops)です。

Selectorクラス

SelectionKeyクラスと同様、ノンブロッキング通信で使用されるSelectorクラスでは、ノンブロッキングで行う処理の記述がラムダ式で記述できるようになりました。

  • int select(java.util.function.Consumer<SelectionKey> action)
  • int select(java.util.function.Consumer<SelectionKey> action, long timeout)
  • int selectNow(java.util.function.Consumer<SelectionKey> action)

今までは、selectedKeysメソッドでSelectionKeyオブジェクトを取り出して、処理していましたが、それが以下のように記述できます。

    selector.select(key -> {
        try {
            if (key.isAcceptable()) {
                // accept の場合の処理
                ServerSocketChannel serverSocket = (ServerSocketChannel) key.channel();
                // Non-Blocking モードに変更
                serverSocket.configureBlocking(false);
                // Selector への登録
                serverSocket.register(selector, SelectionKey.OP_READ);
            } else if (key.isReadable()) {
                // データが送られてきたときの処理
                SocketChannel socket = (SocketChannel) key.channel();
                // 読み込み処理
            }
        } catch (IOException ex) {
            // 例外処理
        }
    });

例外処理をラムダ式内でやらないといけないのが、ちょっとイヤですけど、記述はかなり簡単になりました。

java.nio.fileパッケージ

Pathインタフェース

static Path of(java.lang.String first, java.lang.String... more)

static Path of(java.net.URI uri)

Pathインタフェースにはファクトリメソッドが追加されました。

前者は FileSystems.getDefault().getPath(first, more)メソッドをコールする実装になっています。後者はスキーマがfileであればデフォルトのファイルシステム、それ以外であれば扱えるかどうかをチェックしてから、FileSystemProviderクラスのgetPath(uri)メソッドをコールしています。

Filesクラス

  • static String readString(java.nio.file.Path path)
  • static String readString(java.nio.file.Path path, java.nio.charset.Charset cs)
  • static Path writeString(java.nio.file.Path path, java.lang.CharSequence csq, java.nio.file.OpenOption... options)
  • static Path writeString(java.nio.file.Path path, java.lang.CharSequence csq, java.nio.charset.Charset cs, java.nio.file.OpenOption... options)

Filesクラスでは、ファイルを一括で読み込むreadAllLinesメソッドはあったのですが、この返り値の型はList<String>でした。これに対し、改行コードも含んだ1つの文字列として返すのがreadStringメソッドです。

同様にwriteStringメソッドではCharSequenceオブジェクトで表される文字列を一括で書き込みます。

java.utilパッケージ

Collectionインタフェース

  • <T> T[] toArray(IntFunction<T[]> generator)

配列を生成するための処理を記述できるようになりました。

Optionalクラス

OptionalDoubleクラス

OptionalIntクラス

OptionalLongクラス

  • boolean isEmpty()

isPresentメソッドの逆です。

java.tuil.concurrentパッケージ

TimeUnit列挙型

long convert(java.time.Duration duration)

durationで表される期間をTimeUnitで表される単位に変換します。TimuUnit.NANOSECONDSであればdurationをナノ秒に変換して返します。

java.util.functionパッケージ

Predicateインタフェース

  • <T> Predicate<T> not(Predicate<? super T> target)

targetで表される条件の否定を行うPredicateオブジェクトを返します。

java.util.regexパッケージ

Patternクラス

  • Predicate<String> asMatchPredicate()

Java 8でPatternクラスにasPredicateメソッドが追加されました。このasPredicateメソッドはストリームの中間操作のfilterメソッドの引数に使えるようなPredicateオブジェクトを生成していました。

ただ、このPredicateオブジェクトは s -> s.matcher().find() と同じ動作なので、文字列の部分一致を調べることになります。

そうではなく完全一致を行うようにするのがasMatchPredicateメソッドです。

    Pattern pattern = Pattern.compile("bc");
    List<String> list = List.of("abc", "bc", "bcd");

    list.stream()
        .filter(pattern.asPredicate())
        .forEach(System.out::println);

    list.stream()
        .filter(pattern.asMatchPredicate())
        .forEach(System.out::println);

これを実行すると、前者はabc, bc, bcdのすべてが表示されますが、後者はbcしか表示されません。

java.util.zipパッケージ

Deflaterクラス

  • int deflate(java.nio.ByteBuffer output)
  • int deflate(java.nio.ByteBuffer output, int flush)
  • void setDictionary(java.nio.ByteBuffer dictionary)
  • void setInput(java.nio.ByteBuffer input)

DeflaterクラスはZLIB圧縮を行うためのクラスです。

新たに追加された4種類のメソッドはいずれもオーバーロードです。既存のメソッドはすべてbyte[]が引数だったのに対し、新しいオーバーロードメソッドは引数にByteBufferクラスを使うことができます。

Inflaterクラス

  • int inflate(java.nio.ByteBuffer output)
  • void setDictionary(java.nio.ByteBuffer dictionary)
  • void setInput(java.nio.ByteBuffer input)

InflaterクラスはZIB圧縮を展開するためのクラスです。

Deflaterクラスと同様に、引数としてByteBufferクラスを使うことができるようになりました。

2018/08/17

Java 11 + JAXBのちょっとしたピットフォール

このエントリーをはてなブックマークに追加

前回はJigsawでSerciveLoaderクラスを使用した時の挙動について紹介しました。

今回はその続きのようなもの。

Java SE 11からJAXBが外されてしまうのは、このブログでも何度も書いてます。

なので、JAXBを使うのであれば、GitHubのJAXBのページからダウンロードするか、MavenのCentral Repositoryを使うことになります。

JAXBは5つのJARファイルから構成されています。

APIはjaxb-api.jar、ランタイムがjaxb-impl.jarとjaxb-core.jarです。他の2つはXMLスキーマとJavaのクラスの変換を行うツールxjcとjxcのJARファイルです。

xjcなどを使わないのであれば、jaxb-api.jar、jaxb-impl.jar、jaxb-core.jarの3つだけを使用します。

また、JAXBはJavaBeans Activation Framework (JAF)も使用するので、こちらもダウンロードしておきます。ただ、GitHubのJAFのプロジェクトではJARファイルの配布はしていないので、Maven Central RepositryにあるJAFを利用します。

 

ここでは、簡単なサンプルとして、次に示すJAXBDemoクラスを作りました。

package net.javainthebox;

import java.io.File;
import javax.xml.bind.JAXB;
import javax.xml.bind.JAXBException;

import net.javainthebox.xml.Name;

public class JAXBDemo {
    public static void main(String... args) throws JAXBException {
        File file = new File("name.xml");
        Name sakuraba = JAXB.unmarshal(file, Name.class);

        System.out.println(sakuraba.getFirst() + " " + sakuraba.getLast());
    }
}

name.xmlファイルを読み込んでNameオブジェクトに変換するプログラムです。

Nameクラスはfirstとlastという2つの文字列のフィールドを持っているクラスです。

module-info.javaを次に示します。

module net.javainthebox.xml {
    requires java.xml.bind;
    opens net.javainthebox.xml;
}

requires文で指定しているjava.xml.bindモジュールがjaxb-api.jarファイルです。

opensでnet.javainthebox.xmlパッケージを指定しているのは、JAXBがリフレクションでNameクラスにアクセスするためです。

次にjava.xml.bindモジュールの依存性を調べてみましょう。これにはjarコマンドの--describe-module (もしくは省略形の-d) オプションで調べることができます。

C:\jaxb\mod>jar -d -f jaxb-api-2.3.0.jar
java.xml.bind jar:file:///C:/jaxb/mod/jaxb-api-2.3.0.jar/!module-info.class
exports javax.xml.bind
exports javax.xml.bind.annotation
exports javax.xml.bind.annotation.adapters
exports javax.xml.bind.attachment
exports javax.xml.bind.helpers
exports javax.xml.bind.util
requires java.activation transitive
requires java.base mandated
requires java.desktop
requires java.logging
requires java.xml transitive
uses javax.xml.bind.JAXBContextFactory

一番上の行がモジュール名とモジュラーJARファイルの場所を示しています。

はじめのrequires文で指定しているjava.activationがJAFのモジュールです。

ただし、JAFのjavax.activation-api-1.2.0.jarファイルはAUTOMATIC-MODULE-NAMEは記載されているものの、モジュールにはなっていません。このため、自動モジュールとして扱います。

他のrequires文はJava SEの標準なので、特に何もしなくても大丈夫です。

最後のuses文が前回も登場したServiceLoaderクラスを使用したSPIでロードするインタフェースです。そして、この実装クラスがあるのが、jaxb-impl.jarファイルです。(本当に使用するインタフェースと実装クラスは違うようなのですが、ここでは深入りしません)

jaxb-impl.jarファイルはモジュラーJARではないので、前回説明したようにクラスパスでもモジュールパスで指定してもどちらでも大丈夫です。

まずクラスパスで指定して実行してみましょう。

ここではmodディレクトリにnet.javainthebox.xmlモジュールのjaxbdemo.jarファイル、java.xml.bindモジュールのjaxb-api.jarファイル、JAFのjavax.activation-api-1.2.0.jarファイルを配置してあります。

クラスパスで指定するjaxb-impl.jarファイルとjaxb-core.jarファイルはlibファイルに置きます。

C:\jaxb>java -p mod -cp lib\jaxb-impl.jar;lib\jaxb-core.jar -m net.javainthebox.xml/net.javainthebox.JAXBDemo
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.sun.xml.bind.v2.runtime.reflect.opt.Injector (file:/C:/home/yuichi/Web/java/diary/material/201808/20180817jaxb/lib/jaxb-impl.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int)
WARNING: Please consider reporting this to the maintainers of com.sun.xml.bind.v2.runtime.reflect.opt.Injector
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Yuichi Sakuraba

C:\jaxb>

実行することができました。

警告が出ているのはJAXBが内部でsun.misc.Unsafeクラスを使用しているためです。

次にモジュールパスで指定して、実行してみます。

jaxb-impl.jarファイルはmodディレクトリに移動してあります。

C:\jaxb>java -p mod -cp lib\jaxb-core.jar -m net.javainthebox.xml/net.javainthebox.JAXBDemo
Exception in thread "main" java.lang.NoClassDefFoundError: com/sun/xml/bind/v2/model/annotation/AnnotationReader
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1095)
        at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:206)
        at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:760)
        at java.base/jdk.internal.loader.BuiltinClassLoader.findClassInModuleOrNull(BuiltinClassLoader.java:681)
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:606)
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
        at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3167)
        at java.base/java.lang.Class.getMethodsRecursive(Class.java:3308)
        at java.base/java.lang.Class.getMethod0(Class.java:3294)
        at java.base/java.lang.Class.getMethod(Class.java:2107)
        at java.xml.bind/javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:295)
        at java.xml.bind/javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:286)
        at java.xml.bind/javax.xml.bind.ContextFinder.find(ContextFinder.java:409)
        at java.xml.bind/javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:721)
        at java.xml.bind/javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:662)
        at java.xml.bind/javax.xml.bind.JAXB$Cache.<init>(JAXB.java:127)
        at java.xml.bind/javax.xml.bind.JAXB.getContext(JAXB.java:154)
        at java.xml.bind/javax.xml.bind.JAXB.unmarshal(JAXB.java:168)
        at net.javainthebox.xml/net.javainthebox.JAXBDemo.main(JAXBDemo.java:12)
Caused by: java.lang.ClassNotFoundException: com.sun.xml.bind.v2.model.annotation.AnnotationReader
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
        ... 24 more

C:\jaxb>

AnnotationReaderクラスがないという例外が発生してしまいました!

なぜ、クラスパスだと正常に実行できて、モジュールパスだと実行できないのでしょう?

 

答えはjaxb-impl.jarファイルをモジュールとして扱うか、普通のJARファイル(無名モジュール)として扱うかというところにあります。

ここでロードできないcom.sun.xml.bind.v2.model.annotation.AnnotationReaderクラスはjaxb-core.jarファイルに含まれています。

一方、jaxb-impl.jarファイルにもcom.sun.xml.bind.v2.model.annotationパッケージが含まれているのです。

Project Jigsawでは同じパッケージを複数のモジュールで定義することはできません。1つのパッケージは1つのモジュールで定義します。

jaxb-impl.jarファイルをモジュールパスに配置してしまうと自動モジュールとして扱うので、このパッケージに関する制限に引っかかってしまうのです。このため、jaxb-core.jarファイルのクラスはロードされなかったのです。

しかし、クラスパスで指定すればモジュールではないので、パッケージが複数のJARファイルに分かれていても問題ないわけです。

JAXBがjaxb-impl.jarファイルとjaxb-core.jarファイルに分かれていなければ、なんの問題もないのですが...

既存のライブラリでも同じように同じパッケージを複数のJARファイルに含んでいる場合があるかもしれません。クラスをロードできるはずなのに、ClassNotFoundException例外が発生するような場合は、パッケージが複数のJARに分かれている可能性が高いですね。

こういう問題は、ライブラリがちゃんとメンテされていれば、時間が解決してくれるとは思います。

それにしても、こういうことがあるので、ライブラリはともかく、アプリケーションであれば今すぐモジュール化する必要はないのではないでしょうか。

2018/08/14

Project Jigsawのちょっとしたクイズ

このエントリーをはてなブックマークに追加

前回のエントリーでも触れましたけど、Jigsawのモジュールには通常のモジュールと自動モジュール (Automatic Module)、無名モジュール (Unnamed Module)の3種類あります。

自動モジュールと無名モジュールはモジュールとは名前がついてますけど、普通のJARファイルと変わりません。

自動モジュールはモジュールパスで指定し、無名モジュールは従来通りクラスパスで指定します。

通常のモジュールがアクセスできるのは、通常のモジュールか自動モジュールだけ。無名モジュールにはアクセスできません。無名モジュールにアクセスできるのは、自動モジュールです。

 

ところで、みなさんはjava.util.ServiceLoaderクラスをご存知でしょうか。

ServiceLoaderクラスを使うと、指定したインタフェースの実装クラスを実行時にロードすることができます。いわゆるSPIを実現するためのクラスです。

もちろん、JigsawでもServiceLoaderクラスをサポートしてますが、従来の方法とは実装クラスの指定方法が変わっています。

今までは、インタフェースの実装クラスを提供する場合、JARファイルのMETA-INF/servicesディレクトリにインタフェースと同名のファイルを作成し、ファイルには実装クラス名を記述します。

たとえば、インタフェースがnet.javainthebox.hello.Helloインタフェースで、SPIで提供する実装クラスがnet.javainthebox.hello.impl.HelloImplクラスだったとします。

この場合、META-INF/services/net.javainthebox.hello.Helloファイルを作成します。そして、net.javainthebox.hello.Helloファイルにはnet.javainthebox.hello.impl.HelloImplとだけ記述しておきます。

これでServiceLoaderは、クラスパスにあるJARファイルを調べて、Helloインタフェースの実装クラスをロードすることができました。

 

実装クラスをモジュールで提供する場合、インタフェースと同名のファイルを作成するのではなく、module-info.javaに記述します。

先ほどの例であれば、module-info.javaには次のように記述します。

module net.javainthebox.helloimpl {
    requires net.javainthebox.hello;
    
    provides net.javainthebox.hello.Hello with net.javainthebox.hello.impl.HelloImpl;
}

provides文でインタフェースと実装クラスを記述するわけです。

ただし、後方互換性のためにmodule-info.javaに記述するだけでなく、META-INF/servicesディレクトリにインタフェースと同名のファイルを置いておいた方がいいと思います。

モジュールがこの実装クラスを使いたい場合、はmodule-info.javaにuses文でインタフェースを指定します。

たとえば、次のように記述します。

module net.javainthebox.helloclient {
    requires net.javainthebox.hello;
 
    // SPIで使用するインタフェース
    uses net.javainthebox.hello.Hello;
}

ServiceLoaderクラスの使い方はまったく同じで、Helloインタフェースの実装クラスを探してロードすることができます。

 

ここでクイズです。

インタフェースは通常のモジュールで定義されています。インタフェースを使用するクライアントもモジュールです。

しかし、実装クラスがモジュールでない場合、ようするにMETA-INF/servicesディレクトリを使ったJARファイルの場合、どうすれば実装クラスを読み込むことができるでしょうか。

選択肢は4つ。

  1. 実装クラスがモジュールでないので、読み込めない
  2. モジュールパスで指定したディレクトリに実装クラスのJARファイルを配置して、自動モジュールとして読み込む
  3. クラスパスで指定して、無名モジュールとして読み込む
  4. モジュールパスでもクラスパスでも、どちらでもOK

 

正解は.....

 

選択肢4のモジュールパスでもクラスパスでもOKです。

通常のモジュールからのアクセスなので、モジュールパスに配置して自動モジュールとして扱わなくてはいけないように思えるかもしれません。でも、クラスパスでもOKなんです。

通常のモジュールが無名モジュールにアクセスできるという稀有な例なのでした。

 

いちおう、コードと実行例を示しておきます。

SPIで使用するHelloインタフェースはこんな感じ。

package net.javainthebox.hello;

public interface Hello {
    public void hello();
}

module-info.javaは次の通り。

module net.javainthebox.hello {
    exports net.javainthebox.hello;
}

実装クラスのHelloImplクラス。

package net.javainthebox.hello.impl;

import net.javainthebox.hello.Hello;

public class HelloImpl implements Hello {
    public void hello() {
        System.out.println("Hello, World!");
    }
}

HelloImplクラスを含んだJARファイルは、前述のようにMETA-INF/services/net.javainthebox.hello.Helloファイルを作成して、net.javainthebox.hello.impl.HelloImplと記述してあります。

さて、クライアントは。

package net.javainthebox.helloclient;

import java.util.ServiceLoader;
import net.javainthebox.hello.Hello;

public class HelloClient {
    public static void main(String... args) {
        ServiceLoader<Hello> loader = ServiceLoader.load(Hello.class);

        for (Hello hello: loader) {
            hello.hello();
        }
    }
}

クライアントのmodule-info.javaは上の方に書いてありますね。

ビルドはやってもらうということで、実行してみます。

modディレクトリをモジュールパス、libディレクトリをクラスパス用に使用するとしましょう。

まずは、自動モジュールとして実行してみます。

C:\serviceclient>dir mod
 ドライブ C のボリューム ラベルがありません。
 ボリューム シリアル番号は 4A4B-822F です

 C:\serviceclient\mod のディレクトリ

2018/08/14  21:58    <DIR>          .
2018/08/14  21:58    <DIR>          ..
2018/08/12  14:36             1,235 hello-api.jar
2018/08/12  20:01             1,603 hello-client.jar
2018/08/12  19:36             1,607 hello-impl.jar
               3 個のファイル               4,445 バイト
               2 個のディレクトリ  163,930,116,096 バイトの空き領域

C:\serviceclient>java -p mod -m net.javainthebox.helloclient/net.javainthebox.helloclient.HelloClient
Hello, World!

C:\serviceclient>

Hello, World!が表示されました。

次に、実装クラスのJARファイルをlibディレクトリに移動させて、クラスパスで指定してみましょう。

C:\serviceclient>mv mod\hello-impl.jar lib

C:\serviceclient>java -p mod -cp lib\hello-impl.jar -m net.javainthebox.helloclient/net.javainthebox.helloclient.HelloClient
Hello, World!

C:\serviceclient>

クラスパスで指定しても、ちゃんと実行できています。

というわけで、ちょっとしたJigsawのクイズでした。

2018/07/23

事例から学ぶ、Java SE 11移行 その2
-モジュールアプリケーションの場合-

このエントリーをはてなブックマークに追加

前回の続き。

もし、どうしてもモジュールアプリケーションにしたいという場合を考えてみます。

普通のアプリケーションであれば、そんなに急いでモジュール化する必要は私はないと思っています。ただし、ライブラリを開発している場合は、なるべく早くモジュールに対応してほしいです。

前回のサンプルはライブラリではないのですが、モジュール化する場合もあるでしょうし、とりあえずやってみましょう。

前回のサンプルはJersey + Grizzly の単純なアプリケーションでした。

このサンプルは 2 つのクラスから構成されています。リソースを表すMyResouceクラスと、アプリケーションの起動クラスとなるMainクラスです。いずれもcom.exampleパッケージにあります。

MyResourceクラスはこちら。

package com.example;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
 * Root resource (exposed at "myresource" path)
 */
@Path("myresource")
public class MyResource {

    /**
     * Method handling HTTP GET requests. The returned object will be sent
     * to the client as "text/plain" media type.
     *
     * @return String that will be returned as a text/plain response.
     */
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getIt() {
        return "Got it!";
    }
}

Mainクラスはこちらです。

package com.example;

import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;

import java.io.IOException;
import java.net.URI;

/**
 * Main class.
 *
 */
public class Main {
    // Base URI the Grizzly HTTP server will listen on
    public static final String BASE_URI = "http://localhost:8080/myapp/";

    /**
     * Starts Grizzly HTTP server exposing JAX-RS resources defined in this application.
     * @return Grizzly HTTP server.
     */
    public static HttpServer startServer() {
        // create a resource config that scans for JAX-RS resources and providers
        // in com.example package
        final ResourceConfig rc = new ResourceConfig().packages("com.example");

        // create and start a new instance of grizzly http server
        // exposing the Jersey application at BASE_URI
        return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
    }

    /**
     * Main method.
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        final HttpServer server = startServer();
        System.out.println(String.format("Jersey app started with WADL available at "
                + "%sapplication.wadl\nHit enter to stop it...", BASE_URI));
        System.in.read();
        server.stop();
    }
}

pom.xmlも示しておきます。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>simple-service</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>simple-service</name>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.glassfish.jersey</groupId>
                <artifactId>jersey-bom</artifactId>
                <version>${jersey.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-grizzly2-http</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>      
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.3.0.1</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>javax.activation-api</artifactId>
            <version>1.2.0</version>
        </dependency>

        <!-- uncomment this to get JSON support:
         <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-binding</artifactId>
        </dependency>
        -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <inherited>true</inherited>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>com.example.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <properties>
        <jersey.version>2.27</jersey.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>

 

依存性を調べる

アプリケーションをモジュール化する場合、必須なのがモジュールを定義するmodule-info.javaの作成です。

基本的には、module-info.javaには使用するモジュールと、公開するパッケージを記述します。

しかし、どのモジュールを使っているかなんて、よく分からないですよね。そういう時に使用するのが、jdepsコマンドです。

では、さっそくjdepsコマンドを使ってみましょう。

jdepsはJARファイルもしくはクラスファイルを引数にして実行します。サンプルのJARファイルはMavenではtargetディレクトリに作成されるので、そこで実行させてみました。

C:\jersey-sample\simple-service\target>jdeps -s simple-service-1.0-SNAPSHOT.jar
simple-service-1.0-SNAPSHOT.jar -> java.base
simple-service-1.0-SNAPSHOT.jar -> 見つかりません

C:\jersey-sample\simple-service\target>

上記の例で使用している-sオプションはサマリー表示を行うためです。

この結果、java.baseモジュールを使っていることは分かります。java.baseモジュールはJavaの基本となるモジュールでjava.langパッケージなどが含まれています。

しかし、その他のモジュールは「見つかりません」と表示されてしまいます。

これはモジュールパスもしくはクラスパスが設定されていないためです。

しかし、Mavenを使っていると、他に使用しているライブラリはローカルレポジトリにあるため、クラスパスを設定するにもちょっとめんどうです。そこで、MavenのDependency Pluginを使用して、使用しているJARファイルを一か所にコピーしてしまいましょう。

pom.xmlの<plugins>要素に以下の記述を付けくわえます。

            <plugin>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>install</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                            <includeScope>runtime</includeScope>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

これでtargetディレクトリにlibディレクトリを作成し、使用しているJARファイルをコピーします。

このlibディレクトリ配下のJARファイルをクラスパスに追加して、jdepsを実行してみます。

C:\jersey-sample\simple-service\target>mvn clean install
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]--------------------------------- 
   <<省略>>

C:\jersey-sample\simple-service\target>cd target
C:\jersey-sample\simple-service\target>jdeps -s -cp lib\* simple-service-1.0-SNAPSHOT.jar
エラー: jaxb-api-2.3.0.jarはマルチリリースjarファイルですが--multi-releaseオプションが設定されていません

C:\jersey-sample\simple-service\target>

ところが、今度はエラーが出てしまいました。

ここで、表示されているマルチリリースJARというのは、Java SE 9で導入された機能で、1つのJARファイルに複数のJavaのバージョンに対応したクラスファイルをまとめることができます。

jdepsはマルチリリースJARファイルがあると、--muliti-releaseオプションを設定する必要があるのですが、これがまた問題なのです。実際にやってみましょう。

C:\jersey-sample\simple-service\target>jdeps -s --multi-release 11 -cp lib\* simple-service-1.0-SNAPSHOT.jar
エラー: simple-service-1.0-SNAPSHOT.jarはマルチリリースjarファイルではありませんが--multi-releaseオプションが設定されています

C:\jersey-sample\simple-service\target>

ここから分かるのは--multi-releaseオプションを設定するには、すべてのJARファイルがマルチリリースJARになっていないといけないということです。しかし、それはほぼ無理なのです。

マルチリリースJARに対するjdepsの対応は間違っていると思うのですが、どうにかならないですかねぇ。

しかたないので、jaxb-api-2.3.0.jarをlibディレクトリから移動させて、再びjdepsを実行しました。

C:\jersey-sample\simple-service\target>move lib\jaxb-api-2.3.0.jar .
        1 個のファイルを移動しました。

C:\jersey-sample\simple-service\target>jdeps -s -cp lib\* simple-service-1.0-SNAPSHOT.jar
simple-service-1.0-SNAPSHOT.jar -> lib\grizzly-http-server-2.4.0.jar
simple-service-1.0-SNAPSHOT.jar -> java.base
simple-service-1.0-SNAPSHOT.jar -> lib\javax.ws.rs-api-2.1.jar
simple-service-1.0-SNAPSHOT.jar -> lib\jersey-container-grizzly2-http-2.27.jar
simple-service-1.0-SNAPSHOT.jar -> lib\jersey-server-2.27.jar

C:\jersey-sample\simple-service\target>

やっと結果が表示されました。

java.baseモジュール以外に、4つのJARファイルを使用していることが分かります。実をいうと、これらの4つのJARファイルはモジュールにはなっていません。

しかし、モジュールアプリケーションが直接アクセスできるのは、モジュールだけです。こういう場合、通常のJARファイルをモジュールとして扱う自動モジュール( Automatic Module)という機能を使用します。

実際にそれを行うのは、もうちょっと後なので、ここではまずモジュールにすることを考えましょう。

module-info.javaの生成

java.baseモジュールはJavaアプリケーションには必ず必要なので、module-info.javaに記述しなくても大丈夫です。

なので、それ以外の4つのJARファイルをmodule-info.javaに記述します。でも、これはめんどうなので、jdepsコマンドにmodule-info.javaを作ってもらいましょう。

そのために使用するのが、--generate-module-infoオプションです。

ただし、--generate-module-infoオプションではクラスパスは使用できません。その理由は、先ほどと同じでモジュールはモジュールしかアクセスできないためです。なので、クラスパスではなく、モジュールパスを設定します。ここではlibディレクトリをモジュールパスとして設定します。自動モジュールの機能があるので、モジュールでないJARファイルであっても、これで大丈夫です。

でも、1つ問題があります。libディレクトリには2つのバージョンのjavax.injectが含まれているはずです。Dependency Pluginはpom.xmlの依存性を見ているだけなので、バージョン違いのJARファイルが含まれることがあるのはしかたありません。

しかし、現状のモジュールシステムはモジュールのバージョンを直接あつかうことができません(バージョンをつけることはできます)。そのため、モジュールパスに複数のバージョンのJARファイルを置くことはできないのです。

しかたないので、libディレクトリのjavax.injectの古い方を削除してから、jdepsを実行します。

下の例では複数のバージョンがあるため、jdepsがエラーを出しています。javax.injectを削除すれば、実行できます。

C:\jersey-sample\simple-service\target>jdeps --generate-module-info . --module-path lib simple-service-1.0-SNAPSHOT.jar
Exception in thread "main" java.lang.module.FindException: Two versions of module javax.inject found in lib (javax.inject-2.5.0-b42.jar and javax.inject-1.jar)
        at java.base/jdk.internal.module.ModulePath.scanDirectory(ModulePath.java:293)
        at java.base/jdk.internal.module.ModulePath.scan(ModulePath.java:231)
        at java.base/jdk.internal.module.ModulePath.scanNextEntry(ModulePath.java:189)
        at java.base/jdk.internal.module.ModulePath.findAll(ModulePath.java:165)
        at jdk.jdeps/com.sun.tools.jdeps.JdepsConfiguration$Builder.build(JdepsConfiguration.java:544)
        at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.buildConfig(JdepsTask.java:589)
        at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:543)
        at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:519)
        at jdk.jdeps/com.sun.tools.jdeps.Main.main(Main.java:49)

C:\jersey-sample\simple-service\target>del lib\javax.inject-1.jar

C:\jersey-sample\simple-service\target>jdeps --generate-module-info . --module-path lib simple-service-1.0-SNAPSHOT.jar
writing to .\simple.service\module-info.java

C:\jersey-sample\simple-service\target>

なお、--generate-module-infoの引数はmodule-info.javaを出力するディレクトリです。ここではカレントディレクトリを指定しています。

jdeps指定したディレクトリにJARファイルに対応したディレクトリを作成して、そこにmodule-info.javaを生成します。ここではsimple-service-1.0-SNAPSHOT.javaファイルなので、バージョンを取り除いて、ハイフンをピリオドに置き換えたsimple.serviceをディレクトリ名にしています。

では、作成されたmodule-info.javaを見てみましょう。

module simple.service {
    requires jersey.container.grizzly2.http;
    requires jersey.server;

    requires transitive grizzly.http.server;
    requires transitive java.ws.rs;

    exports com.example;

}

はじめの行のmoduleキーワードの後に記述してあるのがモジュール名です。

生成したモジュール名は、jdepsが作成したディレクトリ名と同じです。つまり、JARファイル名から作成したことが分かります。これは自動モジュールのモジュール名を決めるときと同じルールで行っています。

通常、モジュール名はパッケージ名と同様に、ドメインの逆順を使用して決めます。ここではサンプルなので、このままにしておきましょう。

requires文にモジュール名を指定します。

先ほどの4つのJARファイルに対応するのが、この4行のrequires文です。

ここでも、JARファイル名からバージョン名を取り除いて、ハイフンをピリオドに置き換えたものがモジュール名として使用されています。ところが、java.ws.rsだけはJARファイル名とは異なります。

これは、JARファイルのMANIFEST.MFファイルの中にモジュール名を指定しているためです。

javax.ws.rs-api-2.1.jarのMANIFEST.MFファイルは以下のようになっています。

Manifest-Version: 1.0
Automatic-Module-Name: java.ws.rs
Bnd-LastModified: 1501859871759
Build-Id: 08/04/2017 05:17 PM
  <<以下、省略>>

この中の、Automatic-Module-Nameで指定しているjava.ws.rsがモジュール名になります。

こんなのを1つ1つ調べていくのは大変なので、jdepsコマンドで作ってもらうのが手っ取り早いですね。

さて、module-info.javaのrequires文には、transitiveがついているもののと、ついていないものがあります。

transitiveはこのモジュールが使用しているモジュールであり、かつこのモジュールがそれを外部に対して公開しているモジュールです。

たとえば、grizzly.http.serverがtransitiveになっているのはMainクラスのstartServerメソッドの戻り値の方がorg.glassfish.grizzly.http.server.HttpServerクラスだからです。

jdepsコマンドはこのようにクラスの内部までチェックしてtransitiveかどうかを決めています。

ところが、startServerメソッドはmainメソッドで使われるだけなので、実際にはtransitiveである必要はありません。

まぁ、jdepsもそこまでは調べてくれないということですね。

module-info.javaの最後のexports文はsimple.serviceモジュールで公開するパッケージを指定します。このサンプルは1つのパッケージしか使用していないので、それが表示されているだけです。

しかし、複数のパッケージがあれば、それがずらずらとexports文で記述されてしまうので、ほんとうに公開したいパッケージだけを残して、あとは削除しておきましょう。

さて、生成されたmodule-info.javaは、src/main/javaディレクトリにコピーしておきましょう。つまり、module-info.javaはデフォルトパッケージの位置に配置します。

これで、simple-serviceサンプルはモジュールアプリケーションになります。

では、コンパイルしてみましょう。

C:\jersey-sample\simple-service>mvn clean compile
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ simple-service ---
[INFO] Deleting C:\Users\yuichi\Desktop\jersey\simple-service\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ simple-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\Users\yuichi\Desktop\jersey\simple-service\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ simple-service ---
[WARNING] ********************************************************************************************************************
[WARNING] * Required filename-based automodules detected. Please don't publish this project to a public artifact repository! *
[WARNING] ********************************************************************************************************************
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 3 source files to C:\Users\yuichi\Desktop\jersey\simple-service\target\classes
[INFO] /C:/Users/yuichi/Desktop/jersey/simple-service/src/main/java/com/example/Main.java: C:\Users\yuichi\Desktop\jersey\simple-service\src\main\java\com\example\Main.javaは推奨されないAPIを使用またはオーバーライドしています。
[INFO] /C:/Users/yuichi/Desktop/jersey/simple-service/src/main/java/com/example/Main.java: 詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /C:/Users/yuichi/Desktop/jersey/simple-service/src/main/java/com/example/Main.java:[25,55] org.glassfish.jersey.ExtendedConfigにアクセスできません
  org.glassfish.jersey.ExtendedConfigのクラス・ファイルが見つかりません
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.897 s
[INFO] Finished at: 2018-07-21T21:45:47+09:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:compile (default-compile) on project simple-service: Compilation failure
[ERROR] /C:/Users/yuichi/Desktop/jersey/simple-service/src/main/java/com/example/Main.java:[25,55] org.glassfish.jersey.ExtendedConfigにアクセスできません
[ERROR]   org.glassfish.jersey.ExtendedConfigのクラス・ファイルが見つかりません
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

C:\jersey-sample\simple-service>

クラスが見つからないと、エラーになってしまいました。

ここで見つからないといわれているorg.glassfish.jersey.ExtendedConfigインタフェースは、Mainクラスで使用してるorg.glassfish.jersey.server.ResourceConfigクラスが実装しているインタフェースです。

そして、ExtendedConfigインタフェースはjersey-server-2.27.jarファイルではなく、jersey-common-2.27.jarに含まれているのです。

このことから分かるように、jdepsの--generate-module-infoオプションで作成されるmodule-info.javaは完璧なものではありません。

あくまでも、ひな型としてあつかい、必要に応じて編集しなくてはなりません。

特にリフレクションに関しては、jdepsは無力です。リフレクションを使用している場合は、自分で依存性を記述してください。

ここでは、jersey.commonのrequires文を追加します。

module simple.service {
    requires jersey.container.grizzly2.http;
    requires jersey.server;
    requires jersey.common;

    requires transitive grizzly.http.server;
    requires transitive java.ws.rs;

    exports com.example;

}

これでコンパイルはできるはずです。

ただし、テストは通りません。これはCompiler Pluginのバグで3.7.1で直るということなのですが、テストできないのは困ります。

これについては、ワークアラウンドがあって、pom.xmlのコンパイルのオプションで<source>と<target>を共に9にすれば、テストが通ります。

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <inherited>true</inherited>
                <configuration>
                    <source>9</source>
                    <target>9</target>
                </configuration>
            </plugin>

とはいうものの、せっかくJava 11を使っているのに、9にするのは何か負けているような気がして、個人的にはイヤw

実行

コンパイルできたので、実行してみましょう。

C:\jersey-sample\simple-service>mvn exec:java
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ simple-service ---
7月 22, 2018 11:46:46 午前 org.glassfish.grizzly.http.server.NetworkListener start
情報: Started listener bound to [localhost:8080]
7月 22, 2018 11:46:46 午前 org.glassfish.grizzly.http.server.HttpServer start
情報: [HttpServer] Started.
Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl
Hit enter to stop it...

あっさりと実行できてしまいました。http://localhost:8080/myapp/myresource にアクセスすれば、"Got it!" と表示されるはずです。

ところで、このサンプルで実行のために使用しているExec Maven Pluginでは、ゴールとしてexec:javaとexec:execが定義されています。

exec:javaがMavenと同じVMで実行され、exec:execは異なるVMで実行されるという違いがあります。

どうやら、現状のExec Maven Pluginのexec:javaの場合、モジュールであってもモジュールのロードではなく、単なるクラスロードが使われているようです。このため、特に設定をしなくても実行ができてしまったのです。

しかし、これではモジュールとしての動作の確認ができないので、exec:execで実行できるようにしてみます。

そのためには、pom.xmlの編集が必要です。

現在のpom.xmlのExec Maven Pluginの設定は次のようになっています。

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>com.example.Main</mainClass>
                </configuration>
            </plugin>

これを次のように変更します。

            <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</executable>
                    <arguments>
                        <argument>-p</argument> <modulepath />
                        <argument>-cp</argument> <classpath />
                        <argument>-m</argument>
                            <argument>simple.service/com.example.Main</argument>
                    </arguments>
                </configuration>
            </plugin>

変更したのは<configuration>要素です。

残念ながら、現バージョンのExec Maven Pluginではモジュールを使用するには、javaコマンドの引数を<argument>要素で指定する必要があるのです。

-pオプションは、--module-pathオプションの省略形で、モジュールパスを指定するオプションです。

ただし、クラスパスと同様に、モジュールパスでも<modulepath />要素を使用すれば、実際にモジュールパスを指定する必要はありません。これだけでも、ちょっと楽ができる感じですね。

メインクラスを指定するのは-mオプションです。-mオプションは--moduleオプションの省略形で、モジュールをロードする基点となるモジュールとメインクラスを指定します。

先ほどは、exec:javaで実行しましたが、今回はexec:execです。

C:\jersey-sample\simple-service>mvn exec:exec
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ simple-service ---
7月 22, 2018 13:23:06 午前 org.glassfish.grizzly.http.server.NetworkListener start
情報: Started listener bound to [localhost:8080]
7月 22, 2018 13:23:06 午前 org.glassfish.grizzly.http.server.HttpServer start
情報: [HttpServer] Started.
Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl
Hit enter to stop it...

同じように実行することができたはずです。

単体で実行

ここまではMavenを使ってコンパイル、実行を行ってきました。

しかし、アプリケーションを配布することを考えると、ずっとMavenに頼っているわけにはいきません。

というわけで、単体で実行できるようにしてみましょう。

まずは、Mavenでinstallまで行って、サンプルのJARファイルを作成しておきます。

C:\jersey-sample\simple-service>mvn clean install -Dmaven.test.skip=true
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
  <<以下、省略>>

前述したように、sourceとtargetが11のままだとテストが通りません。そこで、ここではテストをスキップさせています。

これで、targetディレクトリにsimple-service-1.0-SNAPSHOT.jarファイルができているはずです。

また、Maven Dependency Pluginによってlibディレクトリには依存性のあるJARファイルがコピーされています。

ここまでモジュールと自動モジュール、モジュールではないJARファイルの3種類が出てきていましたが、それを一度整理しましょう。

  • モジュール: module-info.javaでモジュール名、依存性、公開範囲が定義されたモジュール
  • 自動モジュール (Automatic Module): モジュールの定義はないが、自動的にモジュールに格上げされたJARファイル
  • 無名モジュール (Unnamed Module): モジュールではないJARファイル

自動モジュールは自動的に格上げされたため、すべてのパッケージが外部に公開されています。自動モジュールのモジュール名は前述したようにMANIFEST.MFのAutomatic-Module-Nameで定義された名前が使用されます。Automatic-Module-Nameがない場合はJARファイル名から自動的に作られます。

モジュールと自動モジュールはモジュールパスで指定したディレクトリに配置します。

無名モジュールは、モジュールという名前がついていますが、単なるJARファイルです。もちろん、すべてのパッケージが公開されています。無名モジュールは、今まで同様にクラスパスで指定します。

ここで、重要なのは通常のモジュールはモジュールもしくは自動モジュールにしかアクセスできません。アクセスできるのは、module-info.javaのrequires文に記述したモジュール(自動モジュールを含む)だけです。

つまり、モジュールは無名モジュールにはアクセスできないのです。無名モジュールにアクセスできるのは、自動モジュールだけです。

ただし、例外もあります。これについては、別エントリーで触れたいと思います。

さて、ここではルートとなるモジュールがsimple-service-1.0-SNAPSHOT.jarファイルで、このモジュールは前述したように5つの自動モジュールに依存しています。

つまり、この6個のJARファイルをモジュールパスで指定するディレクトリに配置します。そして、それ以外のJARファイルをクラスパスで指定します。

ここでは、modsディレクトリを作成して、そこに6個のJARファイルを移動させてみます。

C:\jersey-sample\simple-service\target>dir mods /B
grizzly-http-server-2.4.0.jar
javax.ws.rs-api-2.1.jar
jersey-common-2.27.jar
jersey-container-grizzly2-http-2.27.jar
jersey-server-2.27.jar
simple-service-1.0-SNAPSHOT.jar

C:\jersey-sample\simple-service\target>

では、実行してみましょう。

C:\jersey-sample\simple-service\target>java -p mods -cp lib\* -m simple.service/com.example.Main
7月 22, 2018 8:57:34 午後 org.glassfish.grizzly.http.server.NetworkListener start
情報: Started listener bound to [localhost:8080]
7月 22, 2018 8:57:34 午後 org.glassfish.grizzly.http.server.HttpServer start
情報: [HttpServer] Started.
Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl
Hit enter to stop it...

今までと同じように実行することができました。

まとめ

さて、サンプルアプリケーションをモジュール化して、単体で実行できるところまでやってみました。正直な話、かなりめんどくさいです。

新規に作成するアプリケーションであればさほどではないかもしれませんが、既存のアプリケーションをモジュール化するのはかなりタフなことになるのではないでしょうか。

ここまでの手段をまとめてみました。

  • 依存性を調べるにはjdepsコマンドを使用する
  • マルチリリースJARには気をつける
  • module-info.javaのひな型はjdepsコマンドで生成させる
  • 必要に応じて、生成したmodule-info.javaを編集する
  • Mavenで実行する場合、ゴールがexec:javaであればメインクラスだけの指定でOK
  • ゴールがexec:execの場合、モジュールパス、クラスパス、ルートモジュールとメインクラスを指定する
  • Mavenを使用せずに実行する場合、モジュールと自動モジュールをモジュールパス、その他をクラスパスで指定する

やっぱり、たいへんですね。

2018/07/15

Case Study of Migration to Java SE 11

このエントリーをはてなブックマークに追加

It is almost two months to release Java SE 11. Java SE 11 is the first release of LTS. Terefore, I assume many developers think to skip Java 9 or 10, and use Java 11.

However, it is hard to migrate to Java SE 11. The main reason is Jigsaw. Module system was introduced into Java SE. In addition, some features such as JAXB will remove from Java SE because of module system.

So, I'll show a case to migrate to Java SE 11 using simple sample application.

Sample of Jersey + Grizzly

Jersey is, you may know, RI of JAX-RS. I picked up a sample that was described in Jersey Getting Started document.

You don't need to write a code, just call Maven command.

mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 \
-DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false \
-DgroupId=com.example -DartifactId=simple-service -Dpackage=com.example \
-DarchetypeVersion=2.27

Then, Maven creates a new project from Jersey archetype.

This Jersey project includes Grizzly.

Let's check the sample application code. There are two classes in the project: one is a resouce class, other is main class.

Here is the resource class:

package com.example;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
 * Root resource (exposed at "myresource" path)
 */
@Path("myresource")
public class MyResource {

    /**
     * Method handling HTTP GET requests. The returned object will be sent
     * to the client as "text/plain" media type.
     *
     * @return String that will be returned as a text/plain response.
     */
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getIt() {
        return "Got it!";
    }
}

Main class is shown below.

package com.example;

import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;

import java.io.IOException;
import java.net.URI;

/**
 * Main class.
 *
 */
public class Main {
    // Base URI the Grizzly HTTP server will listen on
    public static final String BASE_URI = "http://localhost:8080/myapp/";

    /**
     * Starts Grizzly HTTP server exposing JAX-RS resources defined in this application.
     * @return Grizzly HTTP server.
     */
    public static HttpServer startServer() {
        // create a resource config that scans for JAX-RS resources and providers
        // in com.example package
        final ResourceConfig rc = new ResourceConfig().packages("com.example");

        // create and start a new instance of grizzly http server
        // exposing the Jersey application at BASE_URI
        return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
    }

    /**
     * Main method.
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        final HttpServer server = startServer();
        System.out.println(String.format("Jersey app started with WADL available at "
                + "%sapplication.wadl\nHit enter to stop it...", BASE_URI));
        System.in.read();
        server.stop();
    }
}

You can see the sample is very simple.

After checking the sample, I compile the sample source code.

C:\jersey-sample\simple-service>mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ simple-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\jersey-sample\simple-service\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ simple-service ---
[INFO] Compiling 2 source files to C:\jersey-sample\simple-service\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.721 s
[INFO] Finished at: 2018-07-04T19:15:21+09:00
[INFO] ------------------------------------------------------------------------
C:\jersey-sample\simple-service>

Compile is done well. After that, execute!

C:\jersey-sample\simple-service>mvn exec:java
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) > validate @ simple-service >>>
[INFO] 
[INFO] <<< exec-maven-plugin:1.2.1:java (default-cli) < validate @ simple-service <<<
[INFO] 
[INFO] 
[INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ simple-service ---
Jul 04, 2018 7:29:51 PM org.glassfish.jersey.internal.Errors logErrors
WARNING: The following warnings have been detected: WARNING: HK2 service reification failed for [org.glassfish.jersey.message.internal.DataSourceProvider] with an exception:
MultiException stack 1 of 2
java.lang.NoClassDefFoundError: javax/activation/DataSource
 at java.base/java.lang.Class.getDeclaredConstructors0(Native Method)
 at java.base/java.lang.Class.privateGetDeclaredConstructors(Class.java:3114)
  <<snip, snip>>

NoClassDefFoundError was occurred.

If you use Java SE 8 or older, you can execute the sample rightly. The reason that Java SE 11 can't execute the sample is lack of modules about Java EE (Jakarta EE).

The removed modules are below:

  • JAX-WS
  • JAXB
  • JavaBean Activation Framework (JAF)
  • Common Annotations
  • Java Transaction API (JTA)
  • CORBA

Above execution log shows javax.activation.DataSource class was not found. DataSource class is included in JAF API.

In addition, classes included in JAXB were not found.

But, the sample don't use both JAF and JAXB. Actually, Jersey and Grizzly use JAF and JAXB.

That's why compile is OK but execution fails.

Although you don't use JAXB or other Java EE features directly, many libraries uses these features. That is pitfall of migration to Java 11.

Solution

It is not so hard to migrate the sample to Java SE 11, because the sample is not modular application.

We should only add lacked libraries to CLASSPATH.

In this case, modify pom.xml for appeding dependecies of JAXB and JAF.

Updated pom.xml is shown in below. Red means updated.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>simple-service</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>simple-service</name>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.glassfish.jersey</groupId>
                <artifactId>jersey-bom</artifactId>
                <version>${jersey.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-grizzly2-http</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>      
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.3.0.1</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>javax.activation-api</artifactId>
            <version>1.2.0</version>
        </dependency>

        <!-- uncomment this to get JSON support:
         <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-binding</artifactId>
        </dependency>
        -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <inherited>true</inherited>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>com.example.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <properties>
        <jersey.version>2.27</jersey.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>

Changes in <plugin> elements are to update plugin version to the latest.

Changes in <dependencies> are important in this case. I added 3 libraries in <dependencies>.

  • jaxb-api
  • jaxb-impl
  • jaxb-core
  • javax.activation-api

JAXB consists of 2 libraries: one is interface definition (jaxb-api) and other is implementation (jaxb-impl and jaxb-core). javax.activation-api is JAF.

Then, clean, compile again, and execute.

C:\jersey-sample\simple-service>mvn exec:java
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ simple-service ---
Jul 04, 2018 20:24:38 PM org.glassfish.grizzly.http.server.NetworkListener start
INFO: Started listener bound to [localhost:8080]
Jul 04, 2018 20:24:38 PM org.glassfish.grizzly.http.server.HttpServer start
INFO: [HttpServer] Started.
Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl
Hit enter to stop it...

After Jersey starts, access http://localhost:8080/myapp/myresource by browser or curl. You can see "Got it!".

 

Conclusion

  • Java 11 removes JAXB, JAF, JAX-WS and other Java EE modules actually
  • If you don't use these modules directly, many libraries uses such modules.
  • If application is not modulear application, only add removed libraries to CLASSPATH
  • In case of Maven, add removed libraries in <dependencies> of pom.xml

2018/07/04

事例から学ぶ、Java SE 11移行

このエントリーをはてなブックマークに追加

Java SE 11のリリースまで3か月を切りました。みなさん、移行の準備は進んでいますでしょうか。

Java SE 11は、新しいリリースモデルになってはじめてのLTSになるバージョンです。なので、Java 9や10はすっ飛ばして、Java 11に移行することを考えている方も多いと思います。

しかし、Java 11への移行はいろいろ大変です。

今回は簡単なサンプルをベースにJava 11への移行を考えてみます。

Jersey + Grizzly

Jerseyは、JAX-RSのRIです。ということは、私よりもこれ読んでいる人の方が絶対に詳しいはずw

そのJerseyのUser GuideのGetting Startedに書いてあるサンプルを題材にしてみましょう。

このサンプルはMavenで勝手に作ってくれます。以下はGetting Startedからの引用です。

mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 \
-DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false \
-DgroupId=com.example -DartifactId=simple-service -Dpackage=com.example \
-DarchetypeVersion=2.27

さて、とりあえず、コンパイルしてみましょう。

C:\jersey-sample\simple-service>mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ simple-service ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\jersey-sample\simple-service\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ simple-service ---
[INFO] Compiling 2 source files to C:\jersey-sample\simple-service\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.721 s
[INFO] Finished at: 2018-07-04T19:15:21+09:00
[INFO] ------------------------------------------------------------------------
C:\jersey-sample\simple-service>

依存しているライブラリがなければ、ダウンロードするログが出るはずですが、コンパイルは成功するはずです。

では、実行してみましょう。

C:\jersey-sample\simple-service>mvn exec:java
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) > validate @ simple-service >>>
[INFO] 
[INFO] <<< exec-maven-plugin:1.2.1:java (default-cli) < validate @ simple-service <<<
[INFO] 
[INFO] 
[INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ simple-service ---
7月 04, 2018 7:29:51 午後 org.glassfish.jersey.internal.Errors logErrors
警告: The following warnings have been detected: WARNING: HK2 service reification failed for [org.glassfish.jersey.message.internal.DataSourceProvider] with an exception:
MultiException stack 1 of 2
java.lang.NoClassDefFoundError: javax/activation/DataSource
 at java.base/java.lang.Class.getDeclaredConstructors0(Native Method)
 at java.base/java.lang.Class.privateGetDeclaredConstructors(Class.java:3114)
  <<以下、省略>>

NoClassDefFoundErrorで実行できませんでした。

このサンプルはJava 8までは普通に実行できます。でも、Java 11で実行できないのはJava EE (Jakarta EE)関連のAPIが削除されてしまったためです。

上の実行例でNoClassDefFoundErrorが出ているのは、javax.activation.DataSourceクラスがないためですが、このクラスはJavaBean Activation Framework (JAF) APIに含まれています。

他にも、JAXBのクラスもNoClassDefFoundErrorが出ています。

しかし、このサンプルのソースコードは、JAFもJAXBも使っていません。これらを使っているのは、JerseyやGrizzlyなのです。

だから、コンパイルは通るのに、実行はできないわけです。

JAXBは使っていないと安心されているかもしれませんが、多くのライブラリでJAXBやJAFを使っています。これが落とし穴になるわけですね。

解決法

では、Java 11で動作するようにしてみましょう。

今回はモジュールアプリケーションではないので、比較的簡単です。つまり、クラスパスに不足しているライブラリを追加するだけでOKです。

実際には、pom.xmlにJAXBとJAFへの依存を記述していきます。

変更したpom.xmlを以下に示します。赤字のところが変更した部分です。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>simple-service</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>simple-service</name>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.glassfish.jersey</groupId>
                <artifactId>jersey-bom</artifactId>
                <version>${jersey.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-grizzly2-http</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.xml.bind</groupId>
     <artifactId>jaxb-api</artifactId>
     <version>2.3.0</version>
        </dependency>      
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
     <artifactId>jaxb-impl</artifactId>
     <version>2.3.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
     <artifactId>jaxb-core</artifactId>
     <version>2.3.0.1</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
     <artifactId>javax.activation-api</artifactId>
     <version>1.2.0</version>
        </dependency>

        <!-- uncomment this to get JSON support:
         <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-binding</artifactId>
        </dependency>
        -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <inherited>true</inherited>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>com.example.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <properties>
        <jersey.version>2.27</jersey.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>

 

pluginのところの変更はバージョンを最新にしただけです。

重要なのは<dependencies>の部分。

依存しているライブラリとして

  • jaxb-api
  • jaxb-impl
  • jaxb-core
  • javax.activation-api

を追加しています。

これでビルドしなおしてから、実行してみましょう。

C:\jersey-sample\simple-service>mvn exec:java
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.example:simple-service >---------------------
[INFO] Building simple-service 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ simple-service ---
7月 04, 2018 20:24:38 午後 org.glassfish.grizzly.http.server.NetworkListener start
情報: Started listener bound to [localhost:8080]
7月 04, 2018 20:24:38 午後 org.glassfish.grizzly.http.server.HttpServer start
情報: [HttpServer] Started.
Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl
Hit enter to stop it...

ここまで表示されたら、ブラウザでもcurlでもいいので、http://localhost:8080/myapp/myresourceにアクセスしてみてください。Got it!と表示されるはずです。

 

まとめ

  • Java 11では、ほんとにJAXB、JAF、JAX-WSなどが削除されました
  • 使っていないと思っていても、意外に使われているのがJAXBとJAF
  • モジュールアプリケーションでなければ、クラスパスにJAXBとJAFのJarを追加するだけ
  • Mavenであれば、pom.xmlの<dependencies>にJAXBとJAFを追加する

 

モジュールアプリケーションの場合はどうするのかについては、需要があれば書こうと思います。