Movatterモバイル変換


[0]ホーム

URL:


The State of the Module System

Automatic Edition

Mark Reinhold

2016/3/8 08:18 -0800 [e36c992f7fd1]
Copyright © 2016 Oracle and/or its affiliates · All Rights Reserved
This version:http://openjdk.java.net/projects/jigsaw/spec/sotms/2016-03-08
Latest version:http://openjdk.java.net/projects/jigsaw/spec/sotms/
Comments to:jpms-spec-comments@openjdk.java.net

This document is slightly out of date. None of the fundamentalconcepts has changed but therequires public directive has beenrenamed torequires transitive, and several additionalcapabilities have been added. An update is in preparation and will be postedhere when ready.

This is an informal overview of enhancements to the JavaSE Platform prototyped inProject Jigsaw and proposed forJSR376: The Java Platform Module System. A relateddocument describes enhancements to JDK-specific tools and APIs,which are outside the scope of the JSR.

As describedin the JSR, the specific goals of themodule system are to provide

These features will benefit application developers, library developers,and implementors of the Java SE Platform itself directly and, also,indirectly, since they will enable a scalable platform, greater platformintegrity, and improved performance.

Contents

1Defining modules
Module declarations ·Module artifacts ·Module descriptors ·Platform modules
2Using modules
The module path ·Resolution ·Readability ·Accessibility ·Implied readability
3Compatibility & migration
The unnamed module ·Bottom-up migration ·Automatic modules ·Bridges to the class path
4Services
5Advanced topics
Reflection ·Reflective readability ·Class loaders ·Unnamed modules ·Layers ·Qualified exports
Summary
Acknowledgements

This is the second edition of this document. Relative to theinitialedition this edition introduces material oncompatibility and migration, revises thedescription ofreflective readability,reorders the text to improve the flow of the narrative, and is organizedinto a two-level hierarchy of sections and subsections for easiernavigation.

There are still manyopen issues in the design, the resolutionsof which will be reflected in future versions of this document.

1

Defining modules

In order to provide reliable configuration and strong encapsulation in away that is both approachable to developers and supportable by existingtool chains we treat modules as a fundamental new kind of Java programcomponent. Amodule is a named, self-describing collection of code anddata. Its code is organized as a set of packages containing types,i.e., Java classes and interfaces; its data includes resources andother kinds of static information.

1.1

Module declarations

A module’s self-description is expressed in itsmodule declaration, anew construct of the Java programming language. The simplest possiblemodule declaration merely specifies the name of its module:

module com.foo.bar { }

One or morerequires clauses can be added to declare that the moduledepends, by name, upon some other modules, at both compile time and runtime:

module com.foo.bar {requires org.baz.qux;}

Finally,exports clauses can be added to declare that the module makesall, and only, the public types in specific packages available for use byother modules:

module com.foo.bar {    requires org.baz.qux;exports com.foo.bar.alpha;exports com.foo.bar.beta;}

If a module’s declaration contains noexports clauses then it will notexport any types at all to any other modules.

The source code for a module declaration is, by convention, placed in afile namedmodule-info.java at the root of the module’s source-filehierarchy. The source files for thecom.foo.bar module,e.g., mightinclude:

module-info.javacom/foo/bar/alpha/AlphaFactory.javacom/foo/bar/alpha/Alpha.java...

A module declaration is compiled, by convention, into a file namedmodule-info.class, placed similarly in the class-file output directory.

Module names, like package names, must not conflict. The recommended wayto name a module is to use the reverse-domain-name pattern that has longbeen recommended for naming packages. The name of a module will,therefore, often be a prefix of the names of its exported packages, butthis relationship is not mandatory.

A module’s declaration does not include a version string, nor constraintsupon the version strings of the modules upon which it depends. This isintentional: It isnot a goal of the module system to solve theversion-selection problem, which is best left to build tools andcontainer applications.

Module declarations are part of the Java programming language, ratherthan a language or notation of their own, for several reasons. One ofthe most important is that module information must be available at bothcompile time and run time in order to achievefidelity acrossphases,i.e., to ensure that the module system works in thesame way at both compile time and run time. This, in turn, allows manykinds of errors to be prevented or, at least, reported earlier—atcompile time—when they are easier to diagnose and repair.

Expressing module declarations in a source file which is compiled, alongwith the other source files in a module, into a class file forconsumption by the Java virtual machine is the natural way in which toestablish fidelity. This approach will be immediately familiar todevelopers, and not difficult for IDEs and build tools to support. AnIDE, in particular, could suggest an initial module declaration for anexisting component by synthesizingrequires clauses from informationalready available in the component’s project description.

