An introduction to Z2 Java components

The one, probably most essential component type, is that of Java components. Per se, modules in Z2 are not "Java modules" (unlike in OSGi for example). From its underlying model, Java code is handled like any other component resource. There is however important conventions that do make Java components special.

If you have not done so, it will be best to read Z2_Modules_intro first to understand the Z2 modularization approach.

Java components play an important role in Z2 modularization, as re-use of code is one of the driving motivations for modularization. Z2 offers a moderately sophisticated way of re-use via inclusion or class loading delegation that is still simple enough that typical Java and Java EE approaches still work without painful adaptations and that you can - in many cases - ignore the more complex, problematic situations.

Conventions

A majority of component types imply the presence of some implementation code or "class path stored" resources: A Web application may require some Servlet implementation or a Tag Library. A Datasource names a JDBC implementation class, a Spring application context needs to find Bean implementation types.

Therefore Z2 implements a module level convention (and this is actually the only place that the module granularity matters) that is used by component types to identify "the default source of implementation code":

Given a component <module>/<name>, when requiring to retrieve implementation types, look for the Java component <module>/java.

That convention is implemented in JavaComponentUtil and used all over the place.

Part of that convention is that components in the module use the implementation or "private" loader of the module's Java component to look for types or class path resources. That is, components in the module are privileged to refer to implementation code that may not be visible to components of other modules (see also IJavaComponent).

This technically slim convention makes modules a place to implement encapsulation and information hiding.

API and Implementation

Z2 supports a separation of API definitions, i.e. Java types or class path resources that may be referenced from other Java components, and implementation types or class path resources that are hidden from other Java components. This is reflected in the folder structure of Java components. You do however have the choice of ignoring this separation by using an "API vs. Implementation"-indifferent folder structure that behaves as if everything is an API.

Before explaining the details of the folder structure, here is some rational:

Ideally modules implement some coherent set of functions hat are exposed via interfaces that hide implementation details. Within the module implementation types may do however seen fit best to implement the promised interfaces. By hiding the implementation from direct access by other modules, the module contract is required to be restricted to the exposed interfaces and you reserve the freedom to modify the implementation without breaking your module's contract.

Another aspect that is in reality at least as important is that APIs effectively (in reality) imply a transitive closure that spans everything needed by the API. Without a clean separation of API and implementation, you do effectively expose also 3rd party implementations used in your implementation which can easily become a major headache due to version conflicts and accidental re-use by users of your API.

There are reasons to not separate API and implementation in a module however. For example:

  • You have an extremely pure or small setup in which there is no possibility of version conflicts or accidental re-use
  • You separate on the module level, i.e. some modules are pure API modules, while other modules are pure implementation modules, and you do not care about hiding

We do generally recommend to split API from implementation and use the corresponding folder structure, even some folders stay empty.

On top of API and implementation, Z2 Java components support test code. Test code is part of the component's implementation code if and only if Z2 runs in development mode (i.e. the system property com.zfabrik.mode is set to development)

Java component folder structure

Typically, Java components are defined in a folder <module name>/java. In the rare case that a Java component does not provide definitions on its own, a Java component can be declared as <module name>/java.properties. The folder structure of Java components assigns meaning to the following folders:

z.properties The component descriptor. Typically this looks like
com.zfabrik.component.type=com.zfabrik.java
java.publicReferences=<java component 1>, <java component 2>,...
java.privateReferences=<java component n>, <java component n+1>,...
src.api Public type definitions that will be compiled and exposed so that they are visible by other Java components. See below.
bin.api/lib JAR libraries to be added to the public types of the Java Component.
bin.api/classes Pre-compiled Java code, i.e. .class files and other resources to be added to the public types of the Java Component.
src Equivalent to src.api and may be combined
bin/lib Equivalent to bin.api/lib and may be combined
bin/classes Equivalent to bin.api/classes and may be combined
src.impl Type definitions that will be compiled and made available for use within the project (see above) but will not be exposed to other Java components. See below.
bin.impl/lib JAR libraries to be added to the private types of the Java Component.
bin.impl/classes Pre-compiled Java code, i.e. .class files and other resources to be added to the private types of the Java Component.
src.test Type definitions that will be compiled and made available for use just like the types defined in src.impl. In contrast to src.impl, the source files in src.test will be ignored unless the runtime runs in development mode, that is the system property Foundation.MODE is set to "development".
bin.test/lib JAR libraries to be added to the private types of the Java Component iff running in development mode.
bin.test/classes Pre-compiled Java code, i.e. .class files and other resources to be added to the private types of the Java Component iff running in development mode.

