Scrollbar |
---|
Info |
---|
Starting with |
Wiki Markup |
---|
{scrollbar} |
Tapestry IoC Decorators
...
Tapestry 5.1, |
...
Service Decoration is augmented with Service Advice. Advisors are similar but more general, as they work on any service interface, which doesn't have to be known at build time. Decoration is used when the type of the service being decorated is known at build time, and involves supplying a new implementation of the service interface. |
Decoration is the name of a popular design pattern. Using decoration, an existing object's behavior can be extended without changing the implementation of the object.
...
A common example for this is the Java I/O library. The abstract InputStream base class has a very simple API for reading bytes from a stream (and a few other things). Subclasses of InputStream provide a wide array of other options such as buffering, encryption or decryption, as well as control over the source of data read by the stream. All of these concerns are encapsulated in different implementations of InputStream, and all can be connected together in a kind of piplinepipeline, using the common InputStream API.
Tapestry IoC uses a similar approach, where a one or more of interceptor objects, all implementing the service interface, are strung together. The service's proxy (responsible for just-in-time instantiation of the service implementation) is at one end of this pipeline, the core service implementation is at the other.
...
Decorations are driven by service decoration methods. Often, a reusable service exists to do the grunt work of creating and instantiating a new class.
Service Decoration Methods
Code Block | ||||
---|---|---|---|---|
| ||||
package org.example.myapp.services; import org.apache.tapestry5.ioc.services.LoggingDecorator; import org.slf4j.Logger; public class MyAppModuleAppModule { public static Indexer build() { return new IndexerImpl(); } public static <T> T decorateIndexer(Class<T> serviceInterface, T delegate, String serviceId, Logger logger, LoggingDecorator decorator) { return decorator.build(serviceInterface, delegate, serviceId, logger); } } |
The method decorateIndexer() is a service decorator method because it starts with the word "decorate". In this simple case, only the myapp.Indexer service will be decorated, even if there are other services in this module or others ... this is because of the name match ("decorateIndexer" and "buildIndexer"), but we'll shortly see how annotations can be used to target many services for decoration.
...
Alternately, when targetting services whose type is known at compile time, you may provide a parameter whose type matches the service interface. For example, decorateIndexer() will always be applied to the Indexer service, whose type (Indexer) is known. We could therefore rewrite decorateIndexer() as:
Code Block | ||
---|---|---|
| ||
public static Indexer decorateIndexer(Indexer delegate, Logger logger, LoggingDecorator decorator)
{
return decorator.build(Indexer.class, delegate, "Indexer", logger);
} |
Of course, nothing stops you from combining building with decorating inside the service builder method:
Code Block | |||||
---|---|---|---|---|---|
| |||||
package package org.example.myapp.services; import org.apache.tapestry5.ioc.services.LoggingDecorator; import org.slf4j.Logger; public class MyAppModuleAppModule { public static Indexer build(Logger logger, LoggingDecorator decorator) { return decorator.build(Indexer.class, logger, new IndexerImpl()); } } |
But as we'll see next, its it's possible to have a single decorator method work on many different services by using annotations.
...
Targeting Multiple Services
By using the @Match annnotation@Match annotation, you may identify which services are to be decorated.
...
For example, to target all the services in your module:
Code Block | ||
---|---|---|
| ||
@Match("*") public static <T> T decorateLogging(Class<T> serviceInterface, T delegate, String serviceId, Logger logger, LoggingDecorator decorator) { return decorator.build(serviceInterface, delegate, serviceId, logger); } |
...
Note: Another idea will be other ways of matching services: base on inheritance of the service interface and/or based on the presence of particular class annotations on the service interface. None of this has been implemented yet, and can readily be accompllished accomplished inside the decorator method (which will return null if it decides the service doesn't need decoration).
Ordering of Decorators
In cases where multiple decorators will apply to a single service, you can control the order in which decorators are applied using an additional annotation: @Order@Order.
This annotation allows any number of ordering constraints to be specified for the decorator, to order it relative to any other decorators.
For example, you almost always want logging decorators to come first, so:
Code Block | ||||
---|---|---|---|---|
| ||||
@Match("*") @Order("before:*") public static <T> T decorateLogging(Class<T> serviceInterface, T delegate, String serviceId, Logger logger, LoggingDecorator decorator) { return decorator.build(serviceInterface, delegate, serviceId, logger); } |
...
Note: the ordering of decorators is in terms of the effect desired. Internally, the decorators are invoked last to first (since each once receives the "next" interceptor as its delegate). So the core service implementation is created (via a service builder method) and that is passed to the last decorator method. The interceptor created there is passed to the the next-to-last decorator method, and so forth.It should now be evident that the delegate passed into a decorator method is sometimes the core service implementation, and some times an interceptor object created by some other decorator method.
It should now be evident that the delegate passed into a decorator method is sometimes the core service implementation, and some times an interceptor object created by some other decorator method.
Annotation driven decorators
Since | ||
---|---|---|
| ||
Code Block |
---|
@Decorate
@Match("*DAO")
public static <T> T byServiceId(Class<T> serviceInterface, T delegate,
String serviceId, Logger logger,
LoggingDecorator decorator)
{
return decorator.build(serviceInterface, delegate, serviceId, logger);
}
|
The decorator above is applied to any service whose id matches the "*DAO" pattern.
Alternatively, marker annotations can be placed on the decorate method to match a specific service.
Code Block |
---|
@Decorate
@Blue
public static <T> T byMarkerAnnotation(Class<T> serviceInterface, T delegate,
String serviceId, Logger logger,
LoggingDecorator decorator)
{
return decorator.build(serviceInterface, delegate, serviceId, logger);
}
|
The decorator above is applied to any service that is marked by the @Blue annotation.
By default, @Decorate annotation applies the decorator to any service matched by the @Match or marker annotations. You can limit the matching to a single service interface, as shown in the following example.
Code Block |
---|
@Decorate(serviceInterface=MyService.class)
@Match("*DAO")
public static <T> T byServiceId(Class<T> serviceInterface, T delegate,
String serviceId, Logger logger,
LoggingDecorator decorator)
{
return decorator.build(serviceInterface, delegate, serviceId, logger);
} |
In the example above, the decorator is applied to any implementation of MyService interfaces whose id matches the "*DAO" pattern.
Code Block |
---|
@Decorate(serviceInterface=MyService.class)
@Blue
public static <T> T byMarkerAnnotation(Class<T> serviceInterface, T delegate,
String serviceId, Logger logger,
LoggingDecorator decorator)
{
return decorator.build(serviceInterface, delegate, serviceId, logger);
}
|
The decorator above is applied to any implementation of the MyService interface that is marked by the @Blue annotation.
Creating your own Decorators
Decorators are a limited form of Aspect Oriented Programming, so we have borrowed some of that terminology here.
...
By way of an example, we'll show an implementation of the LoggingDecorator service:
Code Block | ||||
---|---|---|---|---|
| ||||
public class LoggingDecoratorImpl implements LoggingDecorator
{
private final AspectDecorator aspectDecorator;
private final ExceptionTracker exceptionTracker;
public LoggingDecoratorImpl(AspectDecorator aspectDecorator, ExceptionTracker exceptionTracker)
{
this.aspectDecorator = aspectDecorator;
this.exceptionTracker = exceptionTracker;
}
public <T> T build(Class<T> serviceInterface, T delegate, String serviceId, final Logger logger)
{
final ServiceLogger serviceLogger = new ServiceLogger(logger, exceptionTracker);
MethodAdvice advice = new MethodAdvice()
{
public void advise(Invocation invocation)
{
boolean debug = logger.isDebugEnabled();
if (debug) serviceLogger.entry(invocation);
try
{
invocation.proceed();
}
catch (RuntimeException ex)
{
if (debug) serviceLogger.fail(invocation, ex);
throw ex;
}
if (!debug) return;
if (invocation.isFail())
{
Exception thrown = invocation.getThrown(Exception.class);
serviceLogger.fail(invocation, thrown);
return;
}
serviceLogger.exit(invocation);
}
};
return aspectDecorator.build(serviceInterface, delegate, advice,
String.format("<Logging interceptor for %s(%s)>", serviceId,
serviceInterface.getName()));
}
} |
...
The AspectDecorator service can also be used in more complicated ways: it is possible to only advise some of the methods and not others, or use different advice for different methods. Check the JavaDoc for more details.
...
Scrollbar |
---|