Open Modules and the opens Directive – Java Module System

19.8 Open Modules and the opens Directive

Reflection is a powerful feature in Java that enables inspection and modification of runtime behavior of Java programs. The Reflection API is primarily in the java.lang.reflect package. Reference types (classes, interfaces, and enums) can be inspected at runtime to access information about the declarations contained in them (e.g., fields, methods, and constructors)—often referred to as reflective access. In fact, classes can be programmatically instantiated and methods can be invoked and values of fields can be changed. An example of using reflection to process annotations in Java programs is provided in §25.6, p. 1587.

Some frameworks, such as Spring and Hibernate, make heavy use of reflection in the services they provide. They rely on deep reflection: being able to use reflection, not only on public classes and their public members, but also on non-public classes and non-public members. Note that these frameworks require access for reflection at runtime, and not at compile time.

However, things changed with the introduction of modules in Java, where encapsulation is in the front seat. Reflection is now only allowed on public members of public types in exported packages from a module. In other words, the exports directive does not permit reflection on non-public types and non-public members from exported packages.

The opens directive in a module declaration addresses this issue of reflection at runtime, allowing deep reflection on all classes and all members contained in an open package.

Click here to view code image

module org.liberal {
  opens org.liberal.sesame;     // An open package
}

The adviceApp application (Example 19.1, Figure 19.12) is now modified to illustrate the opens directive. We will refer to the modified application as adviceOpen. The module controller opens the com.passion.controller package to enable clients to inspect its class AdviceController at runtime, and elicit advice by invoking the method showAdvice(). The declaration of the module controller specifies this vital information using an opens directive, as shown below in the module declaration at (1).

The idea is that module main will use reflective access on the controller module to elicit advice. Therefore, the main module does not require the controller module, as we can see from the module declaration below at (2).

It is not necessary for a module to explicitly require a module that has open packages for the purposes of reflection. As long as the module with the open packages is available at runtime, reflection can be used to access the contents of the open packages. The emphasis here is on use of the opens directive rather than a fullblown treatise on reflection.

Click here to view code image

// Filepath: src/controller/module-info.java
module controller {                                 // (1)
  requires model;
  requires view;
  opens com.passion.controller;                    // Open package
}

Click here to view code image

// Filepath: src/main/module-info.java
module main {}                                      // (2)

In Example 19.2, the main() method at (1) in the class Main of the package com.passion.main in the module main uses reflection to invoke the showAdvice() method of the AdviceController class in the com.passion.controller open package.

The comments in the main() method are self-explanatory, but a few things should be noted.

The forName() static method of the Class class at (2) loads the specified class and returns the Class object associated with the class whose name is com.passion .controller.AdviceController. Note that module controller is not specified. We will make it available when we run the application with the java command.

The Class object from (2) is used at (3) to obtain the object representing the no-argument constructor of the AdviceController class. The newInstance() method is invoked at (4) on the constructor object to create an instance of the AdviceController class.

The class object from (2) is used at (5) to obtain all Method objects that represent methods in the AdviceController class. The method getDeclaredMethods() returns an array containing Method objects reflecting all declared methods of the Advice-Controller class represented by the Class object cObj, including public, protected, default access, and private methods in the class. As this class only has one private method—showAdvice()—index 0 designates this method in the array of Method objects, as shown at (6).

The if statement at (7) ensures that the method reference indeed designates the private method showAdvice().

However, before a non-public method is invoked via reflection, it is necessary to explicitly suppress checks for language access control at runtime by calling the setAccessible() method of the Method class with the value true, as shown at (8).

At (9) through (12), the private method showAdvice() designated by the method reference is invoked on the AdviceController instance specified in the argument to the invoke() method, together with an int parameter required by the showAdvice() method for specific advice.

The code in the main() method is enclosed in a try-catch construct to catch the checked exceptions that can be thrown by the various methods.

Example 19.2 Selected Source Code Files in the adviceOpen Application

Click here to view code image

// File path: src/main/com/passion/main/Main.java
package com.passion.main;
import java.lang.reflect.*;                             // Types for reflection
public class Main {
  public static void main(String… args) {                                // (1)
    try {
      // Get the runtime object representing the class.
      Class<?> cObj
          = Class.forName(“com.passion.controller.AdviceController”);      // (2)
      // Get the no-argument constructor of the class.
      Constructor<?> constructor = cObj.getDeclaredConstructor();          // (3)
      // Create an instance of the class.
      Object instance = constructor.newInstance();                         // (4)
      // Get all declared methods, including those that are non-public.
      Method[] methods = cObj.getDeclaredMethods();                        // (5)
      // Class has only one method.
      Method method = methods[0];                                          // (6)
      // Check if it is the right method.
      if (!method.getName().equals(“showAdvice”)) {                        // (7)
        System.out.printf(“Method showAdvice() not found in %s.%n”,
                          cObj.getName());
        return;
      }
      // Disable access control checks on the method as it is a private method.
      method.setAccessible(true);                                          // (8)
      // Invoke the method on the instance, passing any arguments.
      method.invoke(instance, 1);                                          // (9)
      method.invoke(instance, 2);                                          // (10)
      method.invoke(instance, 3);                                          // (11)
      method.invoke(instance, 0);                                          // (12)
    } catch (ClassNotFoundException | NoSuchMethodException |
             InstantiationException | IllegalAccessException |
             IllegalArgumentException | InvocationTargetException ex) {
      ex.printStackTrace();
    }
  }
}

Click here to view code image

// File path: src/controller/com/passion/controller/AdviceController.java
package com.passion.controller;
import com.passion.model.AdviceModel;               // From model module.
import com.passion.view.AdviceView;                 // From view module.
public class AdviceController {
  private AdviceModel model;
  private AdviceView view;
  public AdviceController() {
    model = new AdviceModel();
    view = new AdviceView(model);
  }
  private void showAdvice(int adviceNumber) {       // (13)
    model.setCurrentAdvice(adviceNumber);
    view.updateAdviceDisplay();
  }
}

The accessibility of the showAdvice() method at (13) in class AdviceController is changed to private in Example 19.2 to illustrate that an open package allows reflection on private members.

The other two modules, model and view, are the same as in the adviceApp application (Example 19.1, Figure 19.12).

We can compile all modules in the adviceOpen application, as we did for the adviceApp:

Click here to view code image

>javac -d mods –module-source-path src –module model,view,controller,main

However, trying to run the application throws an exception:

Click here to view code image

>java –module-path mods –module main/com.passion.main.Main
java.lang.ClassNotFoundException: com.passion.controller.AdviceController

The reason for the exception is that the controller module has not been resolved at runtime, since it is not required by any module in the application. We can include modules needed by the application using the –add-modules option:

Click here to view code image

>java –add-modules controller –module-path mods \
     
–module main/com.passion.main.Main
See no evil.
Speak no evil.
Hear no evil.
No advice.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *