h1. Overriding Tapestry IoC Services
|
Tapestry is designed to be easy to customize, and the IoC container is key to that customizability.
|
Part of Tapestry's core functionality is resolving injected objects; that is, when Tapestry is building an object or service and sees a constructor parameter or a field, what value does it plug in? Most of the time, the injected object is a service defined elsewhere within the container (and, in fact, that actual instance will be a proxy to the service, which may not have been fully realized yet).
|
Part of Tapestry's core functionality is resolving injected objects; that is, when Tapestry is building an object or service and sees a constructor parameter or a field, it must decide on what value to plug in? Most of the time, the injected object is a service defined elsewhere within the Tapestry IoC container.
|
However, there are cases where you might want to override how Tapestry operates in some specific way.
|
The strategy used to determine what object gets injected where is [defined inside Tapestry IoC itself|../injection.html]; itself|TAPESTRY:IoC - injection]; thus we can take advantage of several features of the Tapestry IoC container in order to take control over specific injections.
|
|
h1. Overriding Services
|
h2. Contributing a Service Override
|
|
In most cases, services are injected by matching just type; there no @InjectService annotation, just a method or constructor parameter whose type matches the service's service's interface.
|
In this case, it is very easy to supply your own alternate implementation of a service.
|
*AppModule.java* (partial)
|
{code:lang=java|title=AppModule.java (partial)}
|
@Contribute(ServiceOverride.class)
|
{noformat}
|
public static void contributeServiceOverride(MappedConfiguration<Class,Object> setupApplicationServiceOverrides(MappedConfiguration<Class,Object> configuration)
|
{
|
configuration.add(SomeServiceType.class, new SomeServiceType() { . . . });
|
configuration.addInstance(SomeServiceType.class, SomeServiceTypeOverrideImpl.class);
|
}{noformat} }
|
{code}
|
|
In this example, the service to be overriden is provided as an inner class implementing the interface.
|
The name of the method is not important, as long as the @[Contribute|http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/annotations/Contribute.html] annotation is present on the method.
|
|
Sometimes you'll want to define the override as a service of its own: this is useful if you want to inject a Logger specific to the service, or if the overriding implementation needs a configuration:
|
In this example, we are using {{addInstance()}} which will instantiate the indicated class and handle dependency resolution{footnote}Be careful with this, because in some cases, resolving dependencies of the override class can require checking against the ServiceOverrides service, and you'll get a runtime exception about ServiceOverrides requiring itself!{footnote}.
|
|
*AppModule.java* (partial)
|
Sometimes you'll want to define the override as a service of its own: this is useful if you want to inject a Logger specific to the service, or if the overriding implementation needs a [service configuration|TAPESTRY:IoC - configuration]:
|
|
{noformat}
|
{code:lang=java|title=AppModule.java (partial)}
|
public static void bind(ServiceBinder binder) {
|
binder.bind(SomeServiceType.class, SomeServiceTypeOverrideImpl.class).withId("Override"); SomeServiceTypeOverrideImpl.class).withId("SomeServiceTypeOverride");
|
}
|
@Contribute(ServiceOverride.class)
|
public static void contributeServiceOverride(MappedConfiguration<Class,Object> setupApplicationServiceOverrides(MappedConfiguration<Class,Object> configuration, @Local SomeServiceType override)
|
{ configuration.add(SomeServiceType.class, override);
|
}{noformat} }
|
{code}
|
|
Here we're defining a service local to this module using the bind() method.
|
Here we're defining a service using the module's {{bind()}} method.
|
|
Every service in the IoC container must have a unique id, that's that's why we used the {{withId()}} method; if we we hadn't, hadn't, the default service id would have been "SomeServiceType" "SomeServiceType" which is a likely conflict with the very service we're we're trying to override.
|
|
We can inject our overriding implementation of SomeServiceType using the special @[Local|../../apidocs/org/apache/tapestry5/annotations/Local.html] @[Local|http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/annotations/Local.html] annotation, which indicates that a service within the same module only should be injected: otherwise injected (that is, services of the indicated type in other modules are ignored). Without @Local, there would be a problem because the override parameter would need to be resolved using the MasterObjectProvider and, ultimately, the ServiceOverride service; this would cause Tapestry to throw an exception indicating that ServiceOverride depends on itself. We defuse that situation by using @Local, which prevents the MasterObjectProvider service from being used to resolve the override parameter.
|
|
h12. Decorating Services
|
|
Another option is to [decorate|../deocrator.html] [decorate|TAPESTRY:IoC - decorator] the existing service. Perhaps you want to extend some of the behavior of the service but keep the rest.
|
Alternately, this approach is useful to override a service that is matched using marker annotations.
|
*AppModule.java* (partial) {noformat} public SomeServiceType decorateSomeServiceType(SomeServiceType original)
|
{code:lang=java|title=AppModule.java (partial)} public SomeServiceType decorateSomeServiceType(final SomeServiceType delegate)
|
{ return new SomeServiceType() { . . . };
|
}{noformat} }
|
{code}
|
|
This decorate method is invoked because its name matches the service id of the original service, "SomeServiceType" "SomeServiceType" (you have to adjust the name to match the service id).
|
|
It is passed the original service and its job it to return an _interceptor_, and object that implements the same interface, wrapping around the original service.
|
The method is passed the original service and its job it to return an _interceptor_, an object that implements the same interface, wrapping around the original service. In many cases, your code will simply re-invoke methods on the delegate, passing the same parameters. However, an interceptor can decide to not invoke methods, or it can change parameters, or change return values, or catch or throw exceptions.
|
|
Note that the object passed in as original may be the core service implementation, or it may be some other interceptor from some other decorator for the same service (often, such a parameter is named "delegate" to highlight this ambiguity).
|
Note that the object passed in as {{delegate}} may be the core service implementation, or it may be some other interceptor from some other decorator for the same service. ---- {display-footnotes}
|