The JBoss Microcontainer provides an environment to configure and manage POJOs (plain old java objects). It is designed to reproduce the existing JBoss JMX Microkernel but targetted at POJO environments. As such it can be used standalone outside the JBoss Application Server. This initial release provides a set of features to support POJO deployment in JBoss and the bootstrap of services used by the standalone EJB3 distribution. This chapter takes you through several example deployments into JBoss and explaining how to configure POJOs and link them together through injection.
This section describes the packaging. As we will see later in the standalone chapter, this is more of a convention than a requirement. The convention is recommended since it allows "deployments" to be used both standalone and inside JBoss AS.
The basic structure of microcontainer deployment is a .beans archive that contains a META-INF/jboss-beans.xml deployment descriptor that describes the services to be be deployed. The contents of this XML file are described in Section 14.3, “Basic Configuration”. Additionaly, the .beans archive contains classes and resources just like any other JAR file.
example.beans/ example.beans/META-INF/jboss-beans.xml example.beans/com/acme/SomeClass.class
If you want to include a .beans file in an EAR, you will need to reference it as a service module from META-INF/jboss-app.xml.
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE jboss-app PUBLIC "-//JBoss//DTD J2EE Application 1.4//EN" "http://www.jboss.org/j2ee/dtd/jboss-app_4_0.dtd" > <jboss-app> <module><service>example.beans</service></module> </jboss-app>
The microcontainer's main purpose is to allow external configuration of POJOs. In this section, we will look at the some of the common configurations that can be performed.
The deployment acts as a container for many beans that are deployed together.
<?xml version="1.0" encoding="UTF-8"?> <!-- Deployment holds beans --> <deployment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:bean-deployer bean-deployer_1_0.xsd" xmlns="urn:jboss:bean-deployer"> <!-- bean part of the deployment --> <bean .../> <!-- bean part of the deployment --> <bean .../> </deployment>
The bean is the main deployment component. It describes a single object in the runtime.
<bean name="Name1" class="com.acme.Example"/>
The example above uses the default constructor of com.acme.Example to create a new POJO.
new com.acme.Example();
It is given the name Name1 so that it can be referenced elsewhere.
The previous example uses the default constructor. What if you don't want to use some other constructor? The simplest mechanism just matches the number of parameters in the constructor.
public class Example{ public Example(String string, int integer) {} } <bean name="Name1" class=com.acme.Example"> <constructor> <parameter>example string</parameter> <parameter>4</parameter> </constructor> </bean> new com.acme.Example(new String("example string"), 4);
Sometimes this does not always work, because there are two constructors with the same number of parameters. In this case, you must specify the types to resolve the ambiguity.
public class Example{ public Example(String string, int integer) {} public Example(String string, long long) {} } <bean name="Name1" class=com.acme.Example"> <constructor> <parameter>example string</parameter> <parameter class="long">4</parameter> </constructor> </bean> new com.acme.Example(new String("example string"), (long) 4);
Note that you only have to be explicit enough to resolve the ambiguity.
Not all classes have pubic constructors. It is often good practice to use factories when you to have a choice of implementation for an interface. The microcontainer includes support for the three different types of factories.
Static factory with static method:
public class ExampleFactory{ public static Example createExample() {}; } public class Example{ } <bean name="Name1" class=com.acme.Example"> <constructor factoryClass="com.acme.ExampleFactory" factoryMethod="createExample"/> </bean> Example Name1 = ExampleFactory.createExample();
Factory instance with static method:
public class ExampleFactory{ public static Example createExample() {}; } public class Example{ } <bean name="Factory1" class=com.acme.ExampleFactory"/> <bean name="Name1" class=com.acme.Example"> <constructor factoryMethod="createExample"> <factory bean="Factory1"/> </constructor> </bean> ExampleFactory Factory1 = new com.acme.ExampleFactory(); Example Name1 = Factory1.createExample();
Factory instance with instance method (also shows parameters):
public class ExampleFactory{ public Example createExample(String string) {}; } public class Example{ } <bean name="Factory1" class=com.acme.ExampleFactory"/> <bean name="Name1" class=com.acme.Example"> <constructor factoryMethod="createExample"> <factory bean="Factory1"/> <parameter>example string</parameter> </constructor> </bean> ExampleFactory Factory1 = new com.acme.ExampleFactory(); Example Name1 = Factory1.createExample(new String("example string"));
It is possible to create all objects using factories and constructors, however many people use the JavaBeans or MBean convention where an object is constructed using a default constructor and then configured using properties or attributes.
public class Example{ public String getTitle() {} public void setTitle(String string) {} } <bean name="Name1" class=com.acme.Example"> <property name="title">example string</property> </bean> Example example = new com.acme.Example(); example.setTitle(new String("example string"));
So far we have looked at two configurations that require values to be configured. The microcontainer has a number of mechanisms to construct static values.
Null: The null value is trivial, <null/>. But, it needs to differentiated from the string "null".
public class Example{ public String getTitle() {} public void setTitle(String string) {} } <!-- Wrong --> <bean name="Name1" class=com.acme.Example"> <property name="title">null</property> </bean> Example example = new com.acme.Example(); example.setTitle(new String("null")); <!-- Correct --> <bean name="Name1" class=com.acme.Example"> <property name="title"><null/></property> </bean> Example example = new com.acme.Example(); example.setTitle(null);
PropertyEditor: A JavaBeans property editor can be used to convert a string to specific type. JBoss already provides a large number of property editors for standard types.
import java.beans.PropertyEditorSupport; public class URLEditor extends PropertyEditorSupport{ public void setAsText(final String text){ setValue(new URL(text)); } } public class Example{ public URL getLink() {} public void setLink(URL url) {} } <bean name="Name1" class=com.acme.Example"> <property name="link">http://acme.com/index.html</property> </bean> Example example = new com.acme.Example(); PropertyEditor editor = PropertyEditorManager.findEditor(URL.class); editor.setAsText("http://acme.com/index.html"); example.setLink(editor.getValue());
Override the type: Often the property takes an interface or abstract class, but you want to pass a specific implementation or a subclass.
public class Example{ public Number getNumber() {} public void setNumber(Number number) {} } <bean name="Name1" class=com.acme.Example"> <property name="number" class="java.lang.Long">4</property> </bean> Example example = new com.acme.Example(); example.setNumber(new Long(4));
There is also a more long-winded form of value that we will see later when configuring collections.
public class Example{ public Number getNumber() {} public void setNumber(Number number) {} } <bean name="Name1" class=com.acme.Example"> <property name="number"><value class="java.lang.Long">4</value></property> </bean> Example example = new com.acme.Example(); example.setNumber(new Long(4));
Objects by themself are not very useful. They need to be linked together to form more complicated data structures. We have already seen one form of an injection when using factory instances above. Injections can be used anywhere a string value is used. All the examples that we have previously seen with strings could also be done with injections.
public class Example{ public Number getNumber() {} public void setNumber(Number number) {} } <bean name="Four" class="java.lang.Long"> <constructor><parameter>4</parameter></constructor> </bean> <bean name="Name1" class=com.acme.Example"> <property name="number"><inject bean="Four"/></property> </bean> Long four = new Long(4); Example example = new com.acme.Example(); example.setNumber(four);
The order of the <bean/> elements does not matter. The microcontainer orders the beans into the correct order. Which leaves the problem of how to resolve circular dependencies. These can be resolved by specifying when you want the injection to occur. In the example below we say once Name2 is Instantiated (constructed) it is ok to configure it on Name1. Normally, injections wait for the referenced bean to reach the state Installed.
public class Example{ public Example getExample() {} public void setOther(Example other) {} } <bean name="Name1" class=com.acme.Example"> <property name="number"><inject bean="Name2" state="Instantiated"/></property> </bean> <bean name="Name2" class=com.acme.Example"> <property name="number"><inject bean="Name1"/></property> </bean> Example Name1 = new Example(); Example Name2 = new Example(); Name1.setOther(Name2); // We said we don't wait for a fully configured Name2 Name2.setOther(Name1); // Complete the confguration of Name2
Sometimes, we don't want to inject the bean itself. Instead, we want to inject some property of the bean. This is another form of factory, except the referenced bean is not really constructing the object.
public class Example{ public String getHost() {} public void setHost(String host) {} } <bean name="URL" class="java.net.URL"> <constructor><parameter>http://acme.com/index.html</parameter></constructor> </bean> <bean name="Name1" class=com.acme.Example"> <property name="host"><inject bean="URL" property="host"/></property> </bean> URL url = new URL("http://acme.com/index.html"); Example example = new com.acme.Example(); example.setHost(url.getHost());
The <collection/>, <list/>, <set/> and <array/> all take the same form. Only <list/> is shown here.
The elementClass is required, unless you specify explicit types on all the <value/>s.
The default types are:
collection: java.util.ArrayList
list: java.util.ArrayList
set: java.util.HashSet
array: java.lang.Object[]
public class Example{ public List getList() {} public void setList(List list) {} } <bean name="Name1" class=com.acme.Example"> <property name="list"> <list class="java.util.LinkedList" elementClass="java.lang.String"> <value>A string</value> <!-- uses elementClass --> <value class="java.lang.URL">http://acme.com/index.html</value> <!-- a URL --> <value><inject bean="SomeBean"/></value> <!-- inject some other bean --> <value> <!-- a list inside a list --> <list elementClass="java.lang.String"> <value>Another string</value> </list> </value> </list> </property> </bean> Example example = new com.acme.Example(); List list = new LinkedList(); list.add(new String("A string")); list.add(new URL("http://acme.com/index.html")); list.add(someBean); List subList = new ArrayList(); subList.add(new String("Another string")); list.add(subList); element.setList(list);
The other type of collection is a map which also covers properties and hashtables. The default is java.util.HashMap. For maps there are two default types for the elements, the keyClass and valueClass.
The <entry/> differentiates each <key/>, <value/> pair.
public class Example{ public Map getMap() {} public void setMap(Map map) {} } <bean name="Name1" class=com.acme.Example"> <property name="map"> <map class="java.util.Hashtable" keyClass="java.lang.String" valueClass="java.lang.String"> <entry><key>one</key><value>1</value></entry> <!-- uses keyClass and valueClass --> <entry><key class="java.lang.Integer">4</key><value>Four</value></entry> <!-- uses valueClass --> <entry><key>ten</key><value class="java.lang.Long">10</value></entry> <!-- uses keyClass --> <entry><key>bean</key><value><inject bean="SomeBean"/></value></entry> </map> </property> </bean> Example example = new com.acme.Example(); Map map = new Hashtable(); map.put(new String("one"), new String("1")); map.put(new Integer(4), new String("4")); map.put(new String("ten"), new Long(10)); map.put(new String("bean"), someBean); element.setMap(map);
Anybody familiar with the JBoss JMX microkernel will know about the lifecycle. The microcontainer extends the lifecycle to all states of the POJO's:
Not Installed: The POJO has not been described or has been uninstalled.
Described: The POJO's bean description has been examined and dependencies determined.
Instantiated: All the dependencies have been resolved to construct the bean, these include, the class exists, the constructor parameter injections can be resolved, any factory can be resolved.
Configured: All the property injections can be resolved, this includes all the dependencies in any collections.
Create: All the dependent beans have been created, this includes any injections passed to the create method.
Start: All the dependent beans have been started, this includes any injections passed to the start method.
Installed: The lifecycle is complete.
The <depends/> element allows beans to perform two phase startup processing like the JMX microkernel.
<bean name="Name1" .../> <bean name="Name2" ...> <depends>Name1</depends> </bean> // Deploy name1.create(); name2.create(); name1.start(); name2.start(); // Undeploy name2.stop(); name1.stop(); name2.destroy(); name1.destroy();
The create, start, stop and destroy methods can be overridden with parameters passed to them.
public class Example{ public void initialize(Object someObject) {} } <bean name="Name1" class="com.acme.Example"> <create method="initialize"> <parameter><inject bean="SomeBean"/></parameter> </create> </bean>
The microcontainer is going a powerful replacement for the JBoss JMX microcontainer. It brings the loosely coupled configuration environment of JBoss to POJO environments, adding more control and extra features. Both inside and outside the JBoss Application Server.
Going forward these additional features will allow even more advances, including aspectized deployment, on demand services, versioned deployments, etc.
For more information on the microcontainer, including standalone usage outside of JBoss AS, see Microcontainer project page.