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.
+
++----+
[EMAIL PROTECTED](
+{ PARAMETER, FIELD })
[EMAIL PROTECTED](RUNTIME)
[EMAIL PROTECTED]
+public @interface Clustered
+{
+
+}
+
[EMAIL PROTECTED](
+{ PARAMETER, FIELD })
[EMAIL PROTECTED](RUNTIME)
[EMAIL PROTECTED]
+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/[EMAIL PROTECTED]
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.