Java components may "include" components of type com.zfabrik.files and com.zfabrik.java. This can be useful for libraries that cannot be shared by just re-using the classes but instead require to be used within the using component's context, e.g. to load classes by name that would not be within the library's scope. This is true for older versions of the Spring framework for example.

Files components may be included as API or impl provisioning depending on the reference used. The included source files or pre-compiled binaries are expected to comply to the following folder structure:

src Source files that require compilation before execution.
bin/lib JAR libraries
bin/classes Pre-compiled Java code, i.e. .class files and other resources

Referencing and including Java resources from other modules

Via the following component properties, re-use of Java definitions from other Java components can be controlled:

java.publicReferences Points to other java components whose public types will be shared with this one (and maybe others).
Everything referenced as public reference will be visible to the public interface of the referencing component as well as to all referencing the referencing component. In other words: References are transitive. In particular, anything required to compile the public types of a Java component must be referenced via this reference property.
Components may be specified as a comma-separated list. Component names that have no "/" will be defaulted by appending "/java".
java.publicIncludes Points to com.zfabrik.files or com.zfabrik.java components that must have a bin (or alternatively a bin.api, for Java components) folder that will be included into this java component's public java resources. The component may also have a src (or alternatively src.api, for Java components) folder that will be copied before compilation into src.api.
java.privateReferences Points to other java components whose public types will be shared with this one (and maybe others) Nothing referenced as private reference will be automatically exposed to the public interface of the referencing component nor to other components. Anything needed to compile the private types of a Java component, must be referenced as a public reference, be part of the public types of that component, or be referenced via this reference property.
In other words: The private types automatically see the public types and transitively anything referenced publicly as described above. In addition, to use more types in the "private implementation section" of a Java component, types that will not be exposed to referencing components, use this reference property.
Components may be specified as a comma-separated list. Component names that have no "/" will be defaulted by appending "/java".
java.privateIncludes Points to com.zfabrik.files or com.zfabrik.java components that must have a bin (or alternatively a bin.api, for Java components) folder that will be included into this java component's private java resources. The component may also have a src (or alternatively src.api, for Java components) folder that will be copied before compilation into src.impl.
java.testReferences Points to another java component whose public types will be shared with this one (and maybe others) if the execution mode, as defined by the system property Foundation.MODE is set to "development". Test references extend the private references. In conjunction with the tests source folder this allows to add test code and corresponding dependencies that will be ignored by the runtime unless running in development mode.
java.testIncludes Points to com.zfabrik.files or com.zfabrik.java components that must have a bin (or alternatively a bin.api, for Java components) folder that will be included into this java component's test java resources. The component may also have a src (or alternatively src.api, for Java components) folder that will be copied before compilation into src.test.

The following picture illustrates the relationships established via references and includes:

Classloading in Z2

The class loader concept of the Java platform provides a powerful name spacing mechanism on the type system. While in the beginning that seems to be of little concern, in more complex scenarios isolation within the type system in conjunction of sharing of types between modules of a solution becomes the catalyst of successful modularization.

The class loading system in Z2 is based on a delegate-first, multi-delegation scheme. Effectively, a Java component will have two class loaders at runtime. One for the API, one for the Implementation and both ask their delegation targets (other class loaders) first before searching local resources.

The API class loader will have delegation loaders corresponding to all Java components identified by the public references. The implementation class loader will have the API class loader as parent and delegation loaders corresponding to all Java components identified by the private references of the Java component (see above).

Java references that lead to cyclic dependencies between Java modules are not supported.

More Java component configuration

Apart from refrences and includes, the following configuration properties may be used

com.zfabrik.component.type com.zfabrik.java
java.compile.order The compile order must be defined in java components that also contain non-java sources - e.g. scala. This property can be omitted for pure java components, otherwise one has to define all compilers in the right order - e.g: scala, java

References

java.png (82.4 KB) Henning Blohm, 21.09.2012 11:21