You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2007/10/10 20:37:44 UTC

svn commit: r583573 - in /tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt: index.apt module.apt run.apt service.apt shadow.apt

Author: hlship
Date: Wed Oct 10 11:37:44 2007
New Revision: 583573

URL: http://svn.apache.org/viewvc?rev=583573&view=rev
Log:
TAPESTRY-1798: Injection via Marker Annotations

Modified:
    tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/index.apt
    tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/module.apt
    tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/run.apt
    tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/service.apt
    tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/shadow.apt

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/index.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/index.apt?rev=583573&r1=583572&r2=583573&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/index.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/index.apt Wed Oct 10 11:37:44 2007
@@ -8,7 +8,7 @@
   a design approach that allows a working system to be fabricated from many small, easily testable pieces.
   
   An additional benefit of using IoC (Inversion of Control) is that, by breaking a complex system into small pieces, it becomes easier to
-  modify and extend the system, by overriding or replacing small pieces of the system.
+  modify and extend the system, by overriding or replacing selected parts of the system.
   
   The use of IoC in Tapestry represents an evolution from Tapestry 3 to Tapestry 4 to Tapestry 5.  Tapestry 3 did not use IoC, though it included
   some weaker mechanisms, such as extensions, that served a similar purpose.  To make large scale changes to the behavior of Tapestry 3 required
@@ -19,7 +19,7 @@
   because of HiveMind's flexibility.
   
   Tapestry 5 extends on this, replacing HiveMind with a new container specifically build for Tapestry 5,
-  designed for greater ease of use, expressiveness and performance.  And it can be used seperately from the rest of Tapestry!
+  designed for greater ease of use, expressiveness and performance.  And it can be used separately from the rest of Tapestry!
   
 * Why Not Spring?
 
@@ -64,6 +64,16 @@
   
   Tapestry IoC also represents many simplifications of HiveMind, representing lessons learned while creating both
   HiveMind and Tapestry 4.
