© Kishori Sharan 2018
Kishori SharanJava Language Featureshttps://doi.org/10.1007/978-1-4842-3348-1_16

16. Breaking Module Encapsulation

Kishori Sharan
(1)
Montgomery, Alabama, USA
 
In this chapter, you will learn:
  • What breaking a module’s encapsulation means
  • How to export non-exported packages of a module using the --add-exports command-line option and using the MANIFEST.MF file of an executable JAR
  • How to open non-open packages of a module using the --add-opens command-line option and using the MANIFEST.MF file of an executable JAR
  • How to increase readability of a module using the --add-reads command-line option
  • How to use the --illegal-access command-line option to access the JDK internal API using deep reflection

What Is Breaking Module Encapsulation ?

One of the main goals of JDK9 is to encapsulate types and resources in modules and export only those packages whose public types are intended to be accessed by other modules. Sometimes, you may need to break the encapsulation specified by a module to enable white-box testing, use unsupported JDK internal APIs, or use third-party libraries. This is possible by using non-standard command-line options at compile time and runtime. Another reason for having these options is backward compatibility. Not all existing applications will be fully migrated to JDK9 and modularized. If those applications need to use the JDK APIs or APIs provided by libraries that used to be public, but have been encapsulated in JDK9, those applications have a way to keep working. A few of these options have corresponding attributes that can be added to the MANIFEST.MF file of the executable JARs to avoid using the command-line options.
Tip
Every command-line option to break a module’s encapsulation is also supported programmatically using the Module API, which is covered in detail in Chapter 15.
Although it may sound like that these options do the same things as before JDK9, there is a word of caution when accessing JDK internal APIs without any restriction. If a package in a module is not exported or open, it means the module’s designer did not intend for these packages to be used outside the module. Such packages may be modified or even removed from the module without notice. If you still use these packages by exporting or opening them using command-line options, you do so at the risk of breaking your application in future!

Command-Line Options

Three module statements in a module declaration let a module encapsulate its types and resources and let other modules use the encapsulated types and resources from the first module. Those statements are exports, opens, and requires. There is a command-line option corresponding to each of these module statements. For the exports and opens statements, there are corresponding attributes that can be used in the manifest file of an executable JAR. Table 16-1 lists these statements and the corresponding command-line options and manifest attributes. The --illegal-access command-line option lets you work with illegal access to the JDK internal APIs. I describe these options in detail in the following sections.
Table 16-1.
Module Statements with the Corresponding Command-Line Options and Manifest Attributes
Module Statement
Command-Line Option
Manifest Attribute
exports
--add-exports
Add-Exports
opens
--add-opens
Add-Opens
requires
--add-reads
(No attribute is available)
(No statement is available)
--illegal-access
(No attribute is available)
Tip
You can use the --add-exports, --add-opens, and --add-reads command-line options more than once with the same command.

The --add-exports Option

The exports statement in a module declaration exports a package in the module to all or some other modules, so those modules can use the public APIs in the exported package. If a package is not exported by a module, you can export it using the --add-exports command-line option. Its syntax is as follows:
--add-exports <source-module>/<package>=<target-module-list>
Here, <source-module> is the module that exports <package> to <target-module-list>, which is a comma-separated list of target module names. It is equivalent to adding a qualified exports statement to the declaration of <source-module>:
module <source-module> {
    exports <package> to <target-module-list>;
}
Tip
If the target module list is a special value, ALL-UNNAMED, for the --add-exports option, the module’s package is exported to all unnamed modules. The --add-exports option is available with the java and javac commands.
The following option exports the sun.util.logging package in the java.base module to the jdojo.test and jdojo.prime modules:
--add-exports java.base/sun.util.logging=jdojo.test,jdojo.prime
The following option exports the sun.util.logging package in the java.base module to all unnamed modules:
--add-exports java.base/sun.util.logging=ALL-UNNAMED

The --add-opens Option

The opens statement in a module declaration opens a package in a module to all or some other modules, so those modules can use deep reflection to access all member types in the opened package at runtime. If a package of a module is not open, you can open it using the --add-opens command-line option. Its syntax is as follows:
--add-opens <source-module>/<package>=<target-module-list>
Here, <source-module> is the module that opens <package> to <target-module-list>, which is a comma-separated list of target module names. It is equivalent to adding a qualified opens statement to the declaration of <source-module>:
module <source-module> {
    opens <package> to <target-module-list>;
}
Tip
If the target module list is a special value, ALL-UNNAMED for the --add-opens option, the module’s package is open to all unnamed modules. The --add-opens option is available with the java command.
The following option opens the sun.util.logging package in the java.base module to the jdojo.test and jdojo.prime modules:
--add-opens java.base/sun.util.logging=jdojo.test,jdojo.prime
The following option opens the sun.util. logging package in the java.base module to all unnamed modules:
--add-opens java.base/sun.util.logging=ALL-UNNAMED

