Java Modularity

Lakukan tugas rumah & ujian kamu dengan baik sekarang menggunakan Quizwiz!

Another elephant in the room with regards to accessibility rules is reflection. Before the module system, an interesting but dangerous method called setAccessible was available on all reflected objects. By calling setAccessible(true), any element (regardless of whether it is public or private) becomes accessible. This method is still available but now abides by the same rules as discussed previously. It is no longer possible to invoke setAccessible on an arbitrary element exported from another module and expect it to work as before. Even reflection cannot break strong encapsulation.

How does reflection work in regards to accessibility?

Encapsulation of types can be achieved by using a combination of packages and access modifiers (such as private, protected, or public). By making a class protected, for example, you can prevent other classes from accessing it unless they reside in the same package. That raises an interesting question: what if you want to access that class from another package in your component, but still want to prevent others from using it? There's no good way to do this. You can, of course, make the class public. But, public means public to every other type in the system, meaning no encapsulation.

How encapsulation achieved before Java 9?

A module has a name, it groups related code and possibly other resources, and is described by a module descriptor. The module descriptor lives in a file called module-info.java. Modules live in a global namespace; therefore, module names must be unique. As with package names, you can use conventions such as reverse DNS notation (e.g., com.mycompany.project.somemodule) to ensure uniqueness for your own modules. A module descriptor always starts with the module keyword, followed by the name of the module. Then, the body of module-info.java describes other characteristics of the module, if any.

How is a module defined?

A dependency is declared with the requires keyword followed by a module name.

How is dependency declared in a module?

When designing for reuse, you have two main drivers: 1. Adhering to the Unix philosophy of doing only one thing and doing it well. 2. Minimizing the number of dependencies the module has itself. Otherwise, you're burdening all reusing consumers with those transitive dependencies.

How many drivers are there when designing for reuse?

A lean module is as independent as possible, avoiding dependencies on other modules where possible. Nothing is more discouraging than to see a load of (transitive) dependencies being added to your system because you want to use a specific module.

How many other modules does your module needs to accomplish its goals?

Consuming a service in the Java module system requires two steps. The first step is adding a uses clause to module-info.java in the CLI module: module easytext.cli { requires easytext.analysis.api; uses easytext.analysis.api.Analyzer; }

How to consume services in Java module system?

Exposing service implementations to another module without exporting implementation classes is not possible without special support from the module system. The Java module system allows for a declarative description of providing and consuming services in module-info.java.

How to expose service implementations to another module without exporting implementation classes?

java --list-modules

How to get full list of platform modules?

You can inspect the module declarations of platform modules with --describe-module.

How to inspect the module declarations of platform modules?

Readability is not transitive by default.

Is readability transitive by default?

Strong encapsulation A module must be able to conceal part of its code from other modules. By doing so, a clear line is drawn between code that is publicly usable and code that is deemed an internal implementation detail. This prevents accidental or unwanted coupling between modules: you simply cannot use what has been encapsulated. Consequently, encapsulated code may change freely without affecting users of the module. Well-defined interfaces Encapsulation is fine, but if modules are to work together, not everything can be encapsulated. Code that is not encapsulated is, by definition, part of the public API of a module. Since other modules can use this public code, it must be managed with great care. A breaking change in nonencapsulated code can break other modules that depend on it. Therefore, modules should expose well-defined and stable interfaces to other modules. Explicit dependencies Modules often need other modules to fulfill their obligations. Such dependencies must be part of the module definition, in order for modules to be self-contained. Explicit dependencies give rise to a module graph: nodes represent modules, and edges represent dependencies between modules. Having a module graph is important for both understanding an application and running it with all necessary modules. It provides the basis for a reliable configuration of modules.

What are core tenets of Modules?

In some cases, you'll want to expose a package only to certain other modules. You can do this by using qualified exports in the module descriptor. An example of a qualified export can be found in the java.xml module: module java.xml { ... exports com.sun.xml.internal.stream.writers to java.xml.ws ... } Here we see a platform module sharing useful internals with another platform module. The exported package is accessible only by the modules specified after to. Multiple module names, separated by a comma, can be provided as targets for a qualified export. Any module not mentioned in this to clause cannot access types in this package, even when they read the module.

What are qualified exports in the module descriptor?

A module system is concerned with the large-scale structure of whole applications. Turning an inner class into a lambda is a fairly small and localized change within a single class. Modularizing an application affects design, compilation, packaging, deployment, and so on.

What is the fundamental difference between a feature like lambdas and the Java module system?

The most essential platform module in the modular JDK is java.base. It exposes packages such as java.lang and java.util, which no other module can do without.

What is the most essential platform module in the modular JDK?

Explicit dependencies are where things start to fall apart. Yes, Java does have explicit import statements. Unfortunately, those imports are strictly a compile-time construct. Once you package your code into a JAR, there's no telling which other JARs contain the types your JAR needs to run. In fact, this problem is so bad, many external tools evolved alongside the Java language to solve this problem such as Maven.