1.2

Module artifacts

Existing tools can already create, manipulate, and consume JAR files, sofor ease of adoption and migration we definemodular JAR files. Amodular JAR file is like an ordinary JAR file in all possible ways,except that it also includes amodule-info.class file in its rootdirectory. A modular JAR file for the abovecom.foo.bar module,e.g., might have the content:

META-INF/META-INF/MANIFEST.MFmodule-info.classcom/foo/bar/alpha/AlphaFactory.classcom/foo/bar/alpha/Alpha.class...

A modular JAR file can be used as a module, in which case itsmodule-info.class file is taken to contain the module’s declaration.It can, alternatively, be placed on the ordinary class path, in whichcase itsmodule-info.class file is ignored. Modular JAR files allowthe maintainer of a library to ship a single artifact that works both asa module, on Java SE 9 and later, and as a regular JAR file on the classpath, on all releases. We expect that implementations of Java SE 9 whichinclude ajar tool will enhance that tool to make it easy to createmodular JAR files.

For the purpose of modularizing the Java SE Platform’s referenceimplementation, the JDK, we will introduce a new artifact format thatgoes beyond JAR files to accommodate native code, configuration files,and other kinds of data that do not fit naturally, if at all, into JARfiles. This format leverages another advantage of expressing moduledeclarations in source files and compiling them into class files, namelythat class files are independent of any particular artifact format.Whether this new format, provisionally named “JMOD,” should bestandardized is an open question.

1.3

Module descriptors

A final advantage of compiling module declarations into class files isthat class files already have aprecisely-defined and extensibleformat. We can thus considermodule-info.class files in a moregeneral light, asmodule descriptors which include the compiled formsof source-level module declarations but also additional kinds ofinformation recorded in class-file attributes which are inserted afterthe declaration is initially compiled.

An IDE or a build-time packaging tool,e.g., can insert attributescontaining documentary information such as a module’s version, title,description, and license. This information can be read at compile timeand run time via the module system’s reflection facilities for use indocumentation, diagnosis, and debugging. It can also be used bydownstream tools in the construction of OS-specific package artifacts. Aspecific set of attributes will be standardized but, since the Javaclass-file format is extensible, other tools and frameworks will be ableto define additional attributes as needed. Non-standard attributes willhave no effect upon the behavior of the module system itself.

1.4

Platform modules

The Java SE 9 Platform Specification will use the module system to dividethe platform into a set of modules. An implementation of the Java SE 9Platform might contain all of the platform modules or, possibly, justsome of them.

The only module known specifically to the module system, in any case, isthe base module, which is namedjava.base. The base module defines andexports all of the platform’s core packages, including the module systemitself:

module java.base {    exports java.io;    exports java.lang;    exports java.lang.annotation;    exports java.lang.invoke;    exportsjava.lang.module;    exports java.lang.ref;    exports java.lang.reflect;    exports java.math;    exports java.net;    ...}

The base module is always present. Every other module depends implicitlyupon the base module, while the base module depends upon no othermodules.

The remaining platform modules will share the “java.” name prefix andare likely to include,e.g.,java.sql for database connectivity,java.xml for XML processing, andjava.logging for logging. Modulesthat are not defined in the Java SE 9 Platform Specification butinstead specific to the JDK will, by convention, share the “jdk.” nameprefix.

2

Using modules

Individual modules can be defined in module artifacts, or else built-into the compile-time or run-time environment. To make use of them ineither phase the module system must locate them and, then, determine howthey relate to each other so as to provide reliable configuration andstrong encapsulation.

2.1

The module path

In order to locate modules defined in artifacts the module systemsearches themodule path, which is defined by the host system. Themodule path is a sequence, each element of which is either a moduleartifact or a directory containing module artifacts. The elements of themodule path are searched, in order, for the first artifact that defines asuitable module.

The module path is materially different from the class path, and morerobust. The inherent brittleness of the class path is due to the factthat it is a means to locate individual types in all the artifacts on thepath, making no distinction amongst the artifacts themselves. This makesit impossible to tell, in advance, when an artifact is missing. It alsoallows different artifacts to define types in the same packages, even ifthose artifacts represent different versions of the same logical programcomponent, or different components entirely.

The module path, by contrast, is a means to locate whole modules ratherthan individual types. If the module system cannot fulfill a particulardependence with an artifact from the module path, or if it encounters twoartifacts in the same directory that define modules of the same name,then the compiler or virtual machine will report an error and exit.

The modules built-in to the compile-time or run-time environment,together with those defined by artifacts on the module path, arecollectively referred to as the universe ofobservable modules.

2.2