The --add-reads Option

The --add-reads option is not about breaking encapsulation. Rather, it is about increasing the readability of a module. During testing and debugging, it is sometimes necessary for a module to read another module even though the first module does not depend on the second module. The requires statement in a module declaration is used to declare dependence of the current module on another module. You can use the --add-reads command-line option to add a readability edge from a module to another module. This has the same effect of adding a requires statement to the first module. Its syntax is as follows:
--add-reads <source-module>=<target-module-list>
Here, <source-module> is the module whose definition is updated to read the list of modules specified in the <target-module-list>, which is a comma-separated list of target module names. It is equivalent to adding a requires statement to the source module for each module in the target module list:
module <source-module> {
    requires <target-module1>;
    requires <target-module2>;
}
Tip
If the target module list is a special value, ALL-UNNAMED for the --add-reads option, the source module reads all unnamed modules. This is the only way a named module can read unnamed modules. There is no equivalent module statement that you can use in a named module declaration to read an unnamed module. This option is available with the java and javac commands.
The following option adds a read edge to the jdojo.common module to make it read the jdk.accessibility module:
--add-reads jdojo.common=jdk.accessibility

The --illegal-access Option

Many applications written prior to JDK9 may not be modularized soon. To take advantage of the latest JDK, they may be migrated to JDK9 without modularizing them. Such applications will run from the class path. These applications were allowed to access non-public members of the JDK internal API using deep reflection. To ease migration of these applications, JDK9 allows deep reflection on the JDK9 modules by default, which breaks the JDK module’s encapsulation. This is allowed to help the Java community adapt JDK9 sooner. Another reason behind allowing this was backward compatibility. Applications performing deep reflections on the JDK internals will continue to work in JDK9 because the Java community did not get advanced notice that deep reflection on JDK internals will stop working in JDK9.
JDK9 provides an --illegal-access option for the java command. As its name suggests, it is to work with illegal access by code in any unnamed module (code on the class path) to members of types in any named modules of the JDK using deep reflection. Its syntax is as follows:
java --illegal-access=<permit|deny|warn|debug> <other-arguments>
The option takes one of the four parameters : permit, deny, warn, and debug. The default is permit, which means that the absence of the --illegal-access option is the same as --illegal-access=permit. That is, by default, all packages in all explicit modules in the JDK are open to the code in all unnamed modules. The code performing illegal reflective access should be fixed sooner or later. To help identify such offending code, the JDK9 issues a warning to standard error on the first such illegal access. You cannot suppress this warning.
The deny parameter disables all illegal access to the members of the JDK internal types using deep reflection, except when it is allowed using other options such as --add-opens. In a future release, deny will become the default mode. That is, by default, illegal reflective access will be disabled in a future release and you will need to use --illegal-access=permit explicitly to enable illegal reflective access.
The warn parameter issues a warning for each illegal-reflective access. This is helpful in identifying all code in existing applications that uses illegal reflective access.
The debug parameter issues a warning and prints a stack trace for each illegal-reflective access. This is helpful in identifying all code in existing applications that uses illegal reflective access.
The --illegal-access option does not permit illegal access by code in a named module to the members of types in other named modules. To perform illegal reflective access in such cases, you can combine this option with the --add-exports, --add-opens, and --add-reads options.
Tip
The --illegal-access option will be removed in a future release. First, its default mode will be changed from permit to deny and then the option itself will be removed. It is important to note that this option is only for allowing illegal access to the JDK internals by the code on the class path. This option does not allow illegal access to the members of the non-JDK modules; you must use the --add-opens option or open the module/package if illegal access is needed to the members of non-JDK modules.
I present an example of using all these options that allow breaking a module’s encapsulation in the next section.

An Example

