blob: 33d9ac1b101728d03ce296e2f25ab9d354fbe7fc [file] [log] [blame]
---
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.tapestry5.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/tapestry5/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/tapestry5/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.