tapestry-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From conflue...@apache.org
Subject [CONF] Apache Tapestry > Tapestry IoC Decorators
Date Thu, 23 Dec 2010 01:10:00 GMT
<html>
<head>
    <base href="https://cwiki.apache.org/confluence">
            <link rel="stylesheet" href="/confluence/s/1810/9/12/_/styles/combined.css?spaceKey=TAPESTRY&amp;forWysiwyg=true"
type="text/css">
    </head>
<body style="background: white;" bgcolor="white" class="email-body">
<div id="pageContent">
<div id="notificationFormat">
<div class="wiki-content">
<div class="email">
    <h2><a href="https://cwiki.apache.org/confluence/display/TAPESTRY/Tapestry+IoC+Decorators">Tapestry
IoC Decorators</a></h2>
    <h4>Page <b>edited</b> by             <a href="https://cwiki.apache.org/confluence/display/~bobharner">Bob
Harner</a>
    </h4>
        <div id="versionComment">
        <b>Comment:</b>
        Renamed<br />
    </div>
        <br/>
                         <h4>Changes (0)</h4>
                                 
    
<div id="page-diffs">
            <table class="diff" cellpadding="0" cellspacing="0">
            <tr><td class="diff-snipped" >...<br></td></tr>
        </table>
</div>                            <h4>Full Content</h4>
                    <div class="notificationGreySide">
        <style type='text/css'>/*<![CDATA[*/
table.ScrollbarTable  {border: none;padding: 3px;width: 100%;padding: 3px;margin: 0px;background-color:
#f0f0f0}
table.ScrollbarTable td.ScrollbarPrevIcon {text-align: center;width: 16px;border: none;}
table.ScrollbarTable td.ScrollbarPrevName {text-align: left;border: none;}
table.ScrollbarTable td.ScrollbarParent {text-align: center;border: none;}
table.ScrollbarTable td.ScrollbarNextName {text-align: right;border: none;}
table.ScrollbarTable td.ScrollbarNextIcon {text-align: center;width: 16px;border: none;}

/*]]>*/</style><div class="Scrollbar"><table class='ScrollbarTable'><tr><td
class='ScrollbarPrevIcon'><a href="/confluence/display/TAPESTRY/Service+Advisors"><img
border='0' align='middle' src='/confluence/images/icons/back_16.gif' width='16' height='16'></a></td><td
width='33%' class='ScrollbarPrevName'><a href="/confluence/display/TAPESTRY/Service+Advisors">Service
Advisors</a>&nbsp;</td><td width='33%' class='ScrollbarParent'><sup><a
href="/confluence/display/TAPESTRY/IoC"><img border='0' align='middle' src='/confluence/images/icons/up_16.gif'
width='8' height='8'></a></sup><a href="/confluence/display/TAPESTRY/IoC">IoC</a></td><td
width='33%' class='ScrollbarNextName'>&nbsp;<a href="/confluence/display/TAPESTRY/IoC+-+configuration">IoC
- configuration</a></td><td class='ScrollbarNextIcon'><a href="/confluence/display/TAPESTRY/IoC+-+configuration"><img
border='0' align='middle' src='/confluence/images/icons/forwd_16.gif' width='16' height='16'></a></td></tr></table></div>

<h1><a name="TapestryIoCDecorators-TapestryIoCDecorators"></a>Tapestry IoC
Decorators</h1>

<p><b>Decoration has been replaced, in Tapestry 5.1, with <a href="#TapestryIoCDecorators-advice.html">advice</a>,
which is a simpler mechanism for accomplishing the same thing.</b></p>

<p><em>Decoration</em> 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.</p>

<p>Instead, a new object is placed <em>around</em> the existing object.
The rest of the world sees this new object, termed an <b>interceptor</b>. The
interceptor implements the same interface as the underlying object being decorated.</p>

<p>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 <em>concerns</em>
are encapsulated in different implementations of InputStream, and all can be connected together
in a kind of pipline, using the common InputStream API.</p>

<p>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.</p>

<p>For each method in the service interface, the interceptor object can perform some
operations before and after re-invoking the same method on the core service implementation.
This is another design pattern: <em>delegation</em>. An interceptor can even catch
exceptions thrown by the underlying implementation and react to them. A sufficiently clever
interceptor could retry a method if an exception is thrown, or could "soften" a checked exception
by wrapping it in a RuntimeException.</p>

<p>Decorators often are used in the context of <em>cross-cutting concerns</em>,
such as logging or transaction management. This approach is a kind of <em>aspect oriented
design</em>.</p>

<p>One such cross cutting concern is lazy initialization of services. In Apache HiveMind,
services are created only as needed, when a method of a service interface is first invoked.
This concern is supplied by the Tapestry IoC framework itself, but similar concerns are easily
implemented as decorations.</p>

<p>Whereas the popular AspectJ framework changes the compiled bytecode of your classes
(it calls the process "weaving"), with Tapestry IoC, the approach is to wrap your existing
classes in new objects. These wrapper objects are often dynamically created at runtime.</p>

<p>It is also common to have <em>multiple</em> decorations on a single service.
In this case, a whole stack of interceptor objects will be created, each delegating to the
next. Tapestry IoC provides control over the order in which such decorations occur.</p>

<p>Decorations are driven by service decoration methods. Often, a reusable service exists
to do the grunt work of creating and instantiating a new class.</p>

<h1><a name="TapestryIoCDecorators-ServiceDecorationMethods"></a>Service
Decoration Methods</h1>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">package</span> org.example.myapp.services;

<span class="code-keyword">import</span> org.apache.tapestry5.ioc.services.LoggingDecorator;
<span class="code-keyword">import</span> org.slf4j.Logger;

<span class="code-keyword">public</span> class MyAppModule
{
  <span class="code-keyword">public</span> <span class="code-keyword">static</span>
Indexer build()
  {
    <span class="code-keyword">return</span> <span class="code-keyword">new</span>
IndexerImpl();
  }
  
  <span class="code-keyword">public</span> <span class="code-keyword">static</span>
&lt;T&gt; T decorateIndexer(<span class="code-object">Class</span>&lt;T&gt;
serviceInterface, T delegate, 
    <span class="code-object">String</span> serviceId, Logger logger,
    
    LoggingDecorator decorator)
  {
    <span class="code-keyword">return</span> decorator.build(serviceInterface,
delegate, serviceId, logger);
  } 
}</pre>
</div></div>

<p>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.</p>

<p>We are using the parameterized types here (the &lt;T&gt;), to reinforce the
fact that the delegate object passed in (which will be the core service implementation, or
some other interceptor) must implement the service interface, and that the decorator method
must return an instance of the service interface.</p>

<p>The values that may be provided to a decorator method are exactly the same as for
a builder method, with one addition: The underlying service will be passed in as a parameter
of type java.lang.Object (after type erasure, the <tt>T delegate</tt> parameter
becomes <tt>Object delegate</tt>).</p>

<p>In the above example, the decorator method receives the core service implementation,
the service interface for the Indexer service, the Log for the Indexer service, and an interceptor
factory that generates logging interceptors.</p>

<p>The "heavy lifting" is provided by the factory, which will create a new interceptor
that logs method entry before delegating to the core service implementation. The interceptor
will also log method parameters, return values, and even log exceptions.</p>

<p>The return value of the method is the new interceptor. You may return null if your
decorator method decides not to decorate the supplied service.</p>

<p>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:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
  <span class="code-keyword">public</span> <span class="code-keyword">static</span>
Indexer decorateIndexer(Indexer delegate, Logger logger, LoggingDecorator decorator)
  {
    <span class="code-keyword">return</span> decorator.build(Indexer.class, delegate,
<span class="code-quote">"Indexer"</span>, logger);
  }</pre>
</div></div>

<p>Of course, nothing stops you from combining building with decorating inside the service
builder method:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">package</span> org.example.myapp.services;

<span class="code-keyword">import</span> org.apache.tapestry5.ioc.services.LoggingDecorator;
<span class="code-keyword">import</span> org.slf4j.Logger;

<span class="code-keyword">public</span> class MyAppModule
{
  <span class="code-keyword">public</span> <span class="code-keyword">static</span>
Indexer build(Logger logger, LoggingDecorator decorator)
  {
    <span class="code-keyword">return</span> decorator.build(Indexer.class, logger,
 <span class="code-keyword">new</span> IndexerImpl());
  } 
}</pre>
</div></div>

<p>But as we'll see, its possible to have a single decorator method work on many different
services by using annotations.</p>

<h1><a name="TapestryIoCDecorators-TargettingMultipleServices"></a>Targetting
Multiple Services</h1>

<p>By using the <a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/annotations/Match.html"
class="external-link" rel="nofollow">@Match annnotation</a>, you may identify which
services are to be decorated.</p>

<p>The value specified in the Match annotation is one or more patterns. These patterns
are used to match services. Patterns take two forms: glob patterns and regular expressions.</p>

<p>In a glob pattern, a "*" at the start or end of a string will match zero or more
characters. Regular expressions provide a lot more matching power, but require a more involved
syntax.</p>

<p>In either case, the matching is case insensitive.</p>

<p>For example, to target all the services in your module:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
  @Match(<span class="code-quote">"*"</span>)
  <span class="code-keyword">public</span> <span class="code-keyword">static</span>
&lt;T&gt; T decorateLogging(<span class="code-object">Class</span>&lt;T&gt;
serviceInterface, T delegate, 
    <span class="code-object">String</span> serviceId, Logger logger,
    LoggingDecorator decorator)
  {
    <span class="code-keyword">return</span> decorator.build(serviceInterface,
delegate, serviceId, logger);
  }   </pre>
</div></div>

<p>You can use multiple patterns with @Match, in which case, the decorator will be applied
to a service that matches <em>any</em> of the patterns. For instance, if you only
wanted logging for your data access and business logic services, you might end up with <tt>@Match("Data*",
"*Logic")</tt> (based, of course, on how you name your services).</p>

<p>As the preceding example showed, a simple "glob" matching is supported, where a asterisk
('*') may be used at the start or end of the match string to match any number of characters.
As elsewhere, matching is case insensitive.</p>

<p>Thus, <tt>@Match("*")</tt> is dangerous, because it will match every
service in every module.</p>

<p><em>Note: It is not possible to decorate the services of the TapestryIOCModule.</em></p>

<p><em>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
inside the decorator method (which will return null if it decides the service doesn't need
decoration).</em></p>

<h1><a name="TapestryIoCDecorators-OrderingofDecorators"></a>Ordering of
Decorators</h1>

<p>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: <a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/annotations/Order.html"
class="external-link" rel="nofollow">@Order</a>.</p>

<p>This annotation allows any number of <a href="#TapestryIoCDecorators-order.html">ordering
constraints</a> to be specified for the decorator, to order it relative to any other
decorators.</p>

<p>For example, you almost always want logging decorators to come first, so:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
  @Match(<span class="code-quote">"*"</span>)
  @Order(<span class="code-quote">"before:*"</span>)
  <span class="code-keyword">public</span> <span class="code-keyword">static</span>
&lt;T&gt; T decorateLogging(<span class="code-object">Class</span>&lt;T&gt;
serviceInterface, T delegate, 
    <span class="code-object">String</span> serviceId, Logger logger,
    LoggingDecorator decorator)
  {
    <span class="code-keyword">return</span> decorator.build(serviceInterface,
delegate, serviceId, logger);
  }   </pre>
</div></div>

<p>"before:*" indicates that this decorator should come before any decorator in <em>any</em>
module.</p>

<p><b>Note:</b> the ordering of decorators is in terms of the <em>effect</em>
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.</p>

<p>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.</p>

<h1><a name="TapestryIoCDecorators-CreatingyourownDecorators"></a>Creating
your own Decorators</h1>

<p>Decorators are a limited form of Aspect Oriented Programming, so we have borrowed
some of that terminology here.</p>

<p>A decorator exists to create an <em>interceptor</em>. The interceptor
wraps around the service (because these interceptors can get chained, we talk about the "delegate"
and not the "service").</p>

<p>Each method of the interceptor will take <em>advice</em>. Advice is provided
by a <a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/MethodAdvice.html"
class="external-link" rel="nofollow">MethodAdvice</a> instance. The sole method,
<tt>advise()</tt>, receives an <a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/Invocation.html"
class="external-link" rel="nofollow">Invocation</a>. MethodAdvice gives you a chance
to see what the method invocation <em>is</em>; you can query the name of the method,
and the types and values of the parameters.</p>

<p>The MethodAdvice can override the parameters if necessary, then invoke <tt>proceed()</tt>.
This call invokes the corresponding method on the original object, the delegate.</p>

<p>If the method call throws a runtime exception, that exception is not caught. Your
method advice can put a try ... catch block around the call to proceed() if interested in
catching runtime exceptions.</p>

<p>Checked exceptions are not thrown (since they are not part of the proceed() method's
signature). Instead the invocation's <tt>isFail()</tt> method will return true.
You can then retrieve the exception or override it.</p>

<p>In the normal success case, you can ask for the return value and even override it
before returning from the advise() method.</p>

<p>In other words, you have total control. Your MethodAdvice can query or change parameters,
decide whether it proceed into the original code, it can intercept exceptions that are thrown
and replace them, and can query or even replace the return value.</p>

<p>The <a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/services/AspectDecorator.html"
class="external-link" rel="nofollow">AspectDecorator</a> service is how you put your
MethodAdvice into action.</p>

<p>By way of an example, we'll show an implementation of the LoggingDecorator service:</p>

<div class="code panel" style="border-width: 1px;"><div class="codeContent panelContent">
<pre class="code-java">
<span class="code-keyword">public</span> class LoggingDecoratorImpl <span class="code-keyword">implements</span>
LoggingDecorator
{
    <span class="code-keyword">private</span> <span class="code-keyword">final</span>
AspectDecorator aspectDecorator;

    <span class="code-keyword">private</span> <span class="code-keyword">final</span>
ExceptionTracker exceptionTracker;

    <span class="code-keyword">public</span> LoggingDecoratorImpl(AspectDecorator
aspectDecorator, ExceptionTracker exceptionTracker)
    {
        <span class="code-keyword">this</span>.aspectDecorator = aspectDecorator;
        <span class="code-keyword">this</span>.exceptionTracker = exceptionTracker;
    }

    <span class="code-keyword">public</span> &lt;T&gt; T build(<span
class="code-object">Class</span>&lt;T&gt; serviceInterface, T delegate, <span
class="code-object">String</span> serviceId, <span class="code-keyword">final</span>
Logger logger)
    {
        <span class="code-keyword">final</span> ServiceLogger serviceLogger =
<span class="code-keyword">new</span> ServiceLogger(logger, exceptionTracker);

        MethodAdvice advice = <span class="code-keyword">new</span> MethodAdvice()
        {
            <span class="code-keyword">public</span> void advise(Invocation invocation)
            {
                <span class="code-object">boolean</span> debug = logger.isDebugEnabled();

                <span class="code-keyword">if</span> (debug) serviceLogger.entry(invocation);

                <span class="code-keyword">try</span>
                {
                    invocation.proceed();
                }
                <span class="code-keyword">catch</span> (RuntimeException ex)
                {
                    <span class="code-keyword">if</span> (debug) serviceLogger.fail(invocation,
ex);

                    <span class="code-keyword">throw</span> ex;
                }

                <span class="code-keyword">if</span> (!debug) <span class="code-keyword">return</span>;

                <span class="code-keyword">if</span> (invocation.isFail())
                {
                    Exception thrown = invocation.getThrown(Exception.class);

                    serviceLogger.fail(invocation, thrown);

                    <span class="code-keyword">return</span>;
                }

                serviceLogger.exit(invocation);
            }
        };

        <span class="code-keyword">return</span> aspectDecorator.build(serviceInterface,
delegate, advice,
                                      <span class="code-object">String</span>.format(<span
class="code-quote">"&lt;Logging interceptor <span class="code-keyword">for</span>
%s(%s)&gt;"</span>, serviceId,
                                                    serviceInterface.getName()));
    }
}</pre>
</div></div>

<p><em>The actual code has been refactored slightly since this documentation was
written.</em></p>

<p>Most of the logging logic occurs inside the ServiceLogger object, the MethodAdvice
exists to call the right methods at the right time. A Logger doesn't <em>change</em>
parameter values (or thrown exceptions, or the result), it just captures and logs the data.</p>

<p>Notice that for runtime exceptions, we catch the exception, log it, and rethrow it.</p>

<p>For checked exceptions, we use isFail() and getThrown().</p>

<p>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.</p>

<style type='text/css'>/*<![CDATA[*/
table.ScrollbarTable  {border: none;padding: 3px;width: 100%;padding: 3px;margin: 0px;background-color:
#f0f0f0}
table.ScrollbarTable td.ScrollbarPrevIcon {text-align: center;width: 16px;border: none;}
table.ScrollbarTable td.ScrollbarPrevName {text-align: left;border: none;}
table.ScrollbarTable td.ScrollbarParent {text-align: center;border: none;}
table.ScrollbarTable td.ScrollbarNextName {text-align: right;border: none;}
table.ScrollbarTable td.ScrollbarNextIcon {text-align: center;width: 16px;border: none;}

/*]]>*/</style><div class="Scrollbar"><table class='ScrollbarTable'><tr><td
class='ScrollbarPrevIcon'><a href="/confluence/display/TAPESTRY/Service+Advisors"><img
border='0' align='middle' src='/confluence/images/icons/back_16.gif' width='16' height='16'></a></td><td
width='33%' class='ScrollbarPrevName'><a href="/confluence/display/TAPESTRY/Service+Advisors">Service
Advisors</a>&nbsp;</td><td width='33%' class='ScrollbarParent'><sup><a
href="/confluence/display/TAPESTRY/IoC"><img border='0' align='middle' src='/confluence/images/icons/up_16.gif'
width='8' height='8'></a></sup><a href="/confluence/display/TAPESTRY/IoC">IoC</a></td><td
width='33%' class='ScrollbarNextName'>&nbsp;<a href="/confluence/display/TAPESTRY/IoC+-+configuration">IoC
- configuration</a></td><td class='ScrollbarNextIcon'><a href="/confluence/display/TAPESTRY/IoC+-+configuration"><img
border='0' align='middle' src='/confluence/images/icons/forwd_16.gif' width='16' height='16'></a></td></tr></table></div>
    </div>
        <div id="commentsSection" class="wiki-content pageSection">
        <div style="float: right;">
            <a href="https://cwiki.apache.org/confluence/users/viewnotifications.action"
class="grey">Change Notification Preferences</a>
        </div>
        <a href="https://cwiki.apache.org/confluence/display/TAPESTRY/Tapestry+IoC+Decorators">View
Online</a>
        |
        <a href="https://cwiki.apache.org/confluence/pages/diffpagesbyversion.action?pageId=23338481&revisedVersion=4&originalVersion=3">View
Changes</a>
            </div>
</div>
</div>
</div>
</div>
</body>
</html>

Mime
View raw message