Scrollbar |
---|
Info |
---|
Starting with 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.
...
Service Decoration Methods
Code Block | ||||
---|---|---|---|---|
| ||||
package org.example.myapp.services; import org.apache.tapestry5.ioc.services.LoggingDecorator; import org.slf4j.Logger; public class |
...
AppModule { 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 org.example.myapp.services; import org.apache.tapestry5.ioc.services.LoggingDecorator; import org.slf4j.Logger; public class |
...
AppModule { public static Indexer build(Logger logger, LoggingDecorator decorator) { return decorator.build(Indexer.class, logger, new IndexerImpl()); } } |
But as we'll see next, it's possible to have a single decorator method work on many different services by using annotations.
...
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); } |
You can use multiple patterns with @Match, in which case, the decorator will be applied to a service that matches any of the patterns. For instance, if you only wanted logging for your data access and business logic services, you might end up with @Match("Data*", "*Logic")
(based, of course, on how you name your services).
...
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); } |
"before:*" indicates that this decorator should come before any decorator in any module.
...
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.
...
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 actual code has been refactored slightly since this documentation was written.
...
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 |
---|