Resolution

Suppose we have an application that uses the abovecom.foo.bar moduleand also the platform’sjava.sql module. The module that contains thecore of the application is declared as follows:

module com.foo.app {    requires com.foo.bar;    requires java.sql;}

Given this initial application module, the module systemresolves thedependences expressed in itsrequires clauses by locating additionalobservable modules to fulfill those dependences, and then resolves thedependences of those modules, and so forth, until every dependence ofevery module is fulfilled. The result of this transitive-closurecomputation is amodule graph which, for each module with a dependencethat is fulfilled by some other module, contains a directed edge from thefirst module to the second.

To construct a module graph for thecom.foo.app module, the modulesystem inspects the declaration of thejava.sql module, which is:

module java.sql {    requires java.logging;    requires java.xml;    exports java.sql;    exports javax.sql;    exports javax.transaction.xa;}

It also inspects the declaration of thecom.foo.bar module, alreadyshown above, and also those of theorg.baz.qux,java.logging, andjava.xml modules; for brevity, these last three are not shown heresince they do not declare dependences upon any other modules.

Based upon all of these module declarations, the graph computed for thecom.foo.app module contains the following nodes and edges:

Module graph

In this figure the dark blue lines represent explicit dependencerelationships, as expressed inrequires clauses, while the light bluelines represent the implicit dependences of every module upon the basemodule.

2.3

Readability

When one module depends directly upon another in the module graph thencode in the first module will be able to refer to types in the secondmodule. We therefore say that the first modulereads the second or,equivalently, that the second module isreadable by the first. Thus,in the above graph, thecom.foo.app module reads thecom.foo.bar andjava.sql modules but not theorg.baz.qux,java.xml, orjava.logging modules. Thejava.logging module is readable by thejava.sql module, but no others. (Every module, by definition, readsitself.)

The readability relationships defined in a module graph are the basis ofreliable configuration: The module system ensures that every dependenceis fulfilled by precisely one other module, that the module graph isacyclic, that every module reads at most one module defining a givenpackage, and that modules defining identically-named packages do notinterfere with each other.

Reliable configuration is not just more reliable; it can also be faster.When code in a module refers to a type in a package then that package isguaranteed to be defined either in that module or in precisely one of themodules read by that module. When looking for the definition of aspecific type there is, therefore, no need to search for it in multiplemodules or, worse, along the entire class path.

2.4

Accessibility

The readability relationships defined in a module graph, combined withtheexports clauses in module declarations, are the basis ofstrongencapsulation: The Java compiler and virtual machine consider the publictypes in a package in one module to beaccessible by code in some othermodule only when the first module is readable by the second module, inthe sense defined above, and the first module exports that package. Thatis, if two typesS andT are defined in different modules, andT ispublic, then code inScan accessT if:

  1. S’s module readsT’s module, and
  2. T’s module exportsT’s package.

A type referenced across module boundaries that is not accessible in thisway is unusable in the same way that a private method or field isunusable: Any attempt to use it will cause an error to be reported by thecompiler, or anIllegalAccessError to be thrown by the Java virtualmachine, or anIllegalAccessException to be thrown by the reflectiverun-time APIs. Thus, even when a type is declaredpublic, if itspackage is not exported in the declaration of its module then it willonly be accessible to code in that module.

A method or field referenced across module boundaries is accessible ifits enclosing type is accessible, in this sense, and if the declarationof the member itself also allows access.

To see how strong encapsulation works in the case of the above modulegraph, we label each module with the packages that it exports:

Module graph, with exports

Code in thecom.foo.app module can access public types declared in thecom.foo.bar.alpha package becausecom.foo.app depends upon, andtherefore reads, thecom.foo.bar module, and becausecom.foo.barexports thecom.foo.bar.alpha package. Ifcom.foo.bar contains aninternal packagecom.foo.bar.internal then code incom.foo.app cannotaccess any types in that package, sincecom.foo.bar does not export it.Code incom.foo.app cannot refer to types in theorg.baz.qux packagesincecom.foo.app does not depend upon theorg.baz.qux module, andtherefore does not read it.

2.5

Implied readability

If one module reads another then, in some situations, it should logicallyalso read some other modules.

The platform’sjava.sql module,e.g., depends upon thejava.loggingandjava.xml modules, not only because it contains implementation codethat uses types in those modules but also because it defines types whosesignatures refer to types in those modules. Thejava.sql.Driverinterface, in particular, declares the public method

public Logger getParentLogger();

whereLogger is a type declared in the exportedjava.util.loggingpackage of thejava.logging module.

Suppose,e.g., that code in thecom.foo.app module invokes thismethod in order to acquire a logger and then log a message:

