Following object-oriented design principles typically lead to applications consisting of many classes that model entities of the real world that belong to the problem domain of the application. During application runtime instances of these classes are created. Each object is responsible for a specific task. To implement the application's functionality the objects have to interact with each other in complex ways. Therefore they need references to their collaborators. In total this forms a complex network of objects in the application's memory.
In its initialization phase an application has to create the network of its objects somehow. This is not trivial, and it is worth thinking about how this task can be achieved best.
One possibility would be to let the objects create their helper objects themselves. Then the application would only need to instantiate the root objects in the network. All other objects would then be created automatically. This solution works, but it is problematic. One point is that the whole construction process is very inflexible. Objects and the references between them are hard-coded in the application's initialization logic. This makes it very hard to change parts of the object network. Also testing of single classes is hard because if a class creates all its helper objects directly, it is hardly possible to inject stub or mock objects for them. Another point is that care must be taken if the network contains shared references. In this case special logic must be added to ensure that the corresponding target objects are not instantiated multiple times.
Another solution could be a lookup service: Objects that need references to other objects do not create these objects directly, but are passed a service object from which they can request references to other objects. The implementation of the lookup service is then responsible for the creation of the requested objects. An example for such a lookup service is JNDI. The lookup service solution improves the situation and solves some of the problems mentioned above. For instance, the lookup service can be mocked itself, and thus it is possible to pass mock or stub objects to classes that are to be tested. However, there is still a lot of boiler plate code needed to construct the whole network of objects. Every class that has references needs to interact with the lookup service.
For some time another approach has become very popular: dependency injection. In a nutshell, dependency injection means that classes are not responsible at all to create objects they depend on or obtain the corresponding references somehow. Rather, they only provide means that allow these references to be set, e.g. through constructor parameters or through corresponding set methods. Further, there is some kind of description that defines which classes need which other helper objects. Another component, the dependency injection container, reads this description and creates the complete object graph based on this information. This involves creating all object instances and initializing the references to the dependent objects. Dependency injection greatly simplifies the construction of complex object networks. Neither the classes involved nor the application's initialization code has to take care about object creation. Instead all the logic required for constructing the graph of objects is implemented in the dependency injection container in a generic way. Classes that are part of the graph only have to adhere to simple conventions so that the needed references can be passed in (e.g. they can be Java beans and define corresponding set methods). This makes testing these classes in isolation very easy because a test class can use the same conventions for passing in mock objects.
Because dependency injection has many advantages there are many implementations of this concept. The most popular ones are certainly Spring and Google Guice. The JGUIraffe library also implements a lean dependency injection framework. You may ask why another dependency injection framework is needed if there are already well-known and proven implementations.
Well, one point is that dependency injection is such a nice concept that it has been adopted by many other frameworks in different areas. For instance, some web application frameworks use it to connect the components implementing business logic together. Also the Swing application framework uses a kind of dependency injection to pass data defined in resource property files to components of the UI. So integrating the concept of dependency injection into frameworks actually makes sense.
Another reason for JGUIraffe's support for dependency injection is that it really plays nicely together with the builder approach outlined in the last section. JGUIraffe uses XML builder scripts to define the graphical user interface of an application. These scripts can also contain definitions for objects to be created, e.g. controller classes or validators. The newly created objects can directly be connected with elements of the UI. For instance, a dialog is typically associated with a controller object. The controller can be defined in the same script as the dialog itself, and it can be passed references to UI components it needs to interact with. After the builder script has been processed all objects have been created and are correctly connected with all their dependent objects - no further initialization is required for the application. We will see how this works in practice later in this section.
Before we come to concrete examples of using dependency injection in JGUIraffe applications some fundamental concepts have to be introduced. The basic idea is that objects to be created are defined in XML builder scripts (processed by Commons Jelly as regular builder scripts). These objects are also referred to as "beans" because they typically follow well-known Java beans conventions. After a builder script with bean definitions have been processed special context objects are available through which the beans can be queried so that instances can be obtained. We give an introduction of the most important interfaces involved in this process.
When working with the dependency injection framework it is all about bean
definitions: a builder script is processed, and the beans defined there are
stored in internal data structures from where they can be accessed by the
application. An important data structure for storing bean defininitions is
a BeanStore.
A BeanStore
groups a number of bean definitions
together and provides a very basic API to access them. An application
rarely uses a BeanStore
directly because there are other
classes implementing a more convenient API on top of it. However, in builder
scripts tags can be used for creating bean stores and for assigning bean
definitions to them. This can make sense if there is a logic grouping for
the beans used by an application and the beans should really be separated.
(If it is more convenient for an application to store all their beans in a
single place, it need not worry much about bean stores). An important point
to know about bean stores is that they form a hierarchical structure: a
BeanStore
can have a parent bean store. This also is related to
the visibility of beans. A child bean store can access bean definitions in
its parent, but not vice versa. For typical use cases this hierarchy is
convenient. When an application starts up a root bean store is created and
populated with bean definitions. If later another window is to be opened -
say a dialog form -, the root bean store becomes the parent of the bean
store created when processing the builder script for the new window. Thus it
is possible to access the beans defined in the root store from the newly
created window.
As said before, a BeanStore
is mainly used internally by the
framework to organize bean definitions. The interface used by clients is
BeanContext.
A BeanContext
provides a bunch of methods for
accessing beans. Internally, it is associated with a BeanStore
.
When it is queried for a bean it searches the BeanStore
and its
parents if necessary until a corresponding bean definition is found. Then
the bean is created if required and returned. If a bean is to be newly
created, all dependencies for this bean have to be resolved. This can cause
the creation of many other beans, depending on the dependencies defined for
this bean. If the bean can be reused, it is cached so that it can be directly
returned when it is requested the next time.
The following example shows how a BeanContext
object is used
for querying a bean with a given name:
BeanContext beanCtx = ... // obtain the context MyBeanClass bean = (MyBeanClass) beanCtx.getBean("myBeanName");
Each bean definition has a unique name, by which it can be accessed from a
BeanContext
. Alternatively, the bean can be specified by its
class - provided that there is only a single bean definition using this
class. In this case the example looks as follows:
BeanContext beanCtx = ... // obtain the context MyBeanClass bean = beanCtx.getBean(MyBeanClass.class);
Note that the type cast is no more necessary because the method uses Java's
generics. Note further that the class passed to getBean()
does
not have to match exactly the class in the bean definition; rather, it can
be a super type (including an interface). This conforms to good practice:
the application can program against an interface which is used in the
getBean()
call; in the bean definition the concrete
implementation is provided.
In addition to the getBean()
methods the
BeanContext
interface defines methods for listing the available
bean definitions and for checking whether a specific bean definition exists
in the context. Most methods come in two variants: One variant takes a
BeanStore
object as argument, and the other does not. The method
without the BeanStore
argument operates on the default bean
store set for this BeanContext
. The other variant accesses the
specified BeanStore
. (Because a BeanContext
mainly implements additional logic on top of a BeanStore
it
can operate on an arbitrary BeanStore
instance.)
Now where we have discussed how a BeanContext
object can be
used to query beans, you certainly want to know how a
BeanContext
can be created and initialized. Setting up a
BeanContext
requires a builder which can process a
bean definition file. There are two options:
In order to invoke a bean builder, the following steps have to be performed:
BeanBuilderFactory
has to be obtained from the central
Application
object.
BeanBuilder
instance can be created.BeanBuilder
class defines a build()
method
which processes a Jelly script with bean definitions. It returns a
BeanBuilderResult
object with the results of the builder
operation.BeanBuilderResult
object allows access to all
BeanStore
objects created while the builder script was
processed. From the information stored in this object a BeanContext
can be created.BeanBuilderFactory factory = application.getBeanBuilderFactory(); try { BeanBuilder builder = factory.getBeanBuilder(); Locator beanDefs = ... // locator to the script to be processed BeanBuilderResult result = builder.build(beanDefs, null, null); BeanContext beanContext = new DefaultBeanContext(result.getBeanStore(null)); } catch(BuilderException bex) { // exception handling }
Obtaining the BeanBuilderFactory
and the BeanBuilder
from the factory should be clear. Then a
Locator
object is created that points to the bean definition file to be
processed. This object is passed to the builder's build()
method as first argument. For the other arguments null is passed,
which means that default values are used. The second argument is of type
MutableBeanStore
. If here an object is provided, all bean
definitions parsed are added to this store. Otherwise, a new
BeanStore
is created. The third parameter of the
build()
method is a
ClassLoaderProvider
object. We come back to this class later in
the section with advanced topics. For now it is sufficient to know that
null can be passed for a default provider.
build()
returns a
BeanBuilderResult
object. This object can be queried for all
BeanStore
objects created during the builder operation. As
mentioned earlier, bean definitions can be organized in multiple bean
stores. Each bean store has a unique name. The bean store with the name
null is the root bean store, i.e. the top element in the hierarchical
structure of bean stores. The example script obtains this root bean store
and passes it to a newly created
DefaultBeanContext
object. DefaultBeanContext
is the
default implementation of the BeanContext
interface. Note that
both the BeanBuilderFactory
and the BeanBuilder
can throw a
BuilderException
if something goes wrong. So this exception has
to be handled.
The BeanBuilder
API is pretty basic and thus it is harder to
use. The other builders provided by the JGUIraffe library which
are responsible for the creation of UI elements are implemented on top of
the bean builder. They hide much of the complexity of the bean builder API.
The key here is the
BuilderData
object that has to be passed to the builder: After
the builder operation completes it contains some fully initialized objects
allowing access to bean stores and bean definitions created while processing
the builder script. This includes
BeanBuilderResult
object which provides access to all
BeanStore
objects, andBeanContext
object associated with the root bean store.BeanContext
becomes much simpler:
Builder builder = application.getApplicationContext().newBuilder(); ApplicationBuilderData builderData = application .getApplicationContext().initBuilderData(); try { Locator locator = ClassPathLocator.getInstance("mybeans.jelly"); builder.build(locator, builderData); BeanContext beanContext = builderData.getBuilderContext(); } catch(BuilderException bex) { // exception handling }
This is pretty similar to the example code introduced in the section
Invoking a builder. The
BuilderContext
can be obtained directly from the
BuilderData
object. Again, a BuilderException
has
to be handled. In the next section we deal with Jelly scripts that contain
bean definitions and discuss the different ways of defining beans.
Scripts that are used to define beans must be complient with Commons Jelly which is used to interpret them. Therefore they have to be valid XML documents.
Structure of bean definition scripts
The following code fragment shows the fundamental structure of such a builder script:
<?xml version="1.0" encoding="ISO-8859-1"?> <j:jelly xmlns:j="jelly:core" xmlns:di="diBuilder"> <!-- Beans to be stored in the root store.--> <di:bean name="rootBean" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"/> ... <!-- A bean store definition with nested beans.--> <di:store name="beanStore1"> <di:bean name="store1Bean" beanClass="..."/> ... </di:store> ... <!-- Another bean store definition.--> <di:store name="beanStore2" parentName="beanStore1"> ... </di:store> </j:jelly>
The document starts with a typical XML declaration that also defines the
encoding (the encoding is optional). Then the root element
<j:jelly>
with the namespace declarations follows. Two
namespace prefixes are defined:
Below the root element beans can be defined using <di:bean>
tags. If not specified otherwise, these bean definitions are added to the
root BeanStore
. The root bean store is either passed to the
builder's build()
method or it is created automatically.
With the <di:store>
tag additional bean stores can be
created. The beans that should be stored in these stores can be defined by
nested <di:bean>
tags. Alternatively the
<di:bean>
tag supports the store
attribute
that can be used to specify the name of the target bean store. Because bean
stores can form a hierarchical structure the <di:store>
tag supports a parentName
attribute; here the name of the parent
store can be provided. If this attribute is undefined, the new bean store
becomes a child of the root bean store.
The example script in the previous section already showed the declaration of a simple bean:
<di:bean name="rootBean" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"/>
A bean must have a unique name. This is the name that is also passed to a
BeanContext
when querying the bean. In the most simple form the
class of the bean is specified using its fully qualified name. If this form
of a bean declaration is used, the class must have a default constructor.
This constructor is invoked to create the bean instance. By the way, this
example and most of the following ones use the
ReflectionTestClass
class to demonstrate the various ways of
defining beans. The source code of this class can be found below:
package net.sf.jguiraffe.di; public class ReflectionTestClass { /** A test member field. */ private String stringProp; /** Another test member field. */ private int intProp; /** A property storing arbitrary data.*/ private Object data; /** * Creates a new instance of <code>ReflectionTestClass</code>. Default ctor. */ public ReflectionTestClass() { this(null, 0); } /** * Creates a new instance of <code>ReflectionTestClass</code> and initializes * the properties. * * @param s the string property * @param i the int property */ public ReflectionTestClass(String s, int i) { this(i); stringProp = s; } /** * Creates a new instance of <code>TestClass</code> and initializes * the string property. * * @param s the string property */ public ReflectionTestClass(String s) { this(s, 0); } public String getStringProp() { return stringProp; } public void setStringProp(String stringProp) { this.stringProp = stringProp; } public int getIntProp() { return intProp; } public void setIntProp(int intProp) { this.intProp = intProp; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } /** * Initializes the properties of this instance. * * @param s the string property * @param i the int property * @return the old value of the string property */ public String initialize(String s, int i) { String result = getStringProp(); setStringProp(s); setIntProp(i); return result; } /** * A static factory method for testing static method invocations. * * @param s the string property * @param i the int property * @return the newly created instance */ public static ReflectionTestClass getInstance(String s, int i) { return new ReflectionTestClass(s, i); } }
Well, this class does not do anything meaningful, but there are many ways to construct new instances, including various constructors and a static factory method. These will be used by the examples following in this section.
Singleton and non-singleton beans
A singleton is a class from which only a single instance exists during runtime of an application. The singleton pattern is often used to hold central information that must be accessible from any parts of an application. If implemented by hand, typically static instance fields and methods are used to ensure the singleton property.
Because the dependency injection framework controls when an instance of a
bean is created it is easy to make specific beans a singleton. The framework
only has to cache the instance after its creation and return it again if it
is queried later on. Actually this is the default behavior for bean
definitions. Whether a bean is a singleton or not is controlled by the
boolean singleton
attribute of the <bean>
tag whose default value is true:
<!-- A singleton bean --> <di:bean name="singletonBean" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"/> <!-- Not a singleton bean --> <di:bean name="nonSingletonBean" singleton="false" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"/>
In this example the first bean is a singleton while the other is not. If this script is processed by a builder, the following assertians are true:
ReflectionTestClass bean1 = (ReflectionTestClass) beanContext.getBean("singletonBean"); assert bean1 == beanContext.getBean("singletonBean"); ReflectionTestClass bean2 = (ReflectionTestClass) beanContext.getBean("nonSingletonBean"); assert bean2 != beanContext.getBean("nonSingletonBean");
So every time a non-singleton bean definition is accessed, a new instance of this bean is created.
The <di:bean>
tag allows defining beans that are assigned
constant values. These beans are created immediately when the script is
processed by the builder. They are always singletons. This is typically
used for primitive values. The following example fragment shows some
constant definitions:
<di:bean name="intConst" value="42" valueClass="java.lang.Integer"/> <di:bean name="strConst" value="Test"/>
Here a java.lang.Integer
and a java.lang.String
bean are defined. The value
attribute of the
<di:bean>
tag is responsible for assigning a value. With
the valueClass
attribute the data type of the value can be
specified. It is String per default; so in case of the integer value it must
be provided in order to perform the correct type conversion.
So far beans were created only by calling their default constructor. Of
course, it is possible to invoke other constructors as well. This is done
using a nested <di:constructor>
tag. In the next
example the constructor of ReflectionTestClass
is invoked that
expects the values of the string and the integer property:
<di:bean name="constructor" beanClassName="net.sf.jguiraffe.di.ReflectionTestClass"> <di:constructor> <di:param parameterClass="java.lang.String" value="test string"/> <di:param value="42"/> </di:constructor> </di:bean>
The <di:constructor>
tag can have an arbitrary number of
nested <di:param>
tags representing the parameters of the
constructor to be invoked. The value
attribute of the
<di:param>
tag specifies the value to be passed. With
the optional parameterClass
attribute the data type of the
parameter can be specified. This is normally not necessary because the
type can be inferred by the parameters of the constructor. However, if there
are multiple constructors with similar parameter types it may be required
to use the attribute to avoid ambiguities. If values need to be converted to
other types, the valueClass
attribute can be provided.
More information about parameters with different data types can be found in
the sub section Parameter matching and
data type conversions later in this chapter.
Invoking a static factory method
What if the bean to be created does not define a constructor, but it uses a
static factory method? The ReflectionTestClass
class used in
the tests has a static getInstance()
method that can also be
invoked to create new instances. In the builder script the
<di:factory>
tag in collaboration with the
<di:methodInvocation>
tag is used to achieve this:
<di:bean name="staticFactory" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"> <di:factory> <di:methodInvocation method="getInstance" targetClass="net.sf.jguiraffe.di.ReflectionTestClass"> <di:param value="test String"/> <di:param value="42"/> </di:methodInvocation> </di:factory> </di:bean>
With the <di:methodInvocation>
tag (which is implemented
by the
MethodInvocationTag
class) the method to be invoked is specified.
Parameters are provided as nested <di:param>
tags. In this
case we invoke a static method on the ReflectionTestClass
class
- as defined by the targetClass
attribute. The
<di:factory>
tag also allows invoking methods on other
beans. This way non-static factory methods can be used, too. More information
can be found in the documentation of the
FactoryTag
class.
If a bean class fully adheres to the Java Beans specification, it has a
standard constructor and get and set methods for its properties. To create
and initialize such a bean first the standard constructor must be called,
then the properties can be set. We have already seen how to invoke the
standard constructor of a class. With nested <di:setProperty>
tags properties of the newly created bean can be set.
<di:bean name="propertiesBean" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"> <di:setProperty property="intProp" value="42"/> <di:setProperty property="stringProp" value="This is a test."/> </di:bean>
The attributes of the <di:setProperty>
tag are very
similar to the <di:param>
tag. A full description of all
supported features can be found in the documentation of the
SetPropertyTag
class. The property names passed to the
property
attribute conform to the Java Beans specification: A
property named foo requires the existence of a public method named
setFoo() in the corresponding class.
At the beginning of this chapter it was stated that the major advantage of the dependency injection approach is that dependencies to other objects are automatically provided when beans are created. So far we have not dealt with dependencies yet.
References to other beans can be specified in both constructor arguments or
when setting properties. Actually the <di:setProperty>
and the <di:param>
tags work very similar in the way their
data can be specified. The following options are supported:
value
attribute
(plus an optional valueClass
or valueClassName
attribute). We have seen this before.refName
attribute the reference to another bean
can be specified. Here the name of the bean has to be provided. A bean with
this name must exist in the same bean store or in one of its parents.refClass
attribute. This is analogous to the
getBean()
methods of the BeanContext
class which
allow requesting a bean by name or by its class.<di:bean>
tag
in the body of a <di:setProperty>
or
<di:param>
tag. Then this bean becomes the data of the
nesting tag.
We provide some examples for these features. In the first example we use
the refName
attribute to refer to other beans in the script.
In the subsection Constant values a string and an
integer constant have been defined. Now we reference these beans:
<di:bean name="constantDependencies" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"> <di:constructor> <di:param refName="strConst"/> </di:constructor> <di:setProperty property="intProp" refName="intConst"/> </di:bean>
As can be seen, it is possible to mix the invocation of constructors with the setting of properties. Here we call the constructor that expects a string parameter and pass the constant string bean. Then we set the integer property and pass the constant integer bean.
In the next example an instance of ReflectionTestClass
is
defined, and its data
property is assigned another instance
which is defined in place (the data
property is of type
java.lang.Object
, so it accepts properties of all types).
<di:bean name="inplaceBean" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"> <di:setProperty property="data"> <di:bean beanClass="net.sf.jguiraffe.di.ReflectionTestClass"> <di:constructor> <di:param refName="strConst"/> <di:param refName="intConst"/> </di:constructor> </di:bean> </di:setProperty> </di:bean>
In this case the <di:setProperty>
tag has no attribute
defining any value to be set. Instead, the value is specified by the
<di:bean>
tag in the body of the tag. Here another
ReflectionTestClass
instance is defined and initialized. Note
that this nested <di:bean>
tag does not have a
name
attribute; it is an anonymous bean declaration that is
only used in this specific place. What is demonstrated here for the
<di:setProperty>
tag works exactly the same for the
<di:param>
tag. Thus references to other beans can be
passed to constructors or other methods as well.
A related question is how to specify a null value. For instance,
ReflectionTestClass
has a constructor that expects a string and
an integer parameter. How can this constructor be invoked passing in
null for the string? This can be achieved using the
<di:null>
tag:
<di:bean name="nullParam" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"> <di:constructor> <di:param><di:null/></di:param> <di:param value="42"/> </di:constructor> </di:bean>
Some beans have properties or constructor arguments that require a collection. The dependency injection tag library provides several tags for creating various types of collections. Here is an example of how a list can be created and passed as value to a property:
<di:bean name="list" beanClassName="net.sf.jguiraffe.di.ReflectionTestClass"> <di:setProperty property="data"> <di:list elementClassName="java.lang.Integer"> <di:element value="1"/> <di:element value="2"/> <di:element value="1000" valueClassName="java.lang.Long"/> </di:list> </di:setProperty> </di:bean>
The <di:list>
tag (implemented by the
ListTag
class) creates the list. With the optional attributes
elementClass
or elementClassName
the class of the
single list elements can be specified. <di:element>
tags
in the body of the <di:list>
tag define the list elements.
They inherit the class of the elements, so that the string values in this
example are automatically converted to integer values. The last
<di:element>
tag overrides this class using the
valueClassName
attribute - it produces a Long
value.
The <di:element>
tag provides multiple ways to define the
element to be added to the list. Full documentation is available at the
ElementTag
class which implements this tag. Actually the tag
supports the same options as the <di:setProperty>
or the
<di:param>
tags. For instance, list elements can be defined
as references to other beans, or by nested anonymous bean definitions. The
following example shows some of these possibilities:
<di:bean name="listDependency" beanClassName="net.sf.jguiraffe.di.ReflectionTestClass"> <di:setProperty property="data"> <di:list> <di:element/> <di:element refName="intConst"/> <di:element> <di:bean beanClass="net.sf.jguiraffe.di.ReflectionTestClass"/> </di:element> </di:list> </di:setProperty> </di:bean>
Here the first <di:element>
tag is empty. It produces a
null element that is added to the list. The second tag refers to the
bean with the name intConst. The third one evaluates the nested
bean declaration and creates another instance of the
ReflectionTestClass
class. If a java.util.Set
is
needed rather than a java.util.List
, the declaration looks
very similar: the <di:list>
tag has to be replaced by
the <di:set>
tag:
<di:bean name="set" beanClassName="net.sf.jguiraffe.di.ReflectionTestClass"> <di:setProperty property="data"> <di:set elementClassName="java.lang.Integer"> <di:element value="1"/> <di:element value="2"/> <di:element value="1000" valueClassName="java.lang.Long"/> </di:set> </di:setProperty> </di:bean>
This example works analoguously to the first example for lists, just that a
java.util.HashSet
is created and populated by the
<di:element>
tags. The <di:set>
tag
supports an additional ordered
attribute of type boolean. If
set to true, the order of the elements added to the set is kept -
in this case the tag creates a java.util.LinkedHashSet
object
rather than a java.util.HashSet
instance:
<di:bean name="set" beanClassName="net.sf.jguiraffe.di.ReflectionTestClass"> <di:setProperty property="data"> <di:set elementClassName="java.lang.Integer" ordered="true"> <di:element value="1"/> <di:element value="2"/> <di:element value="1000" valueClassName="java.lang.Long"/> </di:set> </di:setProperty> </di:bean>
The definition of a map looks pretty similar. The <di:map>
tag is used (implemented by the
MapTag
class) which supports optional attributes for specifying
the class of the map's keys and values. Instead of the
<di:element>
tag the <di:entry>
tag is
used to define a key-value-pair to be added to the map:
<di:bean name="map" beanClassName="net.sf.jguiraffe.di.ReflectionTestClass"> <di:setProperty property="data"> <di:map keyClass="java.lang.String" valueClass="java.lang.Integer"> <di:entry key="key1" value="1"/> <di:entry key="key2" value="2"/> <di:entry key="key3" value="1000" valueClass="java.lang.Long"/> </di:map> </di:setProperty> </di:bean>
This example creates a map with strings as keys and numbers as values. The
default value class is java.lang.Integer
, the last
<di:entry>
tag however produces a java.lang.Long
object. <di:entry>
can be used analogously to
<di:element>
to define complex values, e.g. by
referencing other beans or by defining beans in the tag's body. Refer to the
documentation for the
EntryTag
class for a full list of all supported attributes. But
what if the key of the entry is a complex object? In this case the
<di:entryKey>
tag can be used. It also supports all
options of defining complex objects which then become the key of the entry.
The following example demonstrates this. It creates a map with an entry
whose key is a list and whose value is a set:
<di:bean name="mapcomplex" beanClassName="net.sf.jguiraffe.di.ReflectionTestClass"> <di:setProperty property="data"> <di:map ordered="true"> <di:entry> <di:entryKey> <di:list elementClassName="java.lang.Integer"> <di:element value="1"/> <di:element value="2"/> <di:element value="1000" valueClassName="java.lang.Long"/> </di:list> </di:entryKey> <di:set elementClassName="java.lang.Integer" ordered="true"> <di:element value="1"/> <di:element value="2"/> <di:element value="1000" valueClassName="java.lang.Long"/> </di:set> </di:entry> </di:map> </di:setProperty> </di:bean>
As is true for the <di:set>
tag, <di:map>
supports the ordered
attribute. If set to true, a
java.util.LinkedHashMap
is created which keeps track of the
order in which entries have been added.
Finally, there is a special tag for defining java.util.Properties
objects. Properties objects are similar to maps, but both keys and values
are strings. The definition of a Properties
object is almost
identical to the definition of a map: only the <di:map>
tag has to be replaced by the <di:properties>
tag:
<di:bean name="properties" beanClassName="net.sf.jguiraffe.di.ReflectionTestClass"> <di:setProperty property="data"> <di:properties> <di:entry key="db.usr" value="scott"/> <di:entry key="db.pwd" value="tiger"/> <di:entry key="db.src" value="defaultDS"/> </di:properties> </di:setProperty> </di:bean>
When string values have to be passed to constructors or properties, so far
we have used the value
attribute of the <param>
or <setProperty>
tag for this purpose. This works fine
for simple strings, but fails if the strings are longer and should span
multiple lines. In this case the <value>
tag (implemented
by the
ValueTag
class) can be used. <value>
tag
obtains the string value from its body which can become arbitrary complex.
It is recommended to put the body in a CDATA section so that
special characters can be used without quoting. The following example passes
a string as constructor parameter that spans multiple lines:
<di:bean name="valueTest" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"> <di:constructor> <di:param> <di:value><![CDATA[Line 1 Line 2 Line 3]]></di:value> </di:param> </di:constructor> </di:bean>
It is sometimes necessary to reference a value that is defined as a
constant (a public static final member field) in a class.
For this purpose, the DI library offers the <const>
tag
(which is implemented by the
ConstantValueTag
class). Consider for instance the following
class that defines such a constant:
package com.acme.data; public class Constants { public static final int ANSWER = 42; }
In order to create an instance of the reflection test class passing in the
ANSWER
constant as value for the constructor's
int parameter, the following fragment can be used:
<di:bean name="constantTest" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"> <di:constructor> <di:param value="The question"> <di:param> <di:const targetClass="com.acme.data.Constants" field="ANSWER"/> </di:param> </di:constructor> </di:bean>
The tag fetches the value of the referenced field from the given target class and passes it to an enclosing tag. Alternatively, the var attribute can be used which assigns the field value to a variable in the Jelly context.
There is another option to directly expose the values of constants as
beans. For instance, a Java class may define a default instance as an
INSTANCE property; or Scala objects are exposed to Java as a
static field named MODULE$. This can be achieved by a combination
of the already discussed <const>
tag and the
<contextBean>
tag (implemented by the
ContextBeanTag
class). The latter obtains an object stored
in the Jelly context and makes it available as a bean in the current bean
context. The idea is that the <const>
tag is invoked
with its var attribute to store the value of the constant field in
the Jelly context. From there it can be accessed and exposed by the
<contextBean>
tag. The following example shows how this
looks like for a Scala object implementing a service:
<di:const var="playlistServiceVar" targetClassName="com.acme.audio.playlist.service.PlaylistService$" field="MODULE$"/> <di:contextBean var="playlistServiceVar" name="playlistService"/>
Here the PlaylistService
can now be accessed from the bean
context under the name playlistService.
We have now discussed the most frequently used methods of defining beans
using the tags of the dependency injection library. With these tags it
should be possible to realize most of the use cases related to the creation
of objects. For complex scenarios, for which these tags are not sufficient,
there are some so-called invocation tags that allow invoking
arbitrary methods on objects. If these tags are used, temporary objects can
be created and stored in a local context that is valid during the processing
of the nesting <di:bean>
tag. Other tags, like
<di:setProperty>
or <di:param>
can
access these temporary objects. We provide an example that creates and
initializes a ReflectionTestClass
instance in a complicated way:
<di:bean name="initializer" singleton="false" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"> <di:constructorInvocation targetClassName="java.lang.StringBuilder" result="buf"/> <di:methodInvocation method="append" source="buf"> <di:param parameterClass="java.lang.String" refName="strConst"/> </di:methodInvocation> <di:methodInvocation method="append" source="buf"> <di:param parameterClass="java.lang.String" refName="strConst"/> </di:methodInvocation> <di:methodInvocation method="toString" source="buf" result="s"/> <di:setProperty property="stringProp" var="s"/> <di:methodInvocation method="valueOf" targetClass="java.math.BigInteger" static="true" result="i1"> <di:param refName="intConst"/> </di:methodInvocation> <di:constructorInvocation targetClass="java.math.BigInteger" result="i2"> <di:param value="2"/> </di:constructorInvocation> <di:methodInvocation method="multiply" source="i1" result="product"> <di:param var="i2"/> </di:methodInvocation> <di:methodInvocation method="intValue" source="product" result="i"/> <di:setProperty property="intProp" var="i"/> </di:bean>
What happens here? A set of <di:constructorInvocation>
and <di:methodInvocation>
tags is used for creating and
manipulating some temporary objects which are eventually passed to properties
of the bean created by the whole fragment. Note the special attributes of
these tags:
source
defines the name of the temporary object on which
a method is to be invoked.result
specifies the name under which the result of a
method invocation is stored in the temporary context. With other words,
this creates a kind of variable.<di:constructorInvocation>
tag to create a new instance of java.lang.StringBuilder
and
stores it as a temporary object under the name buf. Then, using the
<di:methodInvocation>
tag, the append()
method is invoked twice on this object passing in the bean with the name
strConst. After that the content of the StringBuilder
is transformed into a string by calling the toString()
method
and stored under the name s.
The next series of invocations creates two instances of
java.math.BigInteger
, one by invoking the static
valueOf()
method and one by calling a constructor. Then the
product of these objects is created by invoking the multiply()
method and stored under the name product. Finally the product is
transformed into an integer object by invoking the intValue()
method on it. <di:setProperty>
tags are used to pass the
results of these operations to properties of the newly created bean.
Well, this is certainly complex stuff, and we do not encourage such a
programming style. It is obvious that such scripts are hard to understand
and maintain. However, if you are forced to deal with legacy code, you might
have no other option than performing complex method invocations in builder
scripts. The last example shows that you can do so using the dependency
injection tag library. For a full documentation of the invocation tags
refer to the classes
ConstructorInvocationTag
and
MethodInvocationTag
.
If a class supports a bunch of configuration options which should be set at construction time, you may end up with a large number of different constructors: each constructor expects a specific set of configuration options while the remaining ones are set to default values. This allows a client to concentrate on the options of interest ignoring any others.
In such a scenario, a builder approach (not to be confused with the JGUIraffe builder scripts!) can be beneficial. Rather than providing many constructors, the class in question provides a specific builder class. The builder is instantiated and populated with the required options. Often this can be done through a fluent API which simplifies the usage of the builder for clients. Eventuelly, the builder creates an instance of its associated class based on the properties set so far. Typically, the class only provides a private constructor so that the builder is the only possibility to create instances.
An example of a class which provides a builder is
BasicThreadFactory
from the
Apache Commons Lang project.
The builder allows setting options like a naming pattern, a priority, or
the daemon flag for the threads to be created by the factory. The following
code fragment shows how this looks like in practice:
BasicThreadFactory factory = new BasicThreadFactory.Builder() .namingPattern("factory_thread-%d") .daemon(true) .build();
While builders can be used elegantly from Java code, they are problematic
for dependency injection frameworks. First an object of a different class
than the actual bean class has to be created, then methods have to be called
on it, and finally the result of the build()
method must be
used as the resulting bean. With the features introduced in the previous
section you know how objects can be created and manipulated within complex
builder scripts. The only missing piece is that you somehow have to tell the
framework to use the result of a method invocation as resulting bean. This
can be achieved by using the resultVar attribute of the
<di:bean>
tag. It tells the tag that it does not have to
create a bean on its own, but use an object from a local variable of the
initializer script instead. Below is an example which corresponds to the
Java code presented above:
<di:bean name="threadFactory" resultVar="factory" beanClass="java.util.concurrent.ThreadFactory"> <di:constructorInvocation result="builder" targetClassName="org.apache.commons.lang3.concurrent.BasicThreadFactory$Builder"/> <di:methodInvocation method="namingPattern" source="builder"> <di:param value="factory_thread-%d"/> </di:methodInvocation> <di:methodInvocation method="daemon" source="builder"> <di:param value="true"/> </di:methodInvocation> <di:methodInvocation method="build" source="builder" result="factory"> </di:methodInvocation> </di:bean>
Here a new instance of the nested Builder
class is created
using a <di:constructorInvocation>
tag and stored in a
local variable named builder (using the result
attribute). Then on this object multiple methods are invoked. The last
method call is to the build()
method, and its result is stored
in the factory variable. Note that this name is also used in the
resultVar
attribute of the <di:bean>
tag;
this establishes the connection to the tag's managed bean. Note also that
on the embedding <di:bean>
tag the beanClass
attribute is defined. Defining the class of the resulting bean at this place
is not strictly necessary for the correct creation of the bean. However, it
allows the tag to correctly answer queries about the class of its managed
bean, even if the bean has not yet been created. If the attribute was
ommitted, querying the BeanContext
for a bean of this class
would fail. Therefore, the bean class should always be specified if the
resultVar
attribute is used.
Another use case for complex initialization scripts are factory beans: In cases when the creation of an object requires complex logic, it makes sense to extract this logic in a specific factory class. In order to create an instance of the original class, a reference to the factory object has to be obtained, and a specific creation method has to be invoked on it.
The factory beans use case is similar to the problem with
Builder classes, but while a builder instance
was created and stored in a variable local to the initialization script, we
now have to reference another bean declared in the builder script. For this
purpose, the dependency injection tag library provides the
InvocationTargetTag
tag. The tag can appear in the body of a
MethodInvocationTag
and references the target bean of the method
invocation. It supports the possible dependencies as described in the
section Dependencies to other beans. Below is
an example which invokes the method create()
on a bean with
the name factoryBean:
<di:bean name="factoryBeanInvocation" resultVar="result"> <di:methodInvocation method="create" result="result"> <di:param value="someParameterValue"/> <di:invocationTarget refName="factoryBean"/> </di:methodInvocation> </di:bean>
The <di:bean>
tag again has the resultVar
attribute to point to the local variable in which the result of the method
invocation is stored. As you can see, it is possible to pass parameters to
the method of the factory bean in the usual way.
With the tags introduced so far complex bean definitions can be realized. In this section we discuss some edge cases of dependency management and provide some information about enhanced functionality which can be useful in special use cases.
When a bean is defined using the tags of the dependency injection framework
all its dependencies can be declared. When this bean is requested from a
BeanContext
the dependent beans are resolved and are also
created. If these beans declare additional dependencies, this can lead to a
chain of beans being created. Problems can occur if cyclic dependencies
occur. In the most simple case the cycle consists only of two beans
referencing each other as shown in the following picture:
Here bean A has a dependency to bean B and vice versa. In more complex scenarios the cycle can span multiple beans, e.g. bean A depends on bean B which depends on bean C which again depends on bean A. Whether this causes a problem or not depends on the way the dependencies are passed. Consider the following bean declarations:
<!-- Cyclic dependencies! Does not work! --> <di:bean name="beanA" beanClassName="net.sf.jguiraffe.test.BeanA"> <di:constructor> <di:param refName="beanB"/> </di:constructor> </di:bean> <di:bean name="beanB" beanClassName="net.sf.jguiraffe.test.BeanB"> <di:constructor> <di:param refName="beanA"/> </di:constructor> </di:bean>
Here it is tried to pass the beans to each other using the constructors. In this scenario it is impossible to find a working order in which the beans can be created: for the creation of bean A bean B must be available and vice versa. Because of this the framework gives up and throws an exception.
To work around this problem a valid order has to be found in which the beans can be created thus breaking the cycle. This can be achieved if one of the beans is not passed as constructor argument, but as a property as in the following example:
<!-- Cyclic dependencies! This works. --> <di:bean name="beanA" beanClassName="net.sf.jguiraffe.test.BeanA"> <di:constructor> <di:param refName="beanB"/> </di:constructor> </di:bean> <di:bean name="beanB" beanClassName="net.sf.jguiraffe.test.BeanB"> <di:setProperty property="breference" refName="beanA"/> </di:bean>
The framework is able to separate the creation of beans from their initialization. In this case it detects that there is still a cyclic dependency between the beans A and B. For the creation of bean A bean B is required. But bean B can be created without any dependencies (through its default constructor). So the framework creates bean B first, but defers its initialization (the set property operation). Then bean A can be created; it is passed the (so far not completely initialized) reference to bean B. Finally, the initialization of bean B can be completed because now bean A is available.
So the framework is usually smart enough to find a working creation order of dependent beans if possible. However, in this case the beans must be aware that the references passed to them may be only partly initialized. Only after the whole graph of beans has been created initialization is complete. This also works if the cycle contains more than two dependent beans.
Dependencies between singleton and non-singleton beans
If singleton beans have dependencies to non-singleton beans or vice versa, it may not always be obvious when and how many instances of the beans invloved are created. This sub section discusses some scenarios. We start with some simple cases. First imagine a singleton bean that references a non-singleton bean (Note: in this diagram and the following ones singleton beans are marked with a small "S" icon):
When the singleton bean A is accessed the first time, it is created and initialized. In this process also a new instance of the non-singleton bean B is created and passed to the new A instance. Because A is a singleton, the instance is then cached and directly returned if bean A is requested again. Thus the reference to bean B remains constant, and only a single instance of this bean is created (at least as bean A is concerned).
Next let us have a look at the opposite scenario: a non-singleton bean has a dependency to a singleton bean:
Now each access to bean B creates a new instance. Because bean A is a singleton, only a single instance of this bean is created. So all the different instances of bean B have the same reference to bean A.
So far there was no big surprise. Things become more complicate when multiple bean declarations are involved. Have a look at the following figure:
When bean A is created the dependent beans B and C
must be created, too. Both are non-singletons. Bean C also has a
dependency to bean B. So how many instances of bean B are
created? The answer is: only one, i.e. bean A and bean C
share the same reference to bean B. When a bean is requested from
a BeanContext
all steps necessary to create this bean and all
of its dependencies run in a kind of transaction. During this transaction
only one instance of non-singleton beans is created. So if a non-singleton
bean is referenced by multiple beans that are involved in a transaction, all
these beans are initialized with the same reference.
The BeanContext
interface defines methods for adding and removing an object of
type
BeanCreationListener
. A BeanCreationListener
is
notified whenever a bean is created. Note that this does not mean for every
request of a bean, but only if a new instance of this bean is actually created,
the listener is triggered. For a singleton bean the listener is invoked only
once; for non-singleton beans it is called each time an instance is
requested.
When a bean is created the beanCreated()
method of the listener
is called. This method is passed a
BeanCreationEvent
object that contains information about the
newly created bean. This includes of course the new instance of the bean,
but also some internal helper objects involved in the bean creation
process (refer to the Javadocs for more information).
So what is the purpose of a BeanCreationListener
? Such listeners
come in handy if a bean needs information which is not available at
configuration time, but only at application runtime. In this case a
BeanCreationListener
can intercept the bean creation process
and provide the additional information. At the time the listener is called
the bean has already been fully initialized according to its configuration.
The listener only has to add the missing initialization.
As a concrete example consider a bean that needs to call a service for which
user credentials are required, e.g. a web service or a database connection.
For security reasons you do not want to store these credentials in a
configuration file. One solution is to ask the user for the credentials and
then use a BeanCreationListener
to pass this data to the
service bean when it is created.
As was said before, a BeanCreationListener
can be added manually
to a BeanContext
by calling the addBeanCreationListener()
method. If a standard builder is used (rather than a
BeanBuilder
), listeners can also be added to the
ApplicationBuilderData
object obtained from the
ApplicationContext
before it is passed to the builder. This has
the advantage that the listeners are immediately active; they are then also
triggered for beans created during the builder operation.
Many tags of the dependency injection tag library have attributes which
expect classes or class names. For instance, the <di:bean>
tag has the beanClass
attribute for specifying the class of the
bean to be created; the <di:param>
tag supports setting
the parameter class; tags that allow setting a value also offer a means to
define the value class.
What was not explained so far is the fact that all these attributes come in
multiple variants. The most basic variant ends of the name "Class",
e.g. beanClass
, valueClass
, or
targetClass
. These attributes work internally by using standard
means provided by Jelly to load the corresponding Class
object. This is appropriate for many desktop applications, but may fail in
certain environments with multiple class loaders involved, e.g. in an OSGi
container.
Therefore affected tag handler classes provide an alternative way to define
classes. In addition to the attributes ending on "Class", there
are corresponding attributes with the suffixes "ClassName", and
"ClassLoader", e.g. beanClassName
and
beanClassLoader
. The "ClassName" attribute gets passed
the fully-qualified name of the desired Java class. This looks exactly the
same as for the "Class" attribute, but internally a different
mechanism is used to resolve the class name: Instead of using a default
class loader, the class loader to be used can be specified by the
"ClassLoader" attribute. This works as follows:
When calling the builder a
ClassLoaderProvider
object can be specified. If the bean builder
is used, the ClassLoaderProvider
is passed as an argument to
the build()
method. Otherwise, it is obtained from the parent
BeanContext
, which is a property of the
BuilderData
object. The ClassLoaderProvider
interface
defines a method that returns a class loader for a given symbolic name. If
the "ClassName" attribute is used, the tags check whether the
"ClassLoader" attribute is specified, too. In this case, the class
is not resolved using the default class loader, but the
ClassLoaderProvider
is asked for a class loader with the
symbolic name provided by the "ClassLoader" attribute. So it is
possible to define an arbitrary class loader for resolving a class name. The
following example shows how this mechanism is used in practice to obtain the
class of a bean to be created from a specific class loader:
<di:bean name="myBean" beanClassName="com.mypackage.MyBeanClass" beanClassLoader="specialClassLoader"> </di:bean>
In this example the class loader registered for the name
specialClassLoader is used to load the class
com.mypackage.MyBeanClass
. For this to work the class loader
with the name specialClassLoader must be known to the
ClassLoaderProvider
. The ClassLoaderProvider
interface has a registerClassLoader()
method which can be used
to register class loaders under symbolic names. A good place to register
application-specific class loaders is the initClassLoaderProvider()
method of the central
Application
class. This method is automatically called during application
startup. It is passed the default ClassLoaderProvider
which is
used by all builder operations (unless an application overrides this default).
If an application has very specific requirements for loading classes, it can
implement its own ClassLoaderProvider
. In this case it may make
sense to extend the default implementation,
DefaultClassLoaderProvider
. All class loader providers should
support a special class loader with the symbolic name CONTEXT: if
this name is passed, the context class loader for the current thread should
be returned. This reserved name can also be used in tags to select the
context class loader for resolving a class:
<di:bean name="myBean" beanClassName="com.mypackage.MyBeanClass" beanClassLoader="CONTEXT"> </di:bean>
The ClassLoaderProvider
interface also provides a means to set
a default class loader name. The class loader registered with this name is
always used if the "ClassLoader" attribute is missing. This can be
useful if most of the classes should be loaded by a specific class loader. To
enable this feature, the best way is again to override the
initClassLoaderProvider()
method of Application
.
Here the setDefaultClassLoaderName()
method of the
ClassLoaderProvider
object has to be called. The method expects
a string parameter representing the name of the class loader to be used as
default class loader. A class loader with this name must have been
registered unless it is the reserved name CONTEXT referring to the
context class loader. For some applications it may be indeed a good
strategy to set the context class loader as default class loader.
As a general recommendation, for applications that work with multiple class
loaders it is a good approach to avoid the "Class" attributes and
use the "ClassName" attributes instead. The
ClassLoaderProvider
should be configured so that the
defaultClassLoaderName
property is set to the name of the
class loader which is used most frequently. Then the "ClassLoader"
attributes can be omitted in most cases because the default class loader is
selected automatically. For classes to be resolved by a different class
loader "ClassLoader" attributes have to be specified accordingly.
Access to class path resources
Beans declared for an application sometimes need access to resources defined in the class path. As an example, think about a properties file shipped in the application's jar that should be read by one of the service beans defined in a builder script.
Under normal conditions, access to such a file located on the class path is not that complicated: A corresponding URL can be resolved using a class loader. However, as described in the previous sub section, class loader structures can become complicated in special environments. Therefore, the dependency builder library provides a specialized tag for resolving a resource on the class path. The idea is as follows:
The tag is passed the name of the resource file to be resolved and the name of a variable in the Jelly context which takes the result of this operation. Optionally, a class loader name can be specified. The tag then uses the specified class loader (or the default class loader if none was provided) to obtain a URL pointing to the desired resource file. If this succeeds, the URL is converted to a string and written in the target variable. This variable can then be referenced in a bean declaration, for instance as a property value or a constructor argument. It is also possible to store the results of multiple resolve operations in a single variable. This can be achieved by specifying a delimiter string. The tag then checks whether the target variable already has a non-empty string value. If this is the case, the delimiter value is added followed by the result of the current operation. Otherwise, the variable is created or overridden with the current result.
The tag offering the described functionality is named
<di:resource>
and implemented by the
ResourceTag
tag handler class. We will provide a little example
showing its usage. This example assumes that there is a service bean that
has to read multiple properties file from the class path. It expects a
comma-separated string with the URLs to be loaded passed to the constructor.
This class could roughly look as follows:
public class PropertiesService { /** String URLs of the files to be loaded. */ private final String[] fileURLs; public PropertiesService(String files) { fileURLs = files.split(","); } ... }
In the builder script the files to be loaded are declared in form of
multiple instances of the <di:resource>
tag and
collected to a single string variable. This variable is then referenced in
the bean declaration of the properties service bean:
<!-- Properties files to be loaded. --> <di:resource resource="standard.properties" var="propertyFiles"/> <di:resource resource="production.properties" var="propertyFiles" delimiter=","/> <di:resource resource="special.properties" var="propertyFiles" delimiter=","/> <!-- Declaration of the properties service bean --> <di:bean beanClassName="com.mypackage.PropertiesService"> <di:constructor> <di:param value="${propertyFiles}"/> </di:constructor> </di:bean>
Note how the delimiter attribute is used to specify that all
resolved resource files (except for the first one) should be concatenated
to the same target variable. In the bean declaration the variable is
referenced using the ${varName}
notation used within Jelly.
After a successful builder operation the application can access the
resulting BeanContext
and query beans. This causes beans to be
created and - if singleton beans are involved - cached by the context. Some
beans may acquire resources when they are created. If they are no longer
used, these resources should be released. For instance, a bean representing
a database connection should close this connection if it is no more needed.
The
BeanBuilder
interface defines a release()
method
which can be called with a
BeanBuilderResult
object when beans produced by the corresponding
builder operation can be freed. This ensures that all cached beans are
released.
In order to actually release resources occupied by beans, a shutdown
handler can be defined in the bean declaration. A shutdown handler is
defined using the <di:shutdown>
tag. In the body of this
tag arbitrary method invocation tags can be placed that are executed on
shutdown (i.e. when the BeanBuilderResult
is released). The
following code fragment shows an example:
<di:bean name="shutdownMeth" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"> <di:shutdown> <di:methodInvocation method="shutdown"/> </di:shutdown> </di:bean>
Actually, the body of a <di:shutdown>
tag can become
pretty complex. All tags can be placed here that can also be used to
initialize a bean. Refer to the section
Complex initializations for more
examples. This includes tags for setting properties. The next example
defines a shutdown handler which resets some properties of the bean when it
is invoked:
<di:bean name="shutdown" beanClass="net.sf.jguiraffe.di.ReflectionTestClass"> <di:setProperty property="intProp" refName="intConst"/> <di:setProperty property="stringProp" refName="strConst"/> <di:shutdown> <di:setProperty property="intProp" value="0" valueClass="java.lang.Integer"/> <di:setProperty property="stringProp"><di:null/></di:setProperty> </di:shutdown> </di:bean>
As was already stated under Complex initializations, this feature should not be overused. Invoking a single shutdown method is fine, but writing complex shutdown scripts makes it hard to understand what happens at shutdown. Note that shutdown handlers are only supported for singleton beans. This is because only singleton beans are under the control of the dependency injection framework. From non-singleton beans an arbitrary number of instances can be created, and the framework has no chance to know when these instances are no longer needed.
We have explained how the results of a builder operation can be released.
But when should the builder's release()
method be called? If
the bean builder is used directly, the developer is always responsible to
call the release()
method at the correct point of time. The
advanced UI builders in contrast offer a convenience mechanism:
If the builder script defines a window, the framework can automatically
register a specialized window event listener at this window. This listener
will call the release()
method when the window is closed. This
mechanism assumes that all beans and resources are connected to the
window object - which is typically the case in JGUIraffe
applications; a builder script typically defines a window and its controls
plus the beans needed by these graphical elements. As soon as the window
becomes unavailable, all associated resources can be released. Whether the
framework should register this special window listener or not is determined
by the isAutoRelease()
property of the
BuilderData
object passed to the builder. It is set to
true per default.
Parameter matching and data type conversions
Throughout this chapter there have been lots of examples of bean declarations using constructors, set property operations, or method calls for initializing data fields of the objects to be constructed. To recapitulate, here is a typical code fragment defining a bean by calling its constructor:
<di:bean name="constructor" beanClassName="net.sf.jguiraffe.di.ReflectionTestClass"> <di:constructor> <di:param parameterClass="java.lang.String" value="test string"/> <di:param value="42"/> </di:constructor> </di:bean>
This code snippet is pretty simple, but it raises some questions which have not yet been addressed:
int
. How
does the framework know this?These questions are closely related as we will see soon. To answer them we have to explain the algorithm used by the dependency injection framework to determine the constructor to call (the same algorithm is also applied to method invocations, so everything we say about constructors can be transferred to method invocations as well).
When a bean declaration contains a constructor invocation the framework tries to be clever to find the desired constructor. It iterates over all constructors provided by the target class and searches for a unique match. This works well if all constructors have a different number of parameters or some constructors can be sorted out based on the passed in arguments (for instance, passing a null value for a primitive parameter is not allowed). If at the end of the iteration only a single candidate is found, everything is fine.
However, if there are multiple constructors with similar parameter lists, it
might not be possible to find a unique match. For instance, if the following
additional constructor was added to the ReflectionTestClass
class:
public ReflectionTestClass(Object obj, int i) { this(i); data = obj; }
the arguments test string and 42 could be passed to both of them. Actually, in this case the framework is not able to process the bean declaration and throws an exception. There are two options to solve this problem:
<di:param>
tag provides the
parameterClass
attribute. It tells the framework that this
parameter is of the specified type. In this example, we could add a
parameterClass
attribute to the first
<di:param>
tag and set it to
java.lang.String
. This is sufficient to uniquely identify the
desired constructor because there is only one constructor with two
arguments whose first argument is of type String.valueClass
attribute. This
attribute instructs the framework to directly convert the specified value
to the given type. This also influences the search for a matching
constructor because if no unique match is found, the framework checks the
types of the arguments; from these types it may be able to exclude some of
the constructors.
If everything works fine, there is only a single matching constructor. From
the parameter list of this constructor the framework can detect the types of
the expected arguments and compare them with the types of the passed in
parameters. Because bean declarations are written in XML all parameters are
initially strings. Using the valueClass
attribute a conversion
can be explicitly requested, so that the corresponding parameter already has
the desired data type. For all other parameters the framework tries an
implicit conversion to the data type specified by the constructor parameter.
So here is the magic which automatically transforms the string 42
to an integer.
As a side note: When should I use parameterClass and when
valueClass?
In most cases, parameterClass
is the option of choice. The goal is
to give the framework a hint, which constructor to select. This is best
achieved by providing enough parameterClass
attributes so that
a single constructor can be identified. You do not have to specify the type
for all parameters, you only have to provide sufficient information that one
constructor can be uniquely identified. The valueClass
attribute is useful in cases where the actual parameter should be of a
different (but of course compatible) type than the declared parameter. An
example could be that the parameter type is an interface or an abstract base
class. Then, with the valueClass
attribute a concrete
implementation class can be specified.
As was already stated, constructors were used as an example only. The same rules apply for method invocations and property set operations. For instance, if there is a bean declaration like the following one:
<di:bean name="property" beanClassName="net.sf.jguiraffe.di.ReflectionTestClass"> <di:setProperty property="intProp" value="42"/> </di:bean>
the framework can detect the correct data type of the property and perform a
type conversion as necessary. For method invocations the situation is often
easier than for constructors because in most cases there is only a single
method with a given name. Hence, there is no problem with finding the
correct method to invoke. Only if there are overloaded methods, ambiguities
might occur which have to be resolved by specifying
parameterClass
attributes as necessary.
So in a nutshell, the following rules apply for matching constructors, methods, or properties:
parameterClass
attributes
to ensure that only a single match is found.
This algorithm is usually clever enough to find the correct constructor or
method to be invoked so that the developer does not have to care. Only if
exceptions are thrown because no unique match can be found,
parameterClass
attributes have to be added to resolve
ambiguities. In the next sub section we will see which data type conversions
are supported.
In the previous sub section it was stated that data type conversions of constructor or method arguments or properties are automatically performed as necessary. Of course, data type conversion is a complicated topic, and the framework is not able to perform arbitrary conversions. In the following we explain the basics and limitations of the data type converter subsystem.
Behind the scenes the Commons BeanUtils library is used for type conversions. BeanUtils provides some utility classes dealing with conversion issues and a number of default converters that can handle standard Java data types like primitives or date and time. These default converters are active per default and are applied to the values specified in bean declarations. Therefore beans defining only properties of basic types should work out of the box.
The dependency injection framework adds a few extensions to the default converters provided by the BeanUtils library:
public enum Mode { DEVELOPMENT, TEST, PRODUCTION, CRITICAL }
private Mode mode; public Mode getMode() { return mode; } public void setMode(Mode mode) { this.mode = mode; }
<di:bean name="enumProperty" beanClassName="net.sf.jguiraffe.di.ReflectionTestClass"> <di:setProperty property="mode" value="PRODUCTION"/> </di:bean>
Data type converters in Commons BeanUtils must implement the
org.apache.commons.beanutils.Converter
interface. This
interfaces defines only a single method:
Object convert(Class type, Object value);
convert()
is passed the desired target class and the object to
be converted. It can perform arbitrary work to convert the given object to
an instance of the target class. If this is not possible, a
ConversionException
can be thrown. In most cases the passed in
object will be a String because it stems from the XML bean declaration. So
writing a custom data type converter is no rocket science. You may also have
a look at examples in the JGUIraffe source code, for instance,
there is the
EnumConverter
class.
Before a custom type converter can be used it has to be registered first. For this purpose the dependency injection framework provides a thin layer around the Commons BeanUtils API. There are two classes that have to be dealt with:
ConversionHelper
provides functionality directly related to
type conversions. It also manages the data type converters which are
currently active. Therefore an instance of this class has to be used to
register new converters. It defines the following registration methods:
void registerConverter(Converter converter, Class<?>
targetClass)
registers a converter for the specified target
class only.void registerBaseClassConverter(Converter converter,
Class<?> targetClass)
registers a converter for the given
class and all of its subclasses. This is useful if a converter can
handle whole class hierarchies as is true for the
EnumConverter
already mentioned.
InvocationHelper
class is active during the builder operation.
It provides functionality for invoking constructors or methods through
reflection; for instance, it implements the method matching algorithm
described in the previous sub section. It also manages a
ConversionHelper
object which is used for converting
properties or parameters.
In order to add a custom type converter to a builder operation an instance
of ConversionHelper
has to be created, and the converter has to
be registered there. Then an instance of InvocationHelper
has
to be created with the ConversionHelper
object. The
BeanBuilder
interface defines an overloaded variant of the
build()
method which can be passed an
InvocationHelper
object. The builder then uses this object for
method invocations and all data type conversions. So the custom converter
that has been registered at the ConversionHelper
object
managed by the InvocationHelper
becomes active (all standard
converters are automatically registered when a new
ConversionHelper
instance is created, so they are active per
default). The following code fragment shows all steps required to register
an instance of the MyConverter
class for the class
MyData
:
// Prepare and register the converter MyConverter converter = new MyConverter(); ConversionHelper convHelper = new ConversionHelper(); convHelper.registerConverter(converter, MyData.class); // Create an InvocationHelper with the ConversionHelper InvocationHelper invHelper = new InvocationHelper(convHelper); // Call the builder BeanBuilderFactory factory = application.getBeanBuilderFactory(); try { BeanBuilder builder = factory.getBeanBuilder(); Locator beanDefs = ... // locator to the script to be processed BeanBuilderResult result = builder.build(beanDefs, null, null, invHelper); BeanContext beanContext = new DefaultBeanContext(result.getBeanStore(null)); } catch(BuilderException bex) { // exception handling }
Note that the InvocationHelper
object is passed as last argument
to the build()
method of the builder. This approach works, but
it is a bit inconvenient. The developer has to create multiple objects and
perform the registration manually. In a later chapter we will see how
custom data type converters can be declared in a builder script. They are
then registered automatically.
JGUIraffe internally makes use of its own concepts and also employs the dependency injection framework to configure some of its central services. There is a bean definition file named defaultbeans.jelly which is shipped with the framework. Each JGUIraffe application reads this file at startup.
defaultbeans.jelly defines a number of important beans that are used by JGUIraffe applications. Naming convention is that these beans start with the reserved prefix jguiraffe.. We give a short overview over the content of this file in the following. Note that some of the classes involved are discussed in later chapters, so there may be some forward references.
Bean name/group | Description |
---|---|
jguiraffe.resourceLoader | This is the resource loader, i.e. the object which actually accesses resources. Refer to the chapter Resources for more details. |
jguiraffe.resourceManager | The bean representing the resource manager. The resource manager provides access to localized texts. The bean is configured with the resource loader bean already mentioned. |
jguiraffe.messageOutput | A bean implementing the
MessageOutput interface. It is used to produce message boxes in
a way independent on a specific UI toolkit. |
Validation beans | A group of standard beans is related to the validation of input fields
in forms. They are referenced and used by form
controllers to implement functionality related to validation and to give
the user feedback about validation results.
|
jguiraffe.classLoaderProvider | Defines the class loader provider bean. Refer to the sub section Class loader issues for more details. |
jguiraffe.bindingStrategy | This bean defines the binding strategy used by forms to store their data in their model. Per default a strategy is defined which uses plain old Java beans as model objects. |
jguiraffe.guiSynchronizer | Here a bean is declared which controls access to UI elements. Most UI toolkits allow access to graphical elements only in a special thread, the event dispatch thread. The GUI synchronizer is an abstraction over this concept. It must be compatible with the UI toolkit used by the application. The implementation declared in the defaultbeans.jelly file is compatible with Swing. |
jguiraffe.commandQueue | Defines the concrete implementation of the command queue used by the application. Refer to the chapter Commands for more information. |
jguiraffe.applicationContext | The bean implementing the
ApplicationContext interface. This declaration is a bit more
complex because it contains a bunch of references to helper objects
associated with the context. |
Builder beans | The builder for generating UIs, which is discussed in the chapter
Building user interfaces and the following
ones, is completely specified using standard beans. Each time a reference
to the builder is requested, the main builder bean is obtained from the
current BeanContext (and because this is not a singleton bean,
a new instance is created). Multiple helper beans are involved:
|
It is possible to override some of the standard beans. This can be done by defining custom bean definition files and referencing them in the configuration file of the application. The sub section Hooks describes how this is done. If a custom bean definition file contains a bean with the same name as one of the standard beans, it replaces this standard bean. This also works for beans that are referenced by other standard beans. Therefore specific service classes used by an application can be customized.