Let’s walk through a few examples of breaking a module’s encapsulation. I use trivial examples. However, they serve the purpose of demonstrating all concepts and command-line options that can be used to break a module’s encapsulation.
You created the jdojo.intro module as your first module in the first volume of this series. It contains a Welcome class in the com.jdojo.intro package. The module does not export the package, so the Welcome class is encapsulated and cannot be accessed outside the module. Listing 16-1 and Listing 16-2 contain the declaration of the jdojo.intro module and its Welcome class, respectively.
// module-info.java
module jdojo.intro {    
}
Listing 16-1.
The Declaration of a Module Named jdojo.intro
// Welcome.java
package com.jdojo.intro;
public class Welcome {
    public static void main(String[] args) {
        System.out.println("Welcome to Java 9!");        
    }
}
Listing 16-2.
A Welcome Class in the jdojo.intro Module
In this example, you call the main() method of the Welcome class from another module named jdojo.intruder , whose declaration is shown in Listing 16-3. Listing 16-4 contains the code for the TestNonExported class in this module.
// module-info.java
module jdojo.intruder {
    // No module statements
}
Listing 16-3.
The Declaration of a Module Named jdojo.intruder
// TestNonExported.java
package com.jdojo.intruder;
import com.jdojo.intro.Welcome;
public class TestNonExported {
    public static void main(String[] args) {
        Welcome.main(new String[]{});
    }
}
Listing 16-4.
A Class Named TestNonExported
The TestNonExported class contains only one line of code. It calls the static main() method of the Welcome class passing an empty String array. If this class is compiled and run, it will print the following message:
Welcome to Java 9!
If you try compiling the code for the jdojo.intruder module , you will get an error:
c:Java9LanguageFeatures>javac -d buildmodulesjdojo.intruder
--module-path buildmodulesjdojo.intro
srcjdojo.intruderclassesmodule-info.java srcjdojo.intruderclassescomjdojointruderTestNonExported.java
srcjdojo.intruderclassescomjdojointruderTestNonExported.java:4: error: package com.jdojo.intro is not visible
import com.jdojo.intro.Welcome;
                ^
  (package com.jdojo.intro is declared in module jdojo.intro, but module jdojo.intruder does not read it)
1 error
The command uses the --module-path option to include the jdojo.intro module in the module path. The error is pointing to the import statement that imports the com.jdojo.intro.Welcome class. It states that the package com.jdojo.intro is not visible to the jdojo.intruder module. That is, the jdojo.intro module does not export the com.jdojo.intro package that contains the Welcome class. To fix this error, you need to export the com.jdojo.intro package of the jdojo.intro module to the jdojo.intruder module using the --add-exports command-line option as follows:
c:Java9LanguageFeatures>javac -d buildmodulesjdojo.intruder
--module-path buildmodulesjdojo.intro
--add-exports jdojo.intro/com.jdojo.intro=jdojo.intruder
srcjdojo.intruderclassesmodule-info.java
srcjdojo.intruderclassescomjdojointruderTestNonExported.java
warning: [options] module name in --add-exports option not found: jdojo.intro
srcjdojo.intruderclassescomjdojointruderTestNonExported.java:4: error: package com.jdojo.intro is not visible
import com.jdojo.intro.Welcome;
                ^
  (package com.jdojo.intro is declared in module jdojo.intro, but module jdojo.intruder does not read it)
