--- | |
Tapestry IoC Modules | |
--- | |
Tapestry IoC Modules | |
You inform Tapestry about your services and contributions by providing a module builder class. | |
The module builder is a plain Java class. A system of annotations and naming conventions allow | |
Tapestry to determine what services are provided by the module. | |
A module class exists for the following reasons: | |
* To <bind> service interfaces to service implementations | |
* To contribute configuration data <into> services | |
* To <decorate> services by providing <interceptors> around them | |
* To provide explicit code for building a service | |
* To set a default <marker> for all services defined in the module | |
[] | |
A module builder defines builder methods, one for each service provided by the module. | |
<<This page needs to be rewritten or reorganized; using the bind() method is the preferred way to define services, | |
service builder methods are now used in very limited circumstances.>> | |
Service builder methods are public methods. They are often static. Here's a trivial example: | |
+-----------------------------------------------------------------------------------+ | |
package org.example.myapp.services; | |
public class MyAppModule | |
{ | |
public static Indexer build() | |
{ | |
return new IndexerImpl(); | |
} | |
} | |
+-----------------------------------------------------------------------------------+ | |
Any public method (static or instance) whose name starts with "build" is a service builder method, implicitly | |
defining a service within the module. | |
Here we're defining a service around | |
the Indexer service interface (presumably also in the org.example.myapp.services | |
package). | |
Every service has a unique id, used to identify it throughout the Registry of services (the Registry | |
is the combined sum of all services from all modules). If you don't provide an explicit service id, | |
as in this example, the service id is drawn from the return type; this service has an id of "Indexer". | |
You can give a service an explicit id by adding it to the method name: buildIndexer(). This is useful | |
when you do not want the service id to match the service interface name (for example, when you have different | |
services that implement the same interface), or when you need to avoid name collisions on the | |
method name (Java allows only a single method with a given name and set of parameters, even if the return | |
types are differenty, so if you have two different service builder methods that take the same parameters, | |
you should give them explicit service ids in the method name). | |
Tapestry IoC is {{{case.html}case insensitive}}; later we can | |
refer to this service as "indexer" or "INDEXER" or any variation thereof, and connect to | |
this service. | |
Service ids must be unique; if another module contributes a service with the id "Indexer" | |
(or any case variation thereof) a runtime exception will occur when the Registry is created. | |
We could extend this example by adding additional service builder methods, or by showing | |
how to inject dependencies. See {{{service.html#Injecting Dependencies}the service documentation}} | |
for more details. | |
Autobuilding Services | |
An alternate, and usually preferred, way to define a service is via a module's bind() method. The previous | |
example can be rewritten as: | |
+---+ | |
package org.example.myapp.services; | |
import org.apache.tapestry.ioc.ServiceBinder; | |
public class MyAppModule | |
{ | |
public static void bind(ServiceBinder binder) | |
{ | |
binder.bind(Indexer.class, IndexerImpl.class); | |
} | |
} | |
+----+ | |
The {{{service.html}service}} documentation goes into much greater detail about autobuilding of services. In most cases, | |
autobuilding is the <preferred> approach. | |
Generally speaking, you should always bind and autobuild your services. The only exceptions are when: | |
* You wish to do more than just instantiate a class; for example, to register the class as an event listener with some other service. | |
* There is <no implementation class>; in some cases, you can create your implementation on the fly using JDK dynamic proxies or bytecode generation. | |
{Cacheing Services} | |
You will occasionally find yourself in the position of injecting the same services | |
into your service builder or service decorator methods repeatedly (this occurs much less often since the introduction of | |
service autobuilding). This can result in quite | |
a bit of redundant typing. Less code is better code, so as an alternative, you may define a <constructor> for your | |
module that accepts annotated parameters (as with | |
{{{service.html#Injecting Dependencies}service builder injection}}). | |
This gives you a chance to store common services in instance variables for later use inside | |
service builder methods. | |
+-----------------------------------------------------------------------------------+ | |
public class MyModule | |
{ | |
private final JobScheduler scheduler; | |
private final FileSystem fileSystem; | |
public MyModule(JobScheduler scheduler, FileSystem fileSystem) | |
{ | |
this.scheduler = scheduler; | |
this.fileSystem = fileSystem; | |
} | |
public Indexer build() | |
{ | |
IndexerImpl indexer = new IndexerImpl(fileSystem); | |
scheduler.scheduleDailyJob(indexer); | |
return indexer; | |
} | |
} | |
+-----------------------------------------------------------------------------------+ | |
Notice that we've switched from <static> methods to <instance> methods. Since the builder | |
methods are not static, the MyModule class will be instantiated so that the methods may be | |
invoked. The constructor receives two common dependencies, which are stored into instance | |
fields that may later be used inside service builder methods such as buildIndexer(). | |
This approach is far from required; all the builder methods of your module can be static if you wish. | |
It is used when you have many common dependencies and wish to avoid defining those | |
dependencies as parameters to multiple methods. | |
Tapestry IoC automatically resolves the parameter type (JobScheduler and FileSystem, in the example) | |
to the corresponding services that implement that type. When there's more than one | |
service that implements the service interface, you'll get an error (but additional annotations | |
and configuration can be used to ensure the correct service injected). | |
For modules, there are two additional parameter types that are used to refer to <resources> that | |
can be provided to the module instance (rather than <services> which may be injected). | |
* {{{http://www.slf4j.org/api/org/slf4j/Logger.html}org.slf4j.Logger}}: logger for the module (derived from the module's class name) | |
* {{{../apidocs/org/apache/tapestry/ioc/ObjectLocator.html}ObjectLocator}}: access to other services | |
[] | |
Note that the fields are final: this is important. Tapestry IoC is thread-safe and you largely | |
never have to think about concurrency issues. But in a busy application, different services may be | |
built by different threads simultaneously. Each module builder class is instantiated at most once, and | |
making these fields final ensures that the values are available across multiple threads. | |
Refer to Brian Goetz's {{{http://www.javaconcurrencyinpractice.com/}Java Concurrency in Practice}} | |
for a more complete explanation of the relationship between final fields, constructors, and threads ... | |
or just trust us! | |
Care should be taken with this approach: in some circumstances, you may force a situation in which | |
the module constructor is dependent on itself. For example, if you invoke a method on any injected services | |
defined within the same module from the module builder's constructor, | |
then the service implementation will be needed. Creating service implementations | |
requires the module builder instance ... that's a recursive reference. | |
Tapestry detects these scenarios and throws a runtime exception to prevent an endless loop. | |
{Autoloading modules} | |
When setting up the registry, Tapestry can automatically locate modules packaged into JARs. | |
It does this by searching for a particular global manifest entry. | |
The manifest entry name is "Tapestry-Module-Classes". The value is a comma-separated list | |
of fully qualified class names of module builder classes (this allows a single | |
JAR to contain multiple, related modules). Whitespace is ignored. | |
Example: | |
+-----------------------------------------------------------------------------------+ | |
Manifest-Version: 1.0 | |
Tapestry-Module-Classes: org.example.mylib.LibModule, org.example.mylib.internal.InternalModule | |
+-----------------------------------------------------------------------------------+ | |
If you are using Maven 2, then getting these entries into your JAR's manifest | |
is as simple as some configuration in your pom.xml: | |
+-----------------------------------------------------------------------------------+ | |
<project> | |
. . . | |
<build> | |
<plugins> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-jar-plugin</artifactId> | |
<configuration> | |
<archive> | |
<manifestEntries> | |
<Tapestry-Module-Classes>org.example.mylib.LibModule, | |
org.example.mylib.internal.InternalModule</Tapestry-Module-Classes> | |
</manifestEntries> | |
</archive> | |
</configuration> | |
</plugin> | |
</plugins> | |
</build> | |
. . . | |
</project> | |
+-----------------------------------------------------------------------------------+ | |
More details are provided in the | |
{{{http://maven.apache.org/guides/mini/guide-manifest.html}Maven Manifest Guide}}. | |
SubModule Annotation | |
Often, you will have several different modules working together that should all be loaded | |
as a unit. | |
One approach is to update the module ids into the manifest, as shown in the previous extension. | |
This can become tedious, and somewhat brittle in the face of refactorings (such as renaming of classes | |
or packages). | |
A better alternative is the | |
{{{../apidocs/org/apache/tapestry/ioc/annotations/SubModule.html}@SubModule annotation}}. | |
The value for this annotation is a list of <additional> classes to be treated as module builder classes, | |
exactly as if they were identified in the manifest. For example: | |
+----+ | |
@SubModule( | |
{ InternalTransformModule.class }) | |
public final class InternalModule | |
{ | |
. . . | |
+----+ | |
In general, your should only need to identify a single module in the JAR manifest, and make use of | |
@SubModule to pull in any additional module builder classes. | |
Module Builder Implementation Notes | |
Module builder classes are designed to be very, very simple to implement. | |
Again, keep the methods very simple. Use {{{service.html#Injecting Dependencies}parameter injection}} | |
to gain access to the dependencies you need. | |
Be careful about inheritance. Tapestry will see all <public> methods, | |
even those inherited from base classes. Tapestry <only> sees public methods. | |
By convention, module builder class names end in Module and are final classes. | |
You don't <have> to define your methods as static. The use of static methods is only absolutely | |
necessary in a few cases, where the constructor for a module is dependent on contributions | |
from the same module (this creates a chicken-and-the-egg situation that is resolved through | |
static methods). | |
Default Marker | |
Services are often referenced by a particular marker interface on the method or contructor parameter. Tapestry | |
will use the intersection of services with that exact marker and assignable by type to find a unique service | |
to inject. | |
Often, all services in a module should share a marker, this can be specified with a @Marker annotation | |
on the module class. For example, the TapestryIOCModule: | |
+---+ | |
@Marker(Builtin.class) | |
public final class TapestryIOCModule | |
{ | |
. . . | |
+---+ | |
This references a particular annotation class, Builtin: | |
+---+ | |
@Target( | |
{ PARAMETER, FIELD }) | |
@Retention(RUNTIME) | |
@Documented | |
public @interface Builtin | |
{ | |
} | |
+----+ | |
The annotation can be applied to method and constructor parameters, for use within the IoC container. It can also be applied | |
to fields, though this is specific to the Tapestry web framework. |