String url = ...;Properties props = ...;Driver d = DriverManager.getDriver(url);Connection c = d.connect(url, props);d.getParentLogger().info("Connection acquired");

If thecom.foo.app module is declared as above then this will not work:ThegetParentLogger method returns aLogger, which is a type declaredin thejava.logging module, which is not readable by thecom.foo.appmodule, and so the invocation of theinfo method in theLogger classwill fail at both compile time and run time because that class, and thusthat method, is inaccessible.

One solution to this problem is to hope that every author of every modulethat both depends upon thejava.sql module and contains code that usesLogger objects returned by thegetParentLogger method remembers alsoto declare a dependence upon thejava.logging module. This approach isunreliable, of course, since it violates the principle of least surprise:If one module depends upon a second module then it is natural to expectthat every type needed to use the first module, even if the type isdefined in the second module, will immediately be accessible to a modulethat depends only upon the first module.

We therefore extend module declarations so that one module can grantreadability to additional modules, upon which it depends, to any modulethat depends upon it. Suchimplied readability is expressed byincluding thepublic modifier in arequires clause. The declarationof thejava.sql module actually reads:

module java.sql {    requirespublic java.logging;    requirespublic java.xml;    exports java.sql;    exports javax.sql;    exports javax.transaction.xa;}

Thepublic modifiers mean that any module that depends upon thejava.sql module will read not only thejava.sql module but also thejava.logging andjava.xml modules. The module graph for thecom.foo.app module, shown above, thus contains two additional dark-blueedges, linked by green edges to thejava.sql module since they areimplied by that module:

Module graph, with implied reads

Thecom.foo.app module can now include code that accesses all of thepublic types in the exported packages of thejava.logging andjava.xml modules, even though its declaration does not mention thosemodules.

In general, if one module exports a package containing a type whosesignature refers to a package in a second module then the declaration ofthe first module should include arequires public dependence upon thesecond. This will ensure that other modules that depend upon the firstmodule will automatically be able to read the second module and, hence,access all the types in that module’s exported packages.

3

Compatibility & migration

Thus far we have seen how to define modules from scratch, package theminto module artifacts, and use them together with other modules that areeither built-in to the platform or also defined in artifacts.

Most Java code was, of course, written prior to the introduction of themodule system and must continue to work just as it does today, withoutchange. The module system can, therefore, compile and run applicationscomposed of JAR files on the class path even though the platform itselfis composed of modules. It also allows existing applications to bemigrated to modular form in a flexible and gradual manner.

3.1

The unnamed module

If a request is made to load a type whose package is not defined in anyknown module then the module system will attempt to load it from theclass path. If this succeeds then the type is considered to be a memberof a special module known asthe unnamed module, so as to ensurethat every type is associated with some module. The unnamed module is,at a high level, akin to the existing concept ofthe unnamedpackage. All other modules have names, of course, so we willhenceforth refer to those asnamed modules.

The unnamed module reads every other module. Code in any type loadedfrom the class path will thus be able to access the exported types of allother readable modules, which by default will include all of the named,built-in platform modules. An existing class-path application thatcompiles and runs on Java SE 8 will, thus, compile and run inexactly the same way on Java SE 9, so long as it only usesstandard, non-deprecated Java SE APIs.

The unnamed module exports all of its packages. This enables flexiblemigration, as we shall see below. It does not, however, mean that codein a named module can access types in the unnamed module. A namedmodule cannot, in fact, even declare a dependence upon the unnamedmodule. This restriction is intentional, since allowing named modules todepend upon the arbitrary content of the class path would make reliableconfiguration impossible.

If a package is defined in both a named module and the unnamed modulethen the package in the unnamed module is ignored. This preservesreliable configuration even in the face of the chaos of the class path,ensuring that every module still reads at most one module defininga given package. If, in our example above, a JAR file on the classpath contains a class file namedcom/foo/bar/alpha/AlphaFactory.classthen that file will never be loaded, since thecom.foo.bar.alphapackage is exported by thecom.foo.bar module.

3.2

Bottom-up migration

The treatment of types loaded from the class path as members of theunnamed module allows us to migrate the components of an existingapplication from JAR files to modules in an incremental, bottom-upfashion.

Suppose,e.g., that the application shown above had originally beenbuilt for Java SE 8, as a set of similarly-named JAR files placed onthe class path. If we run it as-is on Java SE 9 then the types inthe JAR files will be defined in the unnamed module. That module willread every other module, including all of the built-in platform modules;for simplicity, assume those are limited to thejava.sql,java.xml,java.logging, andjava.base modules shown earlier. Thus we obtainthe module graph