1 error
1 warning
This time, you get a warning and an error. The error is the same as before. The warning message is stating that the compiler could not find the jdojo.intro module. Because there is no dependence on this module, this module is not resolved even if it is in the module path. To resolve the warning, you need to add the jdojo.intro module to the default set of root module using the --add-modules option :
c:Java9LanguageFeatures>javac -d buildmodulesjdojo.intruder
--module-path buildmodulesjdojo.intro
--add-modules jdojo.intro
--add-exports jdojo.intro/com.jdojo.intro=jdojo.intruder
srcjdojo.intruderclassesmodule-info.java
srcjdojo.intruderclassescomjdojointruderTestNonExported.java
This time, the javac command succeeded even though the jdojo.intruder module does not read the jdojo.intro module. It seems to be a bug. If it is not a bug, there is no documentation that I could find to support this behavior. Later, you will see that the java command won’t work for the same modules. If this command errors out with a message that the TestNonExported class cannot access the Welcome class, add the following option to it to fix it:
--add-reads jdojo.intruder=jdojo.intro
Let’s try rerunning the TestNonExported class using the following command, which includes the com.jdojo.intruder module in the module path:
c:Java9LanguageFeatures>java
--module-path buildmodulesjdojo.intro;buildmodulesjdojo.intruder
--add-modules jdojo.intro
--add-exports jdojo.intro/com.jdojo.intro=jdojo.intruder
--module jdojo.intruder/com.jdojo.intruder.TestNonExported
Exception in thread "main" java.lang.IllegalAccessError: class com.jdojo.intruder.TestNonExported (in module jdojo.intruder) cannot access class com.jdojo.intro.Welcome (in module jdojo.intro) because module jdojo.intruder does not read module jdojo.intro at jdojo.intruder/com.jdojo.intruder.TestNonExported.main(TestNonExported.java:8)
The error message is loud and clear. It states that the jdojo.intruder module must read the jdojo.intro module in order for the former to use the latter’s Welcome class. You can fix the error by using the --add-reads option, which will add a read edge (an equivalent of a requires statement) in the jdojo.intruder module to read the jdojo.intro module. The following command does this:
c:Java9LanguageFeatures>java
--module-path buildmodulesjdojo.intro;buildmodulesjdojo.intruder
--add-modules jdojo.intro
--add-exports jdojo.intro/com.jdojo.intro=jdojo.intruder
--add-reads jdojo.intruder=jdojo.intro
--module jdojo.intruder/com.jdojo.intruder.TestNonExported
Welcome to Java 9!
This time, you receive the desired output. Figure 16-1 shows the module graph that is created when this command is run.
A323070_2_En_16_Fig1_HTML.jpg
Figure 16-1.
The module graph after using the --add-modules and --add-reads options
Both the jdojo.intruder and jdojo.intro modules are root modules. The jdojo.intruder module is added to the default set of root modules because the main class being run is in this module. The jdojo.intro module is added to the default set of root modules by the --add-modules option. A read edge is added from the jdojo.intruder module to the jdojo.intro module by the --add-reads option. Use the --show-module-resolution option with this command to see how the modules are resolved.
Let’s walk through another example that will show how to open a package of a module to another module using the --add-opens command-line option. Listing 16-5 contains the declaration of a module named jdojo.contact. Listing 16-6 contains a class named Phone, which is in the jdojo.contact module. The jdojo.contact module exports the com.jdojo.contact package–making the public members of the Phone class accessible from the outside of the jdojo.contact module.
// module-info.java
module jdojo.contact {
    exports com.jdojo.contact;
}
Listing 16-5.
The Declaration of a Module Named jdojo.contact
// Phone.java
package com.jdojo.contact;
public class Phone {
    private String phoneNumber = "9999999999";
    public Phone(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
    public String getPhoneNumber() {
        return phoneNumber;
    }
    public void setPhoneNumber(String phoneNUmber) {
        this.phoneNumber = phoneNUmber;
    }
}
Listing 16-6.
A Phone Class, Which Is a Member of the jdojo.contact Module
The TestNonOpen class , as shown in Listing 16-7, tries to load the Phone class, creates an instance of the class, and accesses its public and private members. The TestNonOpen class is a member of the jdojo.intruder module. The code in the main() method may throw several types of exceptions. I added only one exception in the throws clause to keep the logic simple.
// TestNonOpen.java
package com.jdojo.intruder;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class TestNonOpen {
    public static void main(String[] args) throws Exception {
        String className = "com.jdojo.contact.Phone";
        // Get the class reference
        Class<?> cls = Class.forName(className);
        // Get the no-args constructor
        Constructor constructor = cls.getConstructor(String.class);
        // Create an Object of the Phone class
        Object phone = constructor.newInstance("2222222222");
        // Call the getPhoneNumber() method to get the phone number value
        Method getPhoneNumberRef = cls.getMethod("getPhoneNumber");
        String phoneNumber = (String) getPhoneNumberRef.invoke(phone);
        System.out.println("Using method reference, Phone: " + phoneNumber);
        // Use the private phoneNumber instance variable to read its value
        Field phoneNumberField = cls.getDeclaredField("phoneNumber");
        phoneNumberField.setAccessible(true);
        String phoneNumber2 = (String)phoneNumberField.get(phone);
        System.out.println("Using private field reference, Phone: " + phoneNumber2);
    }
}
Listing 16-7.
A Class Named TestNonOpen
Try compiling the TestNonOpen class:
c:Java9LanguageFeatures>javac -d buildmodulesjdojo.intruder
srcjdojo.intruderclassescomjdojointruderTestNonOpen.java
The TestNonOpen class compiles fine. Note that it accesses the Phone class using deep reflection and the compiler has no knowledge of the fact that this class is not allowed to read the Phone class and its private fields. Now try running the TestNonOpen class:
c:Java9LanguageFeatures>java
--module-path buildmodulesjdojo.contact;buildmodulesjdojo.intruder
--add-modules jdojo.contact
--module jdojo.intruder/com.jdojo.intruder.TestNonOpen
Using method reference, Phone: 2222222222
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private java.lang.String com.jdojo.contact.Phone.phoneNumber accessible: module jdojo.contact does not "opens com.jdojo.contact" to module jdojo.intruder
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:337)
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:281)
        at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:176)
        at java.base/java.lang.reflect.Field.setAccessible(Field.java:170)
        at jdojo.intruder/com.jdojo.intruder.TestNonOpen.main(TestNonOpen.java:28)
