6.2.  Use jdeps to determine dependencies and identify way to address the cyclic dependencies

[Note]

jdeps [options] path ...
					

The jdeps command shows the package-level or class-level dependencies of Java class files. The input class can be a path name to a .class file, a directory, a JAR file, or it can be a fully qualified class name to analyze all class files. The options determine the output. By default, the jdeps command writes the dependencies to the system output. The command can generate the dependencies in DOT language (the -dotoutput option).

Possible options:

Assume we have several JARs as follows:

C:\1Z0-817
│
├───app
│   └───by
│       └───iba
│           └───app
│                   App.java
│
└───info.logger
    └───by
        └───iba
            └───logging
                    InfoLogger.java
					

package by.iba.app;

import by.iba.logging.InfoLogger;
import java.util.logging.Logger;

public class App {

    public static void main(String... args) {
        InfoLogger.log("Application started ...");
        Logger logger = InfoLogger.getLog();
        logger.info("Application finished.");
    }    
}
					

package by.iba.logging;

import java.util.logging.Logger;

public class InfoLogger {
    
    private static final Logger LOG = Logger.getLogger(InfoLogger.class.getName());
    
    public static void log(String msg) {
        LOG.info(msg);
    }

    public static Logger getLog() {
        return LOG;
    }
}
					

javac info.logger\by\iba\logging\InfoLogger.java
jar --create --file info-logger.jar -C info.logger .

javac -cp info-logger.jar app\by\iba\app\App.java
jar --create --file app.jar -C app .
					

Now we can check class path dependencies:

C:\1Z0-817>jdeps -cp info-logger.jar -s app.jar

app.jar -> info-logger.jar
app.jar -> java.base
app.jar -> java.logging
					

C:\1Z0-817>jdeps -s info-logger.jar

info-logger.jar -> java.base
info-logger.jar -> java.logging
					

We can automatically generate module definitions for the 2 JARs:

C:\1Z0-817>jdeps --generate-module-info . *.jar

writing to .\app\module-info.java
writing to .\info.logger\module-info.java
					

The content of generated module-info.java files as follows:

module app {
    requires info.logger;
    requires java.logging;
    exports by.iba.app;
}
					

module info.logger {
    requires transitive java.logging;
    exports by.iba.logging;
}
					

Re-create JARs as modular ones:

C:\1Z0-817>javac info.logger\module-info.java info.logger\by\iba\logging\InfoLogger.java
C:\1Z0-817>jar --create --verbose --file info-logger.jar -C info.logger .

C:\1Z0-817>javac -p info-logger.jar app\module-info.java app\by\iba\app\App.java
C:\1Z0-817>jar --create --verbose --file app.jar -C app .
					

Now we can check module dependencies:

C:\1Z0-817>jdeps --module-path app.jar;info-logger.jar -summary --module app
app -> info.logger
app -> java.base
app -> java.logging
					

C:\1Z0-817>jdeps --module-path app.jar;info-logger.jar -summary --module info.logger
info.logger -> java.base
info.logger -> java.logging
					

Also, you can visualize the dependencies for all JARs:

C:\1Z0-817>jdeps --module-path app.jar;info-logger.jar --dot-output . *.jar
					

The results are several .dot files in the current directory, we check the summary.dot:

digraph "summary" {
  "app"                                              -> "info.logger";
  "app"                                              -> "java.base (java.base)";
  "app"                                              -> "java.logging (java.logging)";
  "info.logger"                                      -> "java.base (java.base)";
  "info.logger"                                      -> "java.logging (java.logging)";
}
					

You can visualize module dependency graph (e.g. at http://www.webgraphviz.com/):

Figure 6.4. Java application module dependency graph

Java application module dependency graph


Cyclic dependencies

Cyclic dependencies between modules can be recognized by java compiler:

C:\1Z0-817
│
├───modA
│       module-info.java
│
└───modB
        module-info.java
					

module modA {
    requires modB;
}
					

module modB {
    requires modA;
}
					

C:\1Z0-817>javac --module-source-path . modA\module-info.java -d modA
.\modB\module-info.java:2: error: cyclic dependence involving modA
    requires modA;
             ^
error: cannot access module-info
  cannot resolve modules
modA\module-info.java:1: error: module not found: modB
module modA {
^
3 errors
					

Imagine we have the following application:

C:\1Z0-817
├───modA
│   │   module-info.java
│   │
│   └───pkgA
│           ClassA.java
│
└───modB
    │   module-info.java
    │
    └───pkgB
            ClassB.java
					

module modA {
    requires modB;
    exports pkgA;
}
					

package pkgA;

import pkgB.ClassB;

public class ClassA {
    public void methodA1() {    
        new ClassB().methodB2();        
    }
    
    public void methodA2() {
        
    }
}					
					

module modB {
    requires modA;
    exports pkgB;
}
					

package pkgB;

import pkgA.ClassA;

public class ClassB {
    public void methodB1() {
        new ClassA().methodA2();
    }
    
    public void methodB2() {
    }    
}
					

Figure 6.5. Java application cyclic dependency graph

Java application cyclic dependency graph


The solutions can be:

Let's try the second solution:

Add third modC module and refactor all interfaces from class methods which create dependencies:

C:\1Z0-817\modC
│   module-info.java
│
└───pkgC
        InterfaceA.java
        InterfaceB.java
					

module modC {
    exports pkgC;
}
					

package pkgC;

public interface InterfaceA {
    public void methodA1();
    public void methodA2();
}

					

package pkgC;

public interface InterfaceB {
    public void methodB1();
    public void methodB2();    
}
					

Now, refactor modA and modB, to depend not on each other, but both depend on modC.

module modA {
    requires modC;
    exports pkgA;
}
					

package pkgA;

import pkgC.InterfaceA;
import pkgC.InterfaceB;

public class ClassA implements InterfaceA {
    
    InterfaceB bIntf; 
    
    public ClassA(InterfaceB bIntf) {
        this.bIntf = bIntf;
    }
    
    @Override
    public void methodA1() {    
        bIntf.methodB2();
    }
    
    @Override
    public void methodA2() {        
    }
}
					

module modB {
    requires modC;
    exports pkgB;
}
					

package pkgB;

import pkgC.InterfaceA;
import pkgC.InterfaceB;

public class ClassB implements InterfaceB {
    
    InterfaceA aIntf;
    
    public ClassB(InterfaceA aIntf) {
        this.aIntf = aIntf;
    }
    
    @Override
    public void methodB1() {
        aIntf.methodA2();
    }
    
    @Override
    public void methodB2() {
    }
}
					

Now you can compile all three modules:

javac --module-source-path . modC\module-info.java modC\pkgC\InterfaceA.java modC\pkgC\InterfaceB.java -d .
javac --module-source-path . modA\module-info.java modA\pkgA\ClassA.java -d .
javac --module-source-path . modB\module-info.java modB\pkgB\ClassB.java -d .
					

Figure 6.6. Java application without cyclic dependency

Java application without cyclic dependency


Create modular JARs:

jar cvf modA.jar -C modA .
jar cvf modB.jar -C modB .
jar cvf modC.jar -C modC .
					

Inspect dependency graph visually:

C:\1Z0-817>jdeps --module-path modA.jar;modB.jar;modC.jar --dot-output . *.jar
					

Figure 6.7. Java application without cyclic dependency

Java application without cyclic dependency


Professional hosting         Exam 1Z0-817: Upgrade OCP Java 6, 7 & 8 to Java SE 11 Developer Quiz     Exam 1Z0-810: Upgrade to Java SE 8 Programmer Quiz