Implied readability

We looked at the problem of dependency leakage in Chapter 5, Using Platform APIs. The module that you depend on might have APIs that might also require you to use another module. Here's an example:

    module A { 
      requires B; 
    } 
 
    module B { 
      requires C; 
    } 

The module A  requires module B, which in turn requires C. We know that with this, A does not read C, since module dependencies are not transitive in nature. But what if it needs to? For instance, if B has an API whose return type is in module C.

A good example can be found in the platform module itself. Let's say you write your custom module that reads java.sql. And you'd like to use the Driver interface from the module. The Driver interface has a method called getParentLogger() that returns the type Logger. Here's what the method in the Driver interface looks like:

    Logger getParentLogger() throws SQLFeatureNotSupportedException 

Here's your code in your custom module that calls the API from java.sql:

    Logger myLogger = driver.getParentLogger();  

To get this to work, you just need to add requires java.sql in your module definition, and you should be good to go, right? Not so fast! Think about the return type Logger. That type is actually coming from java.logging, like we've already seen. The java.sql module depends on java.logging for logging capabilities, so it isn't a problem for that module. But how about yours?

Since yourmodule does not directly require java.logging, in order to use the java.sql API, you'd have to require the java.logging module too!

As you can tell, this is not very convenient. If the usage of a certain module requires the use of other modules too, it just adds to the complexity of the API. Here, you'd need to have some documentation for the developers that says, If you happen to use java.sql, don't forget to also require java.logging.

Is there a better way? Although dependencies aren't transitive by default, what we'd like is the ability to selectively make only certain dependencies transitively available, for situations like this. Thankfully, this is possible in Java 9 by using the transitive keyword. When you declare requires on a module, you can also have that module be available and readable to any modules that depend on your module. The way to use this feature is like this--requires transitive <module-name>;

In the following example, module A requires B. But module B requires transitive C:

    module A { 
      requires B; 
    } 
 
    module B { 
      requires transitive C; 
    } 

Now, module C is readable by not only module B, but by all other modules that read module B. So here, A gets to read C too!

Notice that the transitive keyword is adding an additional semantic to the requires keyword. The line requires transitive C now makes C readable by all modules that read B, while at the same time retaining the meaning of requires that we've known all along--that B needs to read C too!

How does this affect the readability relationship we have just discussed? We know that A reads B because of the explicit requires relationship? But does A read C as well? The answer is yes, and this kind of readability relationship is referred to as implied readability. The relationship is not explicit, since there's no direct dependency declared by A on C. The readability is implied due to its transitive nature.

This feature is leveraged in the java.sql module to solve the problem with the Logger return type. If you run java -d on java.sql, you'll see this:

$ java -d java.sql 
  module java.sql@9 
  exports java.sql 
  exports javax.sql 
  exports javax.transaction.xa 
  requires transitive java.logging 
  requires transitive java.xml 
  requires mandated java.base 
  uses java.sql.Driver 

Notice that the two modules java.xml and java.logging that java.sql requires are both marked transitive. Like we've just seen, this means that any module that requires java.sql will get access to the APIs in java.xml and java.logging automatically! This is a decision taken by the platform team because using many of the APIs in java.sql requires the use of the other two modules as well. So rather than having all the developers remember to require those too, the platform has made it automatic. This is why any module that depends on java.sql and calls the Driver.getParentLogger() will have no problems using the Logger type, since that module will have an implied readability on java.logging:

Note that you need to be cautious about adding a lot of transitive dependencies in your modules. I mentioned dependency leakage in Chapter 5, Using Platform APIs, and how it's better to have all of your module's dependencies restricted in usage in your module. Any usage of APIs that the module exposes should only need to deal with types that are all exposed and available in the same module as well. The concept of transitive dependencies in a way seems to run counter to that philosophy. Thanks to transitive, any dependency leakages can be handled easily by making the modules containing the leaked types as requires transitive. But that's a slippery slope. Imagine having to depend on a module and inadvertently getting a dozen other module dependencies because they are all marked requires transitive in the module you need! Such module designs clearly violate the principles of modularity, and I highly recommend avoiding them unless absolutely required.

There is, however, a really interesting and handy usage of transitive dependencies that will help library developers. That is aggregator modules.

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

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