I added the jdojo.contact module to the default set of root modules using the --add-modules option. You were able to instantiate the Phone class even if the jdojo.intruder module does not read the jdojo.contact module. There are two reasons for this:
  • The jdojo.contact module exports the com.jdojo.contact package, which contains the Phone class. Therefore, the Phone class is accessible to other modules, provided other modules read the jdojo.contact module.
  • The Java Reflection API assumes readability for all reflective operations. This rule assumes that the jdojo.intruder module reads the jdojo.contact module when reflection is used, even if in its module declaration the jdojo.intruder module does not read the jdojo.contact module. If you were to use types from the com.jdojo.contact package at compile time, for example, declaring a variable of the Phone class type, the jdojo.intruder module must read the jdojo.contact module either in its declaration or at the command line.
The output shows that the TestNonOpen class was able to call the public getPhoneNumber() method of the Phone class. However, it threw an exception when it tried to access the private phoneNumber field. Recall that if a type is exported by a module, other modules can use reflection to access the public members of that type. For other named modules to access the private members of the type, the package containing the type must be open. The com.jdojo.contact package is not open. Therefore, the jdojo.intruder module cannot access the private phoneNumber field of the Phone class. You can use the --add-opens option to open the com.jdojo.contact package to the jdojo.intruder module as follows:
c:Java9LanguageFeatures>java
--module-path buildmodulesjdojo.contact;buildmodulesjdojo.intruder
--add-modules jdojo.contact
--add-opens jdojo.contact/com.jdojo.contact=jdojo.intruder
--module jdojo.intruder/com.jdojo.intruder.TestNonOpen
Using method reference, Phone: 2222222222
Using private field reference, Phone: 2222222222
It is time to see the --illegal-access option in action. You need to write code that will perform illegal access on a member of a JDK class. The java.lang.Long class contains a private instance field named value. Listing 16-8 contains the code for a TestIllegalAccess class that uses deep reflection to access the Long.value field. It accesses the private value field of a Long object three times.
// TestIllegalAccess.java
package com.jdojo.intruder;
import java.lang.reflect.Field;
public class TestIllegalAccess {
    public static void main(String[] args) throws Exception {
        Long id = 1969L;        
        Class<Long> cls = Long.class;
        Field valueField = cls.getDeclaredField("value");
        valueField.setAccessible(true);
        // Read the value in the Long variable using its private field value
        long idValue = (long) valueField.get(id);
        System.out.println("Long.value = " + idValue);
        // Read the value in the Long variable using its private field value
        valueField.set(id, 1968L);
        // Read the value in the Long variable using its private field value
        idValue = (long) valueField.get(id);
        System.out.println("Long.value = " + idValue);
    }
}
Listing 16-8.
A Class Named TestIllegalAccess
The TestIllegalAccess class must be run from the class path to see the effect of the --illegal-access option. Use the following command to compile the class.
c:Java9LanguageFeatures>javac -d buildmodulesjdojo.intruder
srcjdojo.intruderclassescomjdojointruderTestIllegalAccess.java
Run the TestIllegalAccess class using the following command:
c:Java9LanguageFeatures>java --class-path buildmodulesjdojo.intruder com.jdojo.intruder.TestIllegalAccess
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.jdojo.intruder.TestIllegalAccess (file:/C:/Java9LanguageFeatures/build/modules/jdojo.intruder/) to field java.lang.Long.value
WARNING: Please consider reporting this to the maintainers of com.jdojo.intruder.TestIllegalAccess
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
Long.value = 1969
Long.value = 1968
Notice the warnings in the output. The command prints warnings about the illegal access of the JDK internals and allows the access. The following commands run the TestIllegalAccess class using different modes of the --illegal-access option. In the deny mode, the illegal access throws a runtime exception. All other modes allow the access with warnings and other details about the illegal access. Note that using the --illegal-access=permit option has the same effect when you do not use the --illegal-access option.
c:Java9LanguageFeatures>java --illegal-access=deny --class-path buildmodulesjdojo.intruder com.jdojo.intruder.TestIllegalAccess
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private final long java.lang.Long.value accessible: module java.base does not "opens java.lang" to unnamed module @2f410acf
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:337)
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:281)
        at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:176)
        at java.base/java.lang.reflect.Field.setAccessible(Field.java:170)
        at com.jdojo.intruder.TestIllegalAccess.main(TestIllegalAccess.java:11)
        at com.jdojo.intruder.TestIllegalAccess.main(TestIllegalAccess.java:10)
c:Java9LanguageFeatures>java --illegal-access=warn --class-path buildmodulesjdojo.intruder com.jdojo.intruder.TestIllegalAccess
WARNING: Illegal reflective access by com.jdojo.intruder.TestIllegalAccess (file:/C:/Java9LanguageFeatures/build/modules/jdojo.intruder/) to field java.lang.Long.value
Long.value = 1969
Long.value = 1968
c:Java9LanguageFeatures>java --illegal-access=debug --class-path buildmodulesjdojo.intruder com.jdojo.intruder.TestIllegalAccess
WARNING: Illegal reflective access by com.jdojo.intruder.TestIllegalAccess (file:/C:/Java9LanguageFeatures/build/modules/jdojo.intruder/) to field java.lang.Long.value
        at com.jdojo.intruder.TestIllegalAccess.main(TestIllegalAccess.java:11)
Long.value = 1969
Long.value = 1968
c:Java9LanguageFeatures>java --illegal-access=permit --class-path buildmodulesjdojo.intruder com.jdojo.intruder.TestIllegalAccess
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.jdojo.intruder.TestIllegalAccess (file:/C:/Java9LanguageFeatures/build/modules/jdojo.intruder/) to field java.lang.Long.value
WARNING: Please consider reporting this to the maintainers of com.jdojo.intruder.TestIllegalAccess
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
Long.value = 1969
Long.value = 1968

Using Manifest Attributes of a JAR

An executable JAR is a JAR file that can be used to run a Java application using the -jar option:
java -jar myapp.jar
Here, myapp.jar is an executable JAR. An executable JAR in its MANIFEST.MF file contains an attribute named Main-Class whose value is the fully qualified name of the main class that the java command is supposed to run. Recall that there are other kinds of JARs such as modular JARs and multi-release JARs. It does not matter which kind of JAR a JAR is based on; an executable JAR is defined only in the context of the way it is used to launch an application using the -jar option.
Suppose an existing application bundled as an executable JAR uses deep reflection to access JDK internal APIs. It worked fine in JDK8. You want to run the executable JAR on JDK9. JDK internal APIs in JDK9 have been encapsulated. Now, you must use the --add-exports and --add-opens command-line options along with the -jar option to run the same executable JAR. Using new command-line options in JDK9 provides a solution. However, it is little inconvenient for the end users of the executable JAR to use these command-line options. To ease such migrations, two new attributes for the MANIFEST.MF file of executable JARs have been added to JDK9:
  • Add-Exports
  • Add-Opens
These attributes are added to the main section of the manifest file. They are counterparts of the two command-line options: --add-exports and --add-opens. There is one difference in using these attributes. These attributes export and open packages of modules to all unnamed modules. So, you specify a list of source modules and their packages without specifying target modules as values for these attributes. In other words, in a manifest file, you can export or open a package to all unnamed modules or none, but not to any named modules. Values of these attributes are space-separated lists of slash-separated module-name/package-name pairs. Here is an example:
Add-Exports: m1/p1 m2/p2 m3/p3 m1/p1
This entry will export the package p1 in module m1, package p2 in module m2, and package p3 in module m3 to unnamed modules at runtime. Rules for parsing manifest files are lenient and allow for duplicates. Notice the duplicate entry m1/p1 in the value.
The syntax for including an Add-Opens attribute in the manifest file is the same as that of the Add-Exports attribute. The following entry in the manifest file will open package p1 in module m1, package p2 in module m2, and package p3 in module m3 to unnamed modules at runtime.
Add-Opens: m1/p1 m2/p2 m3/p3
Tip
It is important to note that the Add-Exports and Add-Opens manifest attributes are used by the runtime only when the application is run using an executable JAR, which you do by using the -jar option with the java command. In other cases, these attributes are ignored.
Let’s create an example that will combine all three previous examples in this chapter. This time, you make the examples work using the Add-Exports and Add-Opens attributes in the manifest file. You create a class named BreakAll, as shown in Listing 16-9. The class simply calls the main() method of the TestNonExported, TestNonOpen, and TestIllegalAccess classes that you have already seen in action.
// BreakAll.java
package com.jdojo.intruder;
public class BreakAll {
    public static void main(String[] args) {
        try {
            TestNonExported.main(new String[0]);
        } catch(Throwable e) {
            e.printStackTrace();
        }
        try {
            TestNonOpen.main(new String[0]);
        } catch(Throwable e) {
            e.printStackTrace();
        }
        try {
            TestIllegalAccess.main(new String[0]);
        } catch(Throwable e) {
            e.printStackTrace();
        }
    }
}
Listing 16-9.
The BreakAll Class
Listing 16-10 shows the contents of the MANIFEST.MF file . The file includes an Add-Exports entry that exports the com.jdojo.intro package in the jdojo.intro module to all unnamed modules. The file includes an Add-Opens entry that opens the com.jdojo.contact package in the jdojo.contact module and the java.lang package in the java.base module to all unnamed modules.
Manifest-Version: 1.0
Main-Class: com.jdojo.intruder.BreakAll
Add-Exports: jdojo.intro/com.jdojo.intro
Add-Opens: jdojo.address/com.jdojo.address java.base/java.lang
Listing 16-10.
The Contents of the MANIFEST.MF File
The following command will compile all classes used in this example:
c:Java9LanguageFeatures>javac -d buildclassesjdojo.intruder
--module-path buildmodulesjdojo.intro
--add-modules jdojo.intro
--add-exports jdojo.intro/com.jdojo.intro=jdojo.intruder
srcjdojo.intruderclassesmodule-info.java
srcjdojo.intruderclassescomjdojointruderTestNonExported.java
srcjdojo.intruderclassescomjdojointruderTestNonOpen.java
srcjdojo.intruderclassescomjdojointruderTestIllegalAccess.java
srcjdojo.intruderclassescomjdojointruderBreakAll.java
The following command creates an executable JAR with all classes for this example:
c:Java9LanguageFeatures>jar --create
--file distjdojo.intruder.jar
--manifest=srcjdojo.intruderclassesMETA_INFMANIFEST.MF
-C buildmodulesjdojo.intruder .
Now run the executable JAR using the following command :
c:Java9LanguageFeatures>java
--module-path buildmodulesjdojo.intro;buildmodulesjdojo.contact
--add-modules jdojo.intro,jdojo.contact
-jar distjdojo.intruder.jar
Welcome to Java 9!
Using method reference, Phone: 2222222222
Using private field reference, Phone: 2222222222
Long.value = 1969
Long.value = 1968
The output indicates that you were able to break the encapsulation of the jdojo.intro, jdojo.contact, and java.base modules using the Add-Exports and Add-Opens manifest attributes in an executable JAR. Even though this example opened the java.lang package of the java.base module using the Add-Opens manifest attribute, it is not advisable to do so because you can run this example successfully without opening the java.lang package. The only difference in the output would be that you would get warnings for illegal access. Getting warnings for illegal access of the JDK internals is preferred because your code may not work in a future release. If you see the warnings, you need to take steps to fix your code.
Try running the BreakAll class from the class path, but not using an executable JAR with the -jar command:
c:Java9LanguageFeatures>java --module-path buildmodulesjdojo.intro;buildmodulesjdojo.contact --add-modules jdojo.intro,jdojo.contact --class-path distjdojo.intruder.jar com.jdojo.intruder.BreakAll
java.lang.IllegalAccessError: class com.jdojo.intruder.TestNonExported (in unnamed module @0x9f70c54) cannot access class com.jdojo.intro.Welcome (in module jdojo.intro) because module jdojo.intro does not export com.jdojo.intro to unnamed module @0x9f70c54
        at com.jdojo.intruder.TestNonExported.main(TestNonExported.java:8)
        at com.jdojo.intruder.BreakAll.main(BreakAll.java:7)