Module graph, with class path

We can immediately convertorg-baz-qux.jar into a named module becausewe know that it does not refer to any types in the other two JAR files,so as a named module it will not refer to any of the types that will beleft behind in the unnamed module. (We happen to know this from theoriginal example, but if we did not already know it then we coulddiscover it with the help of a tool such asjdeps.) We writea module declaration fororg.baz.qux, add it to the source code for themodule, compile that, and package the result as a modular JAR file. Ifwe then place that JAR file on the module path and leave the others onthe class path we obtain the improved module graph

Module graph, with class path + org.baz.qux as a module

The code incom-foo-bar.jar andcom-foo-app.jar continues to workbecause the unnamed module reads every named module, which now includesthe neworg.baz.qux module.

We can proceed similarly to modularizecom-foo-bar.jar, and thencom-foo-app.jar, eventually winding up with the intended module graph,shown previously:

Module graph, with implied reads

Knowing what we do about the types in the original JAR files we could, ofcourse, modularize all three of them in a single step. If, however,org-baz-qux.jar is maintained independently, perhaps by an entirelydifferent team or organization, then it can be modularized before theother two components, and likewisecom-foo-bar.jar can be modularizedbeforecom-foo-app.jar.

3.3

Automatic modules

Bottom-up migration is straightforward, but it is not always possible.Even if the maintainer oforg-baz-qux.jar has not yet converted it intoa proper module—or perhaps never will—we might still want tomodularize ourcom-foo-app.jar andcom-foo-bar.jar components.

We already know that code incom-foo-bar.jar refers to types inorg-baz-qux.jar. If we convertcom-foo-bar.jar into the named modulecom.foo.bar but leaveorg-baz-qux.jar on the class path, however,then that code will no longer work: Types inorg-baz-qux.jar willcontinue to be defined in the unnamed module butcom.foo.bar, which isa named module, cannot declare a dependence upon the unnamed module.

We must, then, somehow arrange fororg-baz-qux.jar to appear as a namedmodule so thatcom.foo.bar can depend upon it. We could fork thesource code oforg.baz.qux and modularize it ourselves, but if themaintainer is unwilling to merge that change into the upstream repositorythen we would have to maintain the fork for as long as we might need it.

We can, instead, treatorg-baz-qux.jar as anautomatic module byplacing it, unmodified, on the module path rather than the class path.This will define an observable module whose name,org.baz.qux, isderived from that of the JAR file so that other, non-automatic modulescan depend upon it in the usual way:

Module graph, with org.baz.qux as an automatic module

An automatic module is a named module that is defined implicitly, sinceit does not have a module declaration. An ordinary named module, bycontrast, is defined explicitly, with a module declaration; we willhenceforth refer to those asexplicit modules.

There is no practical way to tell, in advance, which other modules anautomatic module might depend upon. After a module graph is resolved,therefore, an automatic module is made to read every other named module,whether automatic or explicit:

Module graph, with org.baz.qux as an automatic module + read edges

(These new readability edges do create cycles in the module graph, whichmakes it somewhat more difficult to reason about, but we view these as atolerable and, usually, temporary consequence of enabling more-flexiblemigration.)

There is, similarly, no practical way to tell which of the packages in anautomatic module are intended for use by other modules, or by classesstill on the class path. Every package in an automatic module is,therefore, considered to be exported even if it might actually beintended only for internal use:

Module graph, with org.baz.qux exporting all packages

There is, finally, no practical way to tell whether one of the exportedpackages in an automatic module contains a type whose signature refers toa type defined in some other automatic module. If,e.g., we modularizecom.foo.app first, and treat bothcom.foo.bar andorg.baz.qux asautomatic modules, then we have the graph

Module graph, with com.foo.bar + org.baz.qux as automatic modules

It is impossible to know, without reading all of the class files in bothof the corresponding JAR files, whether a public type incom.foo.bardeclares a public method whose return type is defined inorg.baz.qux.An automatic module therefore grants implied readability to all otherautomatic modules:

Module graph, with com.foo.bar granting implied readability to org.baz.qux

Now code incom.foo.app can access types inorg.baz.qux, although weknow that it does not actually do so.

Automatic modules offer a middle ground between the chaos of the classpath and the discipline of explicit modules. They allow an existingapplication composed of JAR files to be migrated to modules from the topdown, as shown above, or in a combination of top-down and bottom-upapproaches. We can, in general, start with an arbitrary set of JAR-filecomponents on the class path, use a tool such asjdeps toanalyze their interdependencies, convert the components whose source codewe control into explicit modules, and place those along with theremaining JAR files, as-is, on the module path. The JAR files forcomponents whose source code we do not control will be treated asautomatic modules until such time as they, too, are converted intoexplicit modules.