What is the problem with explicit dependencies in Java before Java 9?

Typically, the service type is an interface. However, it could also be an abstract or even concrete class; there is no inherent technical limitation. Also, the service type is meant to be used by service consumers directly. It's also possible to expose a service type that acts like a factory or proxy. If, for example, Analyzer instances would be expensive to instantiate, or extra steps or arguments are required for initialization, the service type could be more akin to the AnalyzerFactory. This approach allows the consumer to be more in control of the instantiation.

What is the type of the service?

Sometimes you do want read relations to be transitive—for example, when a type in an exported package of module M1 refers to a type from another module M2. In that case, modules requiring M1 and thereby referencing types from M2 cannot be used without reading M2 as well.

What is transitive readability?

In general, we can distinguish several axes you can align with when designing modules: Comprehension Modules and their relations reflect the overall structure and intent of the system. Whenever someone looks at the codebase without prior knowledge, the high-level structure and functionality is immediately apparent. The module structure guides developers through the system when looking for specific functionality. Changeability Requirements change constantly. By using modules to encapsulate decisions that are likely to change, the impact of change decreases. Two systems with similar functionality but different anticipated areas of change may have different optimal module boundaries. Reuse Modules are an ideal unit of reuse. To increase reusability, modules should be narrowly focused and as independent as possible. Reusable modules can be composed in many ways across different applications. Teamwork Sometimes you want to use module boundaries to clearly divide work across multiple teams. Rather than using technical considerations, you align module boundaries with organizational boundaries.

What makes a good module?

1. Modularize the JDK itself. 2. Offer a module system for applications to use. These goals are closely related. Modularizing the JDK is done by using the same module system that we, as application developers, can use in Java 9. The module system introduces a native concept of modules into the Java language and runtime. Modules can either export or strongly encapsulate packages. Furthermore, they express dependencies on other modules explicitly.

What were two main goals while designing the Java Platform Module system?

jlink --module-path mods/:$JAVA_HOME/jmods \ --add-modules helloworld \ --launcher hello=helloworld \ --output helloworld-image

Write a hello world jlink example

Services are expressed both in module descriptors and in code by using the ServiceLoader API. In that sense, using services is intrusive: you need to design your application to use them.

How are services expressed both in module descriptors and in code?

When thinking about the measure of a module, take into account two metrics: the size of its public surface area and of its internal implementation. Simplifying and minimizing the publicly exported part of your module is beneficial for two reasons. First, a simple and small API is easier to use than a large and convoluted one. Users of the module are not burdened with unnecessary details. The whole point of modularity is to break down concerns into manageable chunks. Second, minimizing the public part of a module reduces the liability of the module's maintainer. You don't have to support what others can't access, leaving the module authors free to change internal details without grave consequences. The less is revealed, the less is relied upon by consumers, and the more stable an API can be. Whatever is put in the exported part of a module becomes a contract between the module producer and its consumers. That leaves the other metric: the measure of the nonexported part of a module. Here it makes less sense to talk about raw size as with the public part of a module. Again, a module's private implementation should be as big as it needs to be to fulfill its API contract.

How big should a module be?

Readability relations are about which modules read other modules. However, if you read a module, this doesn't mean you can access everything from its exported packages. Normal Java accessibility rules are still in play after readability has been established. Accessibility is enforced at compile- and run-time. Combining accessibility and readability provides the strong encapsulation guarantees we so desire in a module system. The question of whether you can access a type from module M2 in module M1 becomes twofold: 1. Does M1 read M2? 2. If yes, is the type accessible in the package exported by M2? Only public types in exported packages are accessible in other modules. If a type is in an exported package but not public, traditional accessibility rules block its use. If it is public but not exported, the module system's readability rules prevent its use. Violations at compile-time result in a compiler error, whereas violations at run-time result in IllegalAccessError.

How does accessibility work between modules?

The classpath is used by the Java runtime to locate classes. In our example, we run Main, and all classes that are directly or indirectly referenced from this class need to be loaded at some point. You can view the classpath as a list of all classes that may be loaded at runtime. While there is more to it behind the scenes, this view suffices to understand the issues with the classpath. There's no notion of JARs or logical grouping anymore. All classes are sequenced into a flat list, in the order defined by the -classpath argument. When the JVM loads a class, it reads the classpath in sequential order to find the right one. As soon as the class is found, the search ends and the class is loaded. What if a class cannot be found on the classpath? Then you will get a run-time exception. Because classes are loaded lazily, this could be triggered when some unlucky user clicks a button in your application for the first time. The JVM cannot efficiently verify the completeness of the classpath upon starting. There is no way to tell in advance whether the classpath is complete, or whether you should add another JAR. More insidious problems arise when duplicate classes are on the classpath. Let's say you try to circumvent the manual setup of the classpath. Instead, you let Maven construct the right set of JARs to put on the classpath, based on the explicit dependency information in POMs. Since Maven resolves dependencies transitively, it's not uncommon for two versions of the same library (say, Guava 19 and Guava 18) to end up in this set, through no fault of your own. Now both library JARs are flattened into the classpath, in an undefined order. Whichever version of the library classes comes first is loaded. However, other classes may expect a class from the (possibly incompatible) other version. Again, this leads to run-time exceptions. In general, whenever the classpath contains two classes with the same (fully qualified) name, even if they are completely unrelated, only one "wins."