Using method reference, Phone: 2222222222
java.lang.reflect.InaccessibleObjectException: Unable to make field private java.lang.String com.jdojo.contact.Phone.phoneNumber accessible: module jdojo.contact does not "opens com.jdojo.contact" to unnamed module @9f70c54
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:337)
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:281)
        at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:176)
        at java.base/java.lang.reflect.Field.setAccessible(Field.java:170)
        at com.jdojo.intruder.TestNonOpen.main(TestNonOpen.java:28)
        at com.jdojo.intruder.BreakAll.main(BreakAll.java:13)
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.jdojo.intruder.TestIllegalAccess (file:/C:/Java9LanguageFeatures/dist/jdojo.intruder.jar) to field java.lang.Long.value
WARNING: Please consider reporting this to the maintainers of com.jdojo.intruder.TestIllegalAccess
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
Long.value = 1969
Long.value = 1968
The output indicates that the Add-Exports and Add-Opens entries in the manifest were ignored. However, you were able to perform illegal access to the JDK internal with warnings. To fix the errors, you have to resort to the --add-exports and --add-opens command-line options to export and open needed packages to all unnamed modules as follows:
c:Java9LanguageFeatures>java
--module-path buildmodulesjdojo.intro;buildmodulesjdojo.contact
--add-modules jdojo.intro,jdojo.contact
--add-exports jdojo.intro/com.jdojo.intro=ALL-UNNAMED
--add-opens jdojo.contact/com.jdojo.contact=ALL-UNNAMED
--class-path distjdojo.intruder.jar com.jdojo.intruder.BreakAll
Welcome to Java 9!
Using method reference, Phone: 2222222222
Using private field reference, Phone: 2222222222
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.jdojo.intruder.TestIllegalAccess (file:/C:/Java9LanguageFeatures/dist/jdojo.intruder.jar) to field java.lang.Long.value
WARNING: Please consider reporting this to the maintainers of com.jdojo.intruder.TestIllegalAccess
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
Long.value = 1969
Long.value = 1968

Summary

One of the main goals of JDK9 is to encapsulate types and resources in modules and export only those packages whose public types are intended to be accessed by other modules. Sometimes, you may need to break the encapsulation specified by a module to enable white-box testing or use unsupported JDK internal APIs or libraries. This is possible by using non-standard command-line options at compile time and runtime. Another reason for having these options is backward compatibility.
JDK9 provides two command-line options, --add-exports and --add-opens, that let you break encapsulation defined in a module declaration. The --add-exports option lets you export a non-exported package in a module to other modules at compile time and runtime. The --add-opens option lets you open a non-open package in a module to other modules for deep reflection at runtime. The value for these options is of the form <source-module>/<package>=<target-module-list>, where <source-module> is the module that exports or opens <package> to <target-module-list>, which is a comma-separated list of target module names. You can use ALL-UNNAMED as a special value for the list of target modules that exports or opens those packages to all unnamed modules.
There are two new attributes named Add-Exports and Add-Opens that can be used in the main section of the manifest file of an executable JAR. Effects of using these attributes is the same as using the similarly named command-line options, except that these attributes export or open the specified packages to all unnamed modules. The value for these attributes is a space-separated list of slash-separated module-name/package-name pairs. For example, an Add-Opens: java.base/java.lang entry in the main section of a manifest file of an executable JAR will open the java.lang package in the java.base module to all unnamed modules.
During testing and debugging, it is sometimes required that a module read another module where the first module does not use a requires statement in its declaration to read the second module. This can be achieved using the --add-reads command-line option whose value is specified in the form <source-module>=<target-module-list>. The <source-module> is the module whose definition is updated to read the list of modules specified in the <target-module-list>, which is a comma-separated list of target module names. A special value of ALL-UNNAMED for the target module list makes the source module read all unnamed modules.
By default, JDK9 allows illegal reflective access to JDK internals by the code on the class path. This is allowed for backward compatibility. That is, an application performing illegal reflective access on JDK internal that ran on JDK8 will continue to run on JDK9 from the class path. A warning is printed to the standard error on the first use of such an illegal reflective access. You can use the --illegal-access for the java command to print warnings and stack traces for all such accesses. The option takes one of the following four parameters: permit, deny, warn, and debug. The default is permit. In a future release, this default behavior will be changed to deny such illegal access and you will need to explicitly use --illegal-access=permit to allow such access. Further, in a future release, the --illegal-access option itself will be discontinued.
Questions and Exercises
  1. 1.
    What is breaking module encapsulation?
     
  2. 2.
    Describe the effects of using the --add-exports, --add-opens, and --add-reads command-line options.
     
  3. 3.
    What is the difference between using the --add-exports and --add-opens command-line options and their counterparts, the Add-Exports and Add-Opens attributes, in the manifest file?
     
  4. 4.
    Suppose you have a module named M, which needs to use types in unnamed modules. Write the command-line option to achieve this.
     
  5. 5.
    Describe the --illegal-access command-line option with its default behaviors in JDK9 and its proposed behaviors in the future JDK.
     
..................Content has been hidden....................

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