Overriding module behavior

jdeps is great at identifying internal API access and suggesting fixes. They come in handy when fixing your own application code that contains such problems. But what if jdeps reports problems with some code in a library or a framework you are using? In such cases, you have lesser control over the code. Even if the framework itself is open source, the scale and complexity of the library may not make it feasible for you to implement the fix yourself. This does present a very clear risk for applications moving to Java 9--your app won't run in Java 9 until all your libraries are updated to work in Java 9. It's very likely that most library developers have either already heeded to the scores of warnings in Java 8 and fixed their code, or they will soon, because of their code breaking in Java 9. But if they don't, this could mean that your migration plans are at the mercy of the library authors.

Thankfully, the platform comes with some override features to get around this problem. The override features we'll look at here apply not just to legacy Java code being compiled in Java 9, they also work on Java 9 modules. But since they are primarily designed to assist migration, they should be used in the context of migration only, and other uses should ideally be avoided.

What are these override features? Remember that a Java 9 module has a module definition that specifies what it requires and what it exports. These individual module definitions that are specified in the module-info.java file at development time essentially control the accessibility relationship between modules during compilation and runtime. However, it turns out, both the compiler and runtime has override options for these module relationships that allow you to change what any given module requires or exports by specifying command-line arguments.

There are three command line flags to javac and java to override specific module configurations:

  • add-reads: The --add-reads option allows you to specify additional readability relationships that may not already be available for a module as per the module configuration. The syntax is:

      --add-reads <source-module>=<target-module>

Adding this option to the javac and java command line creates a new readability relationship only for that command execution you use the argument for. For example, let's say you have modules moduleA and moduleB, and you want to have moduleA read moduleB. You can either edit the module-info.java file in moduleA and add the line requires moduleB;, or add the following argument to the compiler and runtime, as shown in this following truncated command:

      $ java ... --add-reads moduleA=moduleB
  • add-exports: The --add-exports option allows you to add additional exported packages from a module, thereby breaking or overriding encapsulation. The syntax is:

      --add-exports <source-module>/<package-name>=<target-module>
  • For example, if moduleA needs package pack.internal from moduleB, but moduleB does not export the package, you can add the following override to have moduleB export the required package for moduleA, as shown in this truncated command:

      $ java ... --add-exports moduleB/pack.internal=moduleA
  • add-opens: The --add-opens option lets you override the opens relationship between modules to allow reflective access. This is an override that simulates the opens keyword configuration in the module definition. The syntax is:

      --add-opens <source-module>/<package-name>=<target-module>
  • Following the same example, if moduleA needs runtime-only reflective access to the package pack.internal in moduleB, you would run the javac or java command with the following option:
     $ java ... --add-opens moduleB/pack.internal=moduleA
Note that the overrides are always qualified, that is, you specify them for a specific target module. For example, you don't use the --add-exports flag to export a package to every other module. You explicitly specify one or more target modules that the override applies to from the source module. That's a good thing because every override is made consciously and it's easy to track what is needed to get the application to work.

There is another way to provide these overrides in addition to command line arguments. You can specify them inside JAR file manifests. Let's say you have an executable JAR file that needs some overrides to work with platform modules in Java 9. To avoid having to specify these overrides everytime as command line arguments, you could just specify these as manifest attributes in the MANIFEST.MF file in your JAR file. As with all manifest attributes, the value should be specified following a space after the attribute name in the MANIFEST.MF file. The manifest attribute Add-Exports corresponds to the  --add-exports argument. The attribute  Add-Opens corresponds to --add-opens. There's no manifest attribute equivalent for --add-reads.

In addition to these overrides, there's a master kill switch for any modularity related encapsulation for classes in the classpath--the --permit-illegal-access flag. Unlike the previous three options, this option works only on code in the classpath. This flag, when passed to javac or java effectively disables all readability and accessibility restrictions, thus making any type accessible to any other type in the classpath. It's almost as if there are no Java modularity features, and everything in the classpath works as if you are running in Java 8 or earlier. As you'd expect, it's not a good idea to use this flag, especially if you are running code in production. This is provided to help developers migrate their classpath applications, and could very well be removed in the future. Think of this as a last resort option to get things to work as you work on migration.

Now that you've learned these override options, how can you apply them to solve the problem we started discussing this section with libraries or classes in your Java 8 code that use encapsulated APIs, and thus no longer work in Java 9? If the code is not in your control and you cannot fix it to avoid using the encapsulated types, you could use the override switch to manually add the required exports! For example, our code in the 02-non-standard-api used an internal non-exported type CalendarUtils from java.base. Since we obviously cannot change the module descriptor for the  java.base module, what we could instead do is pass the --add-exports option to the module and have it export the required packages.

But here's a problem. Note that the syntax needs the source and target module names. The source module name is java.base of course. What is the target module? It's the unnamed module, because it's the classes in the classpath that needs this package. This brings up an interesting question--what is the name of the unnamed module? The unnamed module doesn't have a name (or indeed it wouldn't be called that!), but there's a special token called ALL-UNNAMED that you can pass to the override arguments that let the platform know that you are referring to the unnamed module:

This command should solve the problem of the CalendarUtils access in our code. There's still the missing BASE64Encoder type. There's no command-line argument to fix that one. Like we've seen, the type simply doesn't exist in Java 9. We'll have to replace it with something that does. Taking the suggestion provided by jdeps, the class AppFixed.java has the updated code that uses the Base64class instead:

This time, you don't get errors and the compilation goes through. You still see the warning about using an internal type. The compiler still reminds you that it's not an ideal situation, but since you've added the override, it trusts that you know what you are doing.

If the code was complied in a previous version of Java and you were running it in Java 9 and the problem was only with accessing encapsulated types, you can add the same override arguments to the java command and have it run. However, if the compiled classes are referring to unavailable types, like the BASE64Encoder type we just looked at, you have no choice but to edit the code and recompile first.

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

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