You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

Overriding 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).

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; thus we can take advantage of several features of the IoC container in order to take control over specific injections.

Overriding Services

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 interface.

In this case, it is very easy to supply your own alternate implementation of a service.

AppModule.java (partial)

  public static void contributeServiceOverride(MappedConfiguration<Class,Object> configuration)
  {
    configuration.add(SomeServiceType.class, new SomeServiceType() { . . . });
  }

In this example, the service to be overriden is provided as an inner class implementing the interface.

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:

AppModule.java (partial)

  public static void bind(ServiceBinder binder)
  {
    binder.bind(SomeServiceType.class, SomeServiceTypeOverrideImpl.class).withId("Override");
  }

  public static void contributeServiceOverride(MappedConfiguration<Class,Object> configuration, @Local SomeServiceType override)
  {
    configuration.add(SomeServiceType.class, override);
  }

Here we're defining a service local to this module using the bind() method.

Every service in the IoC container must have a unique id, that's why we used the withId() method; if we we hadn't, the default service id would have been "SomeServiceType" which is a likely conflict with the very service we're trying to override.

We can inject our overriding implementation of SomeServiceType using the special @Local annotation, which indicates that a service within the same module only should be injected: otherwise 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.

Decorating Services

Another option is to decorate 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)

  public SomeServiceType decorateSomeServiceType(SomeServiceType original)
  {
    return new SomeServiceType() { . . . };
  }

This decorate method is invoked because its name matches the service id of the original service, "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.

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).

  • No labels