Java9 migration considerations

  java9

Order

This article mainly studies some matters needing attention in migrating to java9.

Migration type

1. The code is not modularized. First, migrate to jdk9 to make good use of the api of jdk9
2. The code is also migrated modularly.

Several Points for Attention

Unreadable class

For example, sun.security.x509 is included in java.base module in java9, but this module does not export this package.

You can modify the export settings by adding-add-exports java.base/sun.security.x509=ALL-UNNAMED at runtime.

Internal class

For example, sun.misc.Unsafe originally only wanted oracle jdk team to use it. However, due to the wide application of these classes, java9 made a compromise for backward compatibility. It just classified these classes into jdk.unsupported modules and did not limit their readability.

➜  ~ java -d jdk.unsupported
jdk.unsupported@9
exports com.sun.nio.file
exports sun.misc
exports sun.reflect
requires java.base mandated
opens sun.misc
opens sun.reflect

Deleted classes

Java9 has deleted sun.misc.BASE64Encoder, which can only be changed to other apis, such as java.util.Base64

classpath vs module-path

Java9 has introduced a module system and its own jdk has also been modularized. module-path is introduced to shield classpath. That is to say, module-path is preferred in java9. After all, jdk itself is modularized. If the application itself is not modularized, java9 implicitly modularizes through unnamed modules and automatic modules mechanisms. Of course, classpath can still be used on java9, for example, it can be used in conjunction with module-path.

Jars without modularity will be classified as unnamed modules; in classpath; The module-path will be automatically created as automatic modules (An automatic modules declares that transitive depends on all named and unnamed module, and then exports its own package.)

A package name cannot appear in multiple modules (split packages)

Because modules can export packages to other modules, if multiple modules export the same package name will cause confusion, especially if other class libraries requires both modules at the same time, you don’t know which module to reference.

Transfer dependency

If the interface parameters or return types of one module use the classes of other modules, it is recommended that requires transitive the modules it depends on.

Careful circular dependence

When designing a module, it is necessary to consider whether there will be circular dependency or not, and if so, it needs to be redesigned.

Use services to implement optional dependencies.

Services is especially suitable for decoupling the dependency between the caller and the implementation class. If the interface has multiple implementation classes, the caller does not need to requiress all the implementation classes, but only needs the requirements interface, and uses the service type to load the instance of the implementation class. Decoupling is realized by dynamically adding implementation modules in module-path.

Module version management

Module-info.java does not support declaring version numbers, but when creating jar packages, you can set them through –module-version. However, the module system still uses the module name to find the module (If there are multiple modules with the same name in the module-path, the module system informs the user to use the first one found and automatically ignores the subsequent modules with the same name), version dependency problem is beyond the scope of module system solution, and is managed by dependency management tools such as maven.

Module resource access

After modularization, the resource files are also protected. Only the module can access the resource files of the module itself. If cross-module access is required, the target module must be found by means of ModuleLayer, and then the target module is called to load the resource files of the module.

Use of reflection

This involves the problem of deep reflection, which is to call a class’s non-public element by reflection. Module-info.java’s exports declares that package only allows classes to which the package directly belongs to access its public element, and does not allow reflection calls to non-public elements.

Reflection requires special declaration in the module system to allow use (Use opens declaration to allow deep reflection), which results in many class libraries that use reflection, such as spring, requiring additional configuration to migrate to java9. There are two solutions: one is to give OpenPackage package name to modules that need reflection, such as spring.beans, etc. One is direct opens whole module.

Default –illegal-access=permit = permit, and this setting is only applicable to packages before java9 that are not allowed to access in java9, and is not applicable to new packages that are not allowed to access in java9. (It is recommended to set it to deny when migrating to a modular system)

However, in the module system, packages with different names belong to different packages, and there is no inheritance relationship. For example, com.service.func1 and com.service.func2 are two different packages. You cannot only use OpenSC.service, but you must specify them separately, which leads to more packages that need to be open. Therefore, it may be easier to open the whole module, but it is also a rougher approach.

The above method is to make changes in the original module-info.java , and the other is to modify the original relationship through specified commands when executing java or javac. such as

java ... --add-opens source-module/source-package=target-module

If you need to export to unnamed modules, the target-module is ALL-UNNAMED

Of course, if it is a new system, it is not recommended to use reflection. You can use MethodHandles and VarHandles.

Common Problems and Measures

ClassNotFoundException/NoClassDefFoundError

For example, JAXBx.xml.bind.JAXBException, which has been included in java.xml.bind module, is added after java naming.

--add-modules java.xml.bind

If it is convenient, add $JAVA_HOME and all third-party class libraries to the module-path, and then add one

--add-modules ALL-MODULE-PATH

illegal reflective access by xxx to method java.lang.ClassLoader.defineClass

The reflection reason is that since the old system did not have module-info, parameters were added to java naming to modify it.

--add-opens java.base/java.lang=ALL-UNNAMED

Identify dependent modules

Through IDE or jdeps analysis

jdeps --class-path 'classes/lib/*' -recursive -summary app.jar

Jdeps is only static code analysis. If there is a reflection class jdeps that cannot be analyzed, you need to manually require it. If dependency is optional, you can requires static

Readability of Module Unit Testing

If the unit test is a separate module, the readability and reflection ability of the unit test module to the target module can be granted through –add-exports or –add-opens at runtime. In addition, due to the split packages problem, the package name of the unit test class cannot be duplicated with the package name of the target module. The original maven project test

Summary

You can migrate to java9 in two steps. First, you will not be modularized but will only run on jdk9 first. And then modular.

doc