--- 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 service interfaces to service implementations * To contribute configuration data services * To services by providing around them * To provide explicit code for building a service * To set a default for all services defined in the module [] A module builder defines builder methods, one for each service provided by the module. <> 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 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 ; 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 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 methods to 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 that can be provided to the module instance (rather than 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: +-----------------------------------------------------------------------------------+ . . . org.apache.maven.plugins maven-jar-plugin org.example.mylib.LibModule, org.example.mylib.internal.InternalModule . . . +-----------------------------------------------------------------------------------+ 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 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 methods, even those inherited from base classes. Tapestry sees public methods. By convention, module builder class names end in Module and are final classes. You don't 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.