+
+* Why not Guice?
+
+  {{{http://code.google.com/p/google-guice/}Google Guice}} is a newcomer to the IoC landscape.  Guice and T5 IoC are very close and, in fact,
+  T5 IoC expressly borrows many great and innovative ideas from Guice. Guice abandons not only XML but even any concept of a service id ...
+  for injection, services are matched by type and perhaps filtered based on annotations.
+
+  Guice is still missing some core ideas needed in T5 IoC.  There's no concept of configurations or anything similar.
+  And there are limitations on injection based on scope (a request scoped value can't be injected into a global scope service; in T5 IoC, scope
+  is internal to the proxy and never an issue).
   
 Goals
 
@@ -104,7 +114,7 @@
   when the minimum number of them control the maximum area on the board.  Playing "heavy" just gives your opponent a free
   chance to take control of another section of the board.
   
-   In software development, we are also attempting to create complex systems
+  In software development, we are also attempting to create complex systems
   from simple pieces, but our tension is derived from the need to add functionality balanced against the need
   to test and maintain existing code.  Too often in the world of software development, the need to add functionality
   trumps all, and testing and maintenance is deferred ... until too late.
@@ -151,7 +161,7 @@
   Services are aggregated into <<modules>>:
       
   * A module is defined by a <<module builder>>, a specific class containing a mix of static or instance methods, used to define
-   services, decorate them (see below), or contribute to service configurations (again, more below).
+    services, decorate them (see below), or contribute to service configurations (again, more below).
   
   * Methods of the module builder class define the services provided by the module, 
     and the same methods are responsible
@@ -163,7 +173,7 @@
   
   The <<registry>> is the outside world's view of the modules and services. From the registry, it is possible to obtain
   a service, via its unique id or by its service interface.  Access by unique id is <caseless> (meaning, a match will be found
-  even the case of the search key doesn't match the case of the service itself). 
+  even the case of the search key doesn't match the case of the service id itself). 
     
     
   Services may be <<decorated>> by <<service decorator methods>>.  These methods create
@@ -173,12 +183,12 @@
   Control is given over the order in which decorators are applied to a service.
   
   A service may have a <<configuration>>. The configuration is either a map, a collection, or an ordered list. The service defines the type
-  of object allowed to be contributed into the configuration. The configuration is contructed
+  of object allowed to be contributed into the configuration. The configuration is constructed
   from <<contributions>> provided by one or more modules.   <<Service contributor methods>> are invoked to contribute objects into
   configurations.
   
   <Note: In HiveMind, services and configurations were separate, which often lead
-  to linked pairs of similarily named services and configurations. For Tapestry IoC, each service is allowed to have a single configuration,
+  to linked pairs of similarly named services and configurations. For Tapestry IoC, each service is allowed to have a single configuration,
   which is normally sufficient.>
   
   Services are instantiated as needed. In this case, "need" translates to "when a method of the service is invoked".
@@ -187,7 +197,7 @@
   constructed. This occurs in a completely <<thread-safe>> manner. Just-in-time instantiation allows for more complex, more finely grained networks of services, and improves
   startup time.
   
-  Services define a <<scope>> that controls when the service is constructed, as well as its visiblity.  The default scope is <<singleton>>, meaning a single
+  Services define a <<scope>> that controls when the service is constructed, as well as its visibility.  The default scope is <<singleton>>, meaning a single
   global instance created as needed.  Other scopes allow service implementations to be bound to the current thread (i.e., the current
   request in a servlet application).
   

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/module.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/module.apt?rev=583573&r1=583572&r2=583573&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/module.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/module.apt Wed Oct 10 11:37:44 2007
@@ -9,7 +9,22 @@
   The module builder is a plain Java class.  A system of annotations and naming conventions allow
   Tapestry to determine what services are provided by the module.
 
-  A module bulider defines builder methods, one for each service provided by the module.
+  A module class exists for the following reasons:
+
+  * To <bind> service interfaces to service implementations
+
+  * To contribute configuration data <into> services
+
+  * To <decorate> services by providing <interceptors> around them
+
+  * To provide explicit code for building a service
+
+  []
+
+  A module builder defines builder methods, one for each service provided by the module.
+
+  <<This page needs to be rewritten or reorganized; using the bind() method is the preferred way to define services,
+  service builder methods are now used in very limited circumstances.>>
 
   Service builder methods are public methods. They are often static. Here's a trivial example:
 
@@ -79,11 +94,18 @@
 
   The {{{service.html}service}} documentation goes into much greater detail about autobuilding of services. In most cases,
   autobuilding is the <preferred> approach.
+
+  Generally speaking, you should always bind and autobuild your services. The only exceptions are when:
+
+  * You wish to do more than just instantiate a class; for example, to register the class as an event listener with some other service.
+
+  * There is <no implementation class>; in some cases, you can create your implementation on the fly using JDK dynamic proxies or bytecode generation.
   
 {Cacheing Services}
 
-  You will often find yourself in the position of injecting the same services
-  into your service builder or service decorator methods repeatedly. This can be quite
+  You will occasionally find yourself in the position of injecting the same services
+  into your service builder or service decorator methods repeatedly (this occurs much less often since the introduction of
+  service autobuilding). This can result in quite
   a bit of redundant typing.  Less code is better code, so as an alternative, you may define a <constructor> for your
   module that accepts annotated parameters (as with 
   {{{service.html#Injecting Dependencies}service builder injection}}).
@@ -143,10 +165,10 @@
   built by different threads simultaneously. Each module builder class is instantiated at most once, and
   making these fields final ensures that the values are available across multiple threads.
   Refer to Brian Goetz's {{{http://www.javaconcurrencyinpractice.com/}Java Concurrency in Practice}}
-  for a more complete explantation of the relationship between final fields, constructors, and threads ...
+  for a more complete explanation of the relationship between final fields, constructors, and threads ...
   or just trust us!
   
-  Care should be taken with this approach: in some circustances, you may force a situtation in which
+  Care should be taken with this approach: in some circumstances, you may force a situation in which
   the module constructor is dependent on itself. For example, if you invoke a method on any injected services
   defined within the same module from the module builder's constructor,
   then the service implementation will be needed. Creating service implementations
@@ -159,7 +181,7 @@
   When setting up the registry, Tapestry can automatically locate modules packaged into JARs.
   It does this by searching for a particular global manifest entry. 
   
-  The manifest entry name is "Tapestry-Module-Classes".  The value is a comma-seperated list
+  The manifest entry name is "Tapestry-Module-Classes".  The value is a comma-separated list
   of fully qualified class names of module builder classes (this allows a single
   JAR to contain multiple, related modules).  Whitespace is ignored.
   

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/run.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/run.apt?rev=583573&r1=583572&r2=583573&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/run.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/run.apt Wed Oct 10 11:37:44 2007
@@ -44,7 +44,7 @@
 Building the Default Registry
 
   The default registry is available by invoking the static method
-  {{{../apidocs/org/apache/tapestry/ioc/IOCUtilities.html#buildDefaultRegistry()}ICCUtilities.buildDefaultRegistry()}}.
+  {{{../apidocs/org/apache/tapestry/ioc/IOCUtilities.html#buildDefaultRegistry()}IOCUtilities.buildDefaultRegistry()}}.
   This method builds a Registry using
   {{{module.html#Autoloading modules}autoloading logic}}, where modules to load
   are identified via a JAR Manifest entry.

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/service.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/service.apt?rev=583573&r1=583572&r2=583573&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/service.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/service.apt Wed Oct 10 11:37:44 2007
@@ -12,6 +12,34 @@
   words, you should be careful to ensure that your service interface is complete, since
   Tapestry IoC effectively walls you off from backdoors such as casts.
   
+Service Life-cycle
+
+  Every service has a very specific life-cycle.
+
+  * Defined: The service has a definition (from some module) but has not yet been referenced.
+
+  * Virtual: The service has been referenced, so a proxy for the class has been created.
+
+  * Realized: A method on the proxy has been invoked, so the service implementation has 
+    been instantiated, and any decorators applied.
+
+  * Shutdown: The entire Registry has been shut down and with it, all the proxies have been disabled.
+
+  []
+
+  When the Registry is first created, all modules are scanned and the definitions for all services
+  are created.  
+
+  Services will be referenced by either accessing them using the Registry, or as dependencies
+  of other realized services.  
+
+  Tapestry IoC waits until the last possible moment to <realize> the service: that's defined
+  as when a method of the service is invoked. Tapestry is <thread-safe>, so even in a heavily
+  contested, highly threaded envrionment (such as a servlet container or application server) 
+  things <Just Work>.
+
+Service Builder Methods
+
   Tapestry doesn't know how to instantiate and configure your service; instead it relies
   on you to provide the code to do so, in a service builder method:
   
@@ -36,16 +64,18 @@
   all the different ways possible to create a service; those things are best expressed in Java code.
   For a simple case (as here), it would be hard for external configuration (again, in XML or Java annotations)
   to be shorter than "new IndexerImpl()".
+
+  <The above paragraph was written before Binding and Autobuilding were introduced.>
   
   For more complex and realistic scenarios, such as injecting dependencies via the constructor, or
   doing more interest work (such as registering the newly created service for events published by some other service),
   the Java code is simply the most direct, flexible, extensible and readable approach.
   
-Autobuilding
+Binding and Autobuilding
+
+  Tapestry IoC can also <autobuild> your service. Autobuilding is the <preferred> way to 
+  instantiate your services.
 
-  Tapestry IoC can also <autobuild> your service. Autobuilding is an alternate way to register services
-  with the container.
-  
   Every module may have an optional, static bind() method which is passed a
   {{{../apidocs/org/apache/tapestry/ioc/ServiceBinder.html}ServiceBinder}}.  Services may be registered with
   the container by "binding" a service interface to a service implementation:
@@ -64,11 +94,12 @@
 }
 +----+
 
-  You can make repeated calls to bind(), to register more services.
+  You can make repeated calls to ServiceBinder.bind(), to bind additional services.
 
 
   You might ask, "which is better, a builder method for each service, or a bind() method for the module?"  For simple services,
-  those that are just an instantiated instance with simple dependencies, binding is better than building.
+  those that are just an instantiated instance with simple dependencies, binding is better than building. That covers
+  at least 90% of all services, so bind away!
   
   There are many cases, however, where constructing a service is more than just instantiating a class. Often the new service
   will (for example) be registered as a listener with some other service. In other cases, the implementation of the
@@ -171,7 +202,95 @@
   {{{module.html#Caching Services}cache dependency injections}} in your module, by defining
   a constructor.  This reduces duplication in your module.
  
-  
+Disambiguation with Marker Annotations
+
+  In the previous example we were faced with a problem: multiple versions of the JobScheduler
+  service.  They had the same service interface but unique service ids.  If you try to inject
+  based on type, the service to inject will be ambiguous.  Tapestry will throw an exception (identifying
+  the parameter type and the matching services that implement that type).
+
+  The problem is that when injecting a JobScheduler into some other service we need to know 
+  which <one> to inject. Rather than using the service id, another approach is to
+  use a <marker annotation>.
+
+  You may optionally link a service implementation with a marker annotation.
+
+  For example, maybe you have one JobScheduler implementation where the jobs are spread across
+  a number of nodes in a cluster, and you have another JobScheduler where the jobs are all executed exclusively
+  in the current process.
+
+  We can associate those two JobSchedulers with two annotations.
+
++----+
+@Target(
+{ PARAMETER, FIELD })
+@Retention(RUNTIME)
+@Documented
+public @interface Clustered
+{
+
+}
+
+@Target(
+{ PARAMETER, FIELD })
+@Retention(RUNTIME)
+@Documented
+public @interface InProcess
+{
+
+}
+
+
+public class MyModule
+{
+  public static void bind(ServiceBinder binder)
+  {
+    binder.bind(JobScheduler.class, ClusteredJobSchedulerImpl.class).withId("ClusteredJobScheduler").withMarker(Clustered.class);
+    binder.bind(JobScheduler.class, SimpleJobSchedulerImpl.class).withId("InProcessJobScheduler").withMarker(InProcess.class);
+  }
+}
++---+
+
+  Notice that the marker annotations have no attributes.  Further, we support markers on fields
+  (for use in Tapestry components) as well as parameters.
+
+  To get the right version of the service, you use one of the annotations:
+
++---+
+public class MyServiceImpl implements MyService
+{
+  private final JobScheduler _jobScheduler;
+
+  public MyServiceImpl(@Clustered JobScheduler jobScheduler)
+  {
+    _jobScheduler = jobScheduler;
+  }
+
+  . . .
+}  
++---+
+
+  The @Clustered annotation on the parameter is combined with the parameter type (JobScheduler) to find the exact
+  service implementation.
+
+  Why is this better than using the service id?  It's more refactoring-safe.  Service ids can change, which can break
+  your services.  However, using an IDE to rename or move an annotation class or service interface
+  will be able to update all the uses of the annotation or interface.
+
+  With a service builder method, you use the
+  {{{../apidocs/org/apache/tapestry/ioc/annotations/Marker.html}@Marker}} annotation:
+
++---+
+  @Marker(Clustered.class)
+  public JobScheduler buildClusteredJobScheduler()
+  {
+    return . . .;
+  }
++---+
+
+  The @Marker annotation may also be placed on an implementation class, which means that you may omit
+  the call to withMarker() inside the bind() method.
+
 Injecting Dependencies for Autobuilt Services
 
   With autobuilt services, there's no service builder method in which to specify injections.

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/shadow.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/shadow.apt?rev=583573&r1=583572&r2=583573&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/shadow.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/shadow.apt Wed Oct 10 11:37:44 2007
@@ -10,31 +10,31 @@
   
   Effectively, it is used to allow a property of another service to be exposed as its own service.
   
-  For example, the tapestry-core module provides a WebRequest property as a shadow of the RequestGlobals
+  For example, the tapestry-core module provides a Request property as a shadow of the RequestGlobals
   service's request property:
   
 +----+
-public WebRequest build()
+public Request build()
 {
-  return _shadowBuilder.build(_requestGlobals, "request", WebRequest.class);
+  return _shadowBuilder.build(_requestGlobals, "request", Request.class);
 }
 +----+
 
   This can be thought of as similar to:
   
 +----+
-public WebRequest build()
+public Request build()
 {
   return _requestGlobals.getRequest();
 }
 +----+
     
   However there is a <critical> difference between the two:  a shadow property is <re-evaluated on each method invocation>.
-  In the former case, the WebRequest service will always obtain the current value of the request property from the
+  In the former case, the Request service will always obtain the current value of the request property from the
   per-thread RequestGlobals service. The second example is more than likely broken, since it will expose whatever
-  value is in the request property of the RequestGlobals <at the time the WebRequest service implementation is created>.
+  value is in the request property of the RequestGlobals <at the time the Request service implementation is created>.
   
-  Notice that in this example, the WebRequest service is a normal singleton. This service can be freely injected
+  Notice that in this example, the Request service is a normal singleton. This service can be freely injected
   into any service throughout the framework or application. Invoking methods on this service will always delegate
   to the current thread's request. Callers don't have to be aware of this internal delegation; it just happens.