3.4

Bridges to the class path

Many existing JAR files can be used as automatic modules, but somecannot. If two or more JAR files on the class path contain types in thesame package then at most one of them can be used as an automatic module,since the module system still guarantees that every named module reads atmost one named module defining a given package and that named modulesdefining identically-named packages do not interfere with each other. Insuch situations it often turns out that only one of the JAR files isactually needed. If the others are duplicates or near-duplicates,somehow placed on the class path by mistake, then one can be used as anautomatic module and the others can be discarded. If, however, multipleJAR files on the class path intentionally contain types in the samepackage then on the class path they must remain.

To enable migration even when some JAR files cannot be used as automaticmodules we enable automatic modules to act as bridges between code inexplicit modules and code still on the class path: In addition to readingevery other named module, an automatic module is also made to read theunnamed module. If our application’s original class path had,e.g.,also contained the JAR filesorg-baz-fiz.jar andorg-baz-fuz.jar,then we would have the graph

Module graph, with other org.baz components left on class path

The unnamed module exports all of its packages, as mentioned earlier, socode in the automatic modules will be able to access any public typeloaded from the class path.

An automatic module that makes use of types from the class path must notexpose those types to the explicit modules that depend upon it, sinceexplicit modules cannot declare dependences upon the unnamed module. Ifcode in the explicit modulecom.foo.app refers to a public type incom.foo.bar,e.g., and the signature of that type refers to a type inone of the JAR files still on the class path, then the code incom.foo.app will not be able to access that type sincecom.foo.appcannot depend upon the unnamed module. This can be remedied by treatingcom.foo.app as an automatic module temporarily, so that its code canaccess types from the class path, until such time as the relevant JARfile on the class path can be treated as an automatic module or convertedinto an explicit module.

4

Services

The loose coupling of program components via service interfaces andservice providers is a powerful tool in the construction of largesoftware systems. Java has long supported services via thejava.util.ServiceLoader class, which locates service providersat run time by searching the class path. For service providers definedin modules we must consider how to locate those modules amongst the setof observable modules, resolve their dependences, and make the providersavailable to the code that uses the corresponding services.

Suppose,e.g., that ourcom.foo.app module uses a MySQL database, andthat a MySQL JDBC driver is provided in an observable module which hasthe declaration

module com.mysql.jdbc {    requires java.sql;    requires org.slf4j;    exports com.mysql.jdbc;}

whereorg.slf4j is a logging library used by the driver andcom.mysql.jdbc is the package that contains the implementation of thejava.sql.Driver service interface. (It is not actually necessary toexport the driver package, but we do so here for clarity.)

In order for thejava.sql module to make use of this driver, theServiceLoader class must be able to instantiate the driver class viareflection; for that to happen, the module system must add the drivermodule to the module graph and resolve its dependences, thus:

Module graph, with JDBC module

To achieve this the module system must be able to identify any uses ofservices by previously-resolved modules and, then, locate and resolveproviders from within the set of observable modules.

The module system could identify uses of services by scanning the classfiles in module artifacts for invocations of theServiceLoader::loadmethods, but that would be both slow and unreliable. That a module usesa particular service is a fundamental aspect of that module’s definition,so for both efficiency and clarity we express that in the module’sdeclaration with auses clause:

module java.sql {    requires public java.logging;    requires public java.xml;    exports java.sql;    exports javax.sql;    exports javax.transaction.xa;uses java.sql.Driver;}

The module system could identify service providers by scanning moduleartifacts forMETA-INF/services resource entries, as theServiceLoader class does today. That a module provides animplementation of a particular service is equally fundamental, however,so we express that in the module’s declaration with aprovides clause:

module com.mysql.jdbc {    requires java.sql;    requires org.slf4j;    exports com.mysql.jdbc;provides java.sql.Driverwith com.mysql.jdbc.Driver;}

Now it is very easy to see, simply by reading these modules’declarations, that one of them uses a service that is provided by theother.

Declaring service-provision and service-use relationships in moduledeclarations has advantages beyond improved efficiency and clarity.Service declarations of both kinds can be interpreted at compile time toensure that the service interface (e.g.,java.sql.Driver) isaccessible to both the providers and the users of a service.Service-provider declarations can be further interpreted to ensure thatproviders (e.g.,com.mysql.jdbc.Driver) actually do implement theirdeclared service interfaces. Service-use declarations can, finally, beinterpreted by ahead-of-time compilation and linking tools to ensure thatobservable providers are appropriately compiled and linked prior to runtime.