What are some problems with classpath before Java 9?

Reliable configuration The module system checks whether a given combination of modules satisfies all dependencies before compiling or running code. This leads to fewer run-time errors. Strong encapsulation Modules explicitly choose what to expose to other modules. Accidental dependencies on internal implementation details are prevented. Scalable development Explicit boundaries enable teams to work in parallel while still creating maintainable codebases. Only explicitly exported public types are shared, creating boundaries that are automatically enforced by the module system. Security Strong encapsulation is enforced at the deepest layers inside the JVM. This limits the attack surface of the Java runtime. Gaining reflective access to sensitive internal classes is not possible anymore. Optimization Because the module system knows which modules belong together, including platform modules, no other code needs to be considered during JVM startup. It also opens up the possibility to create a minimal configuration of modules for distribution. Furthermore, whole-program optimizations can be applied to such a set of modules. Before modules, this was much harder, because explicit dependency information was not available and a class could reference any other class from the classpath.

What are the most important benefits of the Java Platform Module System?

A module descriptor can also contain exports statements. Strong encapsulation is the default for modules. Only when a package is explicitly exported, like java.util.prefs in this example, can it be accessed from other modules. Packages inside a module that are not exported are inaccessible from other modules by default. Other modules cannot refer to types in encapsulated packages, even if they have a dependency on the module.

What does exports statement do in a module descriptor?

The provides with syntax declares that this module provides an implementation of the Analyzer interface with the ColemanAnalyzer as an implementation class. Both the service type (after provides) and the implementation class (after with) must be fully qualified type names. Most important, the package containing the ColemanAnalyzer implementation class is not exported from this provider module. module easytext.analysis.coleman { requires easytext.analysis.api; provides javamodularity.easytext.analysis.api.Analyzer with javamodularity.easytext.analysis.coleman.ColemanAnalyzer; }

What does provides do in module declaration?

The implicit dependency on java.base may be added to a module descriptor. Doing so adds no value, similar to how you can (but generally don't) add "import java.lang.String" to a class using strings.

What happens when you add java.base to a module descriptor?

At its heart, modularization is the act of decomposing a system into self-contained but interconnected modules. Modules are identifiable artifacts containing code, with metadata describing the module and its relation to other modules. Ideally, these artifacts are recognizable from compile-time all the way through run-time. An application then consists of multiple modules working together.

What is Modularity?

An optional linking phase is introduced with Java 9, between the compilation and run-time phases. With a new tool called jlink, you can create a runtime image containing only the necessary modules to run an application.

What is jlink?

An important new concept when reasoning about dependencies between modules is readability. Reading another module means you can access types from its exported packages. You set up readability relations between modules through requires clauses in the module descriptor. By definition, every module reads itself. A module that requires another module reads the other module.

What is readability when reasoning about dependencies between modules?

Even though module path sounds quite similar to classpath, they behave differently. The module path is a list of paths to individual modules and directories containing modules. Each directory on the module path can contain zero or more module definitions, where a module definition can be an exploded module or a modular JAR file. An example module path containing all three options looks like this: out/:myexplodedmodule/:mypackagedmodule.jar. All modules inside the out directory are on the module path, in conjunction with the module myexplodedmodule (a directory) and mypackagedmodule (a modular JAR file).

What is the difference between module path and class path?

A normal requires allows a module to access types in exported packages from the required module only. requires transitive means the same and more. In addition, any module requiring java.sql will now automatically be requiring java.logging and java.xml. That means you get access to the exported packages of those modules as well by virtue of these implied readability relations. With requires transitive, module authors can set up additional readability relations for users of the module. module java.sql { requires transitive java.logging; requires transitive java.xml; exports java.sql; exports javax.sql; exports javax.transaction.xa; } From the consumer side, this makes it easier to use java.sql. When you require java.sql, you get access to the exported packages java.sql, javax.sql, and javax.transaction.xa (which are all exported by java.sql directly), but also to all packages exported by modules java.logging and java.xml. It's as if java.sql re-exports those packages for you, courtesy of the implied readability relations it sets up with requires transitive.

What is the difference between requires and requires transitive?


Set pelajaran terkait

test 4 developmental psych. (test)

View Set

Vocabulary Workshop Level D Unit 7 Answers

View Set

From Ideas to Implementation - Module 2

View Set

Chapter 2: evaluating nutrition information

View Set

Gazdasági alapismeretek 9. - fogalmak

View Set