For migration purposes, if a JAR file that defines an automatic modulecontainsMETA-INF/services resource entries then each such entry istreated as if it were a correspondingprovides clause in a hypotheticaldeclaration of that module. An automatic module is considered to useevery available service.

5

Advanced topics

The remainder of this document addresses advanced topics which, whileimportant, may not be of interest to most developers.

5.1

Reflection

To make the module graph available via reflection at run time we define aModule class in thejava.lang.reflect package and some related typesin a new package,java.lang.module. An instance of theModule classrepresents a single module at run time. Every type is in a module, soeveryClass object has an associatedModule object, which is returnedby the newClass::getModule method.

The essential operations on aModule object are:

package java.lang.reflect;public final class Module {    public String getName();    public ModuleDescriptor getDescriptor();    public ClassLoader getClassLoader();    public boolean canRead(Module target);    public boolean isExported(String packageName);}

whereModuleDescriptor is a class in thejava.lang.module package,instances of which represent module descriptors; thegetClassLoadermethod returns the module’s class loader; thecanRead method tellswhether the module can read the target module; and theisExportedmethod tells whether the module exports the given package.

Thejava.lang.reflect package is not the only reflection facility inthe platform. Similar additions will be made to the compile-timejavax.lang.model package in order to support annotation processors anddocumentation tools.

5.2

Reflective readability

Aframework is a facility that uses reflection to load, inspect, andinstantiate other classes at run time. Examples of frameworks in theJava SE Platform itself are service loaders, resource bundles, dynamicproxies, and serialization, and of course there are many popular externalframework libraries for purposes as diverse as database persistence,dependency injection, and testing.

Given a class discovered at run time, a framework must be able to accessone of its constructors in order to instantiate it. As things stand,however, that will usually not be the case.

The platform’sstreaming XML parser,e.g.,loads andinstantiates the implementation of theXMLInputFactory service named by the system propertyjavax.xml.stream.XMLInputFactory, if defined, in preference to anyprovider discoverable via theServiceLoader class. Ignoring exceptionhandling and security checks the code reads, roughly:

String providerName    = System.getProperty("javax.xml.stream.XMLInputFactory");if (providerName != null) {    Class providerClass = Class.forName(providerName, false,                                        Thread.getContextClassLoader());    Object ob = providerClass.newInstance();    return (XMLInputFactory)ob;}// Otherwise use ServiceLoader...

In a modular setting the invocation ofClass::forName will continue towork so long as the package containing the provider class is known to thecontext class loader. The invocation of the provider class’s constructorvia the reflectivenewInstance method, however, will not work: Theprovider might be loaded from the class path, in which case it will be inthe unnamed module, or it might be in some named module, but in eithercase the framework itself is in thejava.xml module. That module onlydepends upon, and therefore reads, the base module, and so a providerclass in any other module will be not be accessible to the framework.

To make the provider class accessible to the framework we need to makethe provider’s module readable by the framework’s module. We couldmandate that every framework explicitly add the necessary readabilityedge to the module graph at run time, as in anearlier version of thisdocument, but experience showed that approach to becumbersome and a barrier to migration.

We therefore, instead, revise the reflection API simply to assume thatany code that reflects upon some type is in a module that can read themodule that defines that type. This enables the above example, and othercode like it, to work without change. This approach does not weakenstrong encapsulation: A public type must still be in an exportedpackage in order to be accessed from outside its defining module, whetherfrom compiled code or via reflection.

5.3

Class loaders

Every type is in a module, and at run time every module has a classloader, but does a class loader load just one module? The module system,in fact, places few restrictions on the relationships between modules andclass loaders. A class loader can load types from one module or frommany modules, so long as the modules do not interfere with each other andall of the types in any particular module are loaded by just one loader.

This flexibility is critical to compatibility, since it allows us toretain the platform’s existing hierarchy of built-in class loaders. Thebootstrap and extension class loaders still exist, and are used to loadtypes from platform modules. The application class loader also stillexists, and is used to load types from artifacts found on the modulepath.

This flexibility will also make it easier to modularize existingapplications which already construct sophisticated hierarchies or evengraphs of custom class loaders, since such loaders can be upgraded toload types in modules without necessarily changing their delegationpatterns.

5.4

Unnamed modules

We previously learned that if a type is not defined in a named,observable module then it is considered to be a member of the unnamedmodule, but with which class loader is the unnamed module associated?

Every class loader, it turns out, has its own unique unnamed module,which is returned by the newClassLoader::getUnnamedModule method. Ifa class loader loads a type that is not defined in a named module thenthat type is considered to be in that loader’s unnamed module,i.e.,thegetModule method of the type’sClass object will return itsloader’s unnamed module. The module colloquially referred to as “theunnamed module” is, then, simply the unnamed module of the applicationclass loader, which loads types from the class path when they are inpackages not defined by any known module.

5.5

Layers

The module system does not dictate the relationships between modules andclass loaders, but in order to load a particular type it must somehow beable to find the appropriate loader. At run time, therefore, theinstantiation of a module graph produces alayer, which maps eachmodule in the graph to the unique class loader responsible for loadingthe types defined in that module. Theboot layer is created by theJava virtual machine at startup, by resolving the application’s initialmodule against the observable modules, as discussed earlier.

Most applications, and certainly all existing applications, will neveruse a layer other than the boot layer. Multiple layers can, however, beof use in sophisticated applications with plug-in or containerarchitectures such as application servers, IDEs, and test harnesses.Such applications can use dynamic class loading and the reflectivemodule-system API, thus far described, to load and run hostedapplications that consist of one or more modules. Two additional kindsof flexibility are, however, often required:

A container application can create a new layer for a hosted applicationon top of an existing layer by resolving that application’s initialmodule against a different universe of observable modules. Such auniverse can contain alternate versions of upgradeable platform modulesand other, non-platform modules already present in the lower layer; theresolver will give these alternate modules priority. Such a universe canalso contain different service providers than those already discovered inthe lower layer; theServiceLoader class will load and return theseproviders before it returns providers from the lower layer.

Layers can be stacked: A new layer can be built on top of the boot layer,and then another layer can be built on top of that. As a result of thenormal resolution process the modules in a given layer can read modulesin that layer or in any lower layer. A layer’s module graph can hence beconsidered to include, by reference, the module graphs of every layerbelow it.

5.6

Qualified exports

It is occasionally necessary to arrange for some types to be accessibleamongst a set of modules yet remain inaccessible to all other modules.

Code in the JDK’s implementations of the standardjava.sql andjava.xml modules,e.g., makes use of types defined in the internalsun.reflect package, which is in thejava.base module. In order forthis code to access types in thesun.reflect package we could simplyexport that package from thejava.base module:

module java.base {    ...    exports sun.reflect;}

This would, however, make every type in thesun.reflect packageaccessible to every module, since every module readsjava.base, andthat is undesirable because some of the classes in that package defineprivileged, security-sensitive methods.

We therefore extend module declarations to allow a package to be exportedto one or more specifically-named modules, and to no others. Thedeclaration of thejava.base module actually exports thesun.reflectpackage only to a specific set of JDK modules:

module java.base {    ...exports sun.reflectto        java.corba,        java.logging,        java.sql,        java.sql.rowset,        jdk.scripting.nashorn;}

Thesequalified exports can be visualized in a module graph by addinganother type of edge, here colored gold, from packages to the specificmodules to which they are exported:

Module graph, with qualified exports

The accessibility rules stated earlier are refined as follows: If twotypesS andT are defined in different modules, andT ispublic,then code inS can accessT if:

  1. S’s module readsT’s module, and
  2. T’s module exportsT’s package,either directly toS’s module or to all modules.

We also extend the reflectiveModule class with a method to tellwhether a package is exported to a specific module, rather than to allmodules:

public final class Module {    ...    public boolean isExported(String packageName, Module target);}

Qualified exports can inadvertently make internal types accessible tomodules other than those intended, so they must be used with care. Anadversary could,e.g., name a modulejava.corba in order to accesstypes in thesun.reflect package. To prevent this we can analyze a setof related modules at build time and record, in each module’s descriptor,hashes of the content of the modules that are allowed to depend upon itand use its qualified exports. During resolution we verify, for anymodule named in a qualified export of some other module, that the hash ofits content matches the hash recorded for that module name in the secondmodule. Qualified exports are safe to use in an untrusted environment solong as the modules that declare and use them are tied together in thisway.

Summary

The module system described here has many facets, but most developerswill only need to use some of them on a regular basis. We expect thebasic concepts of module declarations, modular JAR files, the modulepath, readability, accessibility, the unnamed module, automatic modules,and modular services to become reasonably familiar to most Javadevelopers in the coming years. The more advanced features of reflectivereadability, layers, and qualified exports will, by contrast, be neededby relatively few.

Acknowledgements

This document includes contributions from Alan Bateman, Alex Buckley,Mandy Chung, Jonathan Gibbons, Chris Hegarty, Karen Kinnear, and PaulSandoz.


[8]ページ先頭

©2009-2025 Movatter.jp