Over a million developers have joined DZone.

A Look Inside JBoss Microcontainer, Part II – Advanced Dependency Injection and IoC

· Java Zone

Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code! Brought to you in partnership with ZeroTurnaround.

Today, Dependency injection (DI), or Inversion of Control (IoC), lies at the core of many frameworks that espouse the notion of a container or a component model. We discussed component models in my previous Microcontainer (MC) article. JBoss’ old JMX kernal provided lightweight DI/IoC support primarily due to the limitations of accessing mbeans through the mbean server; however with the new POJO-based component model, we have introduced several new and interesting features.

In this article, I will show how you can apply different DI concepts with the help of the JBoss Microcontainer. These concepts will be expressed via XML code (-beans.xml files), but you can also apply most of these features using annotations.

Since nothing speaks better than code when explaining DI/IoC, we'll go step by step over the code examples.

Read the other parts in DZone's exclusive JBoss Microcontainer Series:

 

Demo environment setup

To refresh the demo configuration from my previous article, let's quickly go over what we need to do to get this demo running in our IDE.

Before we begin, I'd like to first describe the various parts that constitute the demo. All the source code can be found at the following location in our Subversion repository:

    * http://anonsvn.jboss.org/repos/jbossas/projects/demos/microcontainer/branches/DZone_1_0/

The project is fully mavenized, so it should be easy to adjust it to your IDE.

I will first go over the sub-projects within the demo and describe their usage. At the end of my article series, I will provide a more detailed look at what certain sub-projects do.

Below are the JBoss Microcontainer demos and sub-projects that are relevant for this article:

    * bootstrap (as the name suggests, it bootstraps the Microcontainer with demo code)
    * jmx (adds the JMX/AOP notion to demo's bootstrap)
    * ioc (source code of our ioc examples)

 
The demo has only one variable you need to set - demos home - but even this one can be optional if you checked-out your project into the \projects\demos directory. Otherwise, you need to set the system property demos.home (e.g. -Ddemos.home=<my demos home>). You should now be able to run JMXMain as a main class. In this case jmx classpath is enough, as ioc sub-project doesn't require any additional entry on classpath. Once the Microcontainer is booted it starts to scan the ${demos.home}/sandbox directory for any changes. Now all we need to do is provide a deployable unit and drop it in there.


Advanced IoC

We won't look into simple injection examples, as this should be obvious to most readers. Configuring attributes/setters with primitive or simple values or other beans is something we see all the time. I'll try to show you some of the stuff we feel is unique (OK, not all of it is unique, some features explained are just really cool :-).

I'll go over how some of the examples relate to one another, where applicable.  Most of the examples however, are independent of each other.
 

Value factory (value-factory-beans.xml)
 
Sometimes we want some bean just to act as a value factory - meaning we want to use one of its methods to generate some value for us.

<bean name="Binding" class="org.jboss.demos.ioc.vf.PortBindingManager">
<constructor>
<parameter>
<map keyClass="java.lang.String" valueClass="java.lang.Integer">
<entry>
<key>http</key>
<value>80</value>
</entry>
<entry>
<key>ssh</key>
<value>22</value>
</entry>
</map>
</parameter>
</constructor>
</bean>
<bean name="PortsConfig" class="org.jboss.demos.ioc.vf.PortsConfig">
<property name="http"><value-factory bean="Binding" method="getPort" parameter="http"/></property>
<property name="ssh"><value-factory bean="Binding" method="getPort" parameter="ssh"/></property>
<property name="ftp">
<value-factory bean="Binding" method="getPort">
<parameter>ftp</parameter>
<parameter>21</parameter>
</value-factory>
</property>
<property name="mail">
<value-factory bean="Binding" method="getPort">
<parameter>mail</parameter>
<parameter>25</parameter>
</value-factory>
</property>
</bean>

Here we can see how the PortsConfig bean is using Binding bean to get its values via getPort metod invocation.

public class PortBindingManager
{
private Map<String, Integer> bindings;
public PortBindingManager(Map<String, Integer> bindings)
{
this.bindings = bindings;
}
public Integer getPort(String key)
{
return getPort(key, null);
}
public Integer getPort(String key, Integer defaultValue)
{
if (bindings == null)
return defaultValue;
Integer value = bindings.get(key);
if (value != null)
return value;
if (defaultValue != null)
bindings.put(key, defaultValue);
return defaultValue;
}
}

Callbacks (callback-beans.xml)

A lot of times we want to 'collect' all beans of a certain type. We might even limit the number of matching beans that makes sense for our bean.
 
<bean name="checker" class="org.jboss.demos.ioc.callback.Checker">
<constructor>
<parameter>
<value-factory bean="parser" method="parse">
<parameter>
<array elementClass="java.lang.Object">
<value>http://www.jboss.org</value>
<value>SI</value>
<value>3.14</value>
<value>42</value>
</array>
</parameter>
</value-factory>
</parameter>
</constructor>
</bean>
<bean name="editorA" class="org.jboss.demos.ioc.callback.DoubleEditor"/>
<bean name="editorB" class="org.jboss.demos.ioc.callback.LocaleEditor"/>
<bean name="parser" class="org.jboss.demos.ioc.callback.Parser">
<incallback method="addEditor" cardinality="4..n"/>
<uncallback method="removeEditor"/>
</bean>
<bean name="editorC" class="org.jboss.demos.ioc.callback.LongEditor"/>
<bean name="editorD" class="org.jboss.demos.ioc.callback.URLEditor"/>

In this case we have a Parser that collects all Editors.  We see this from the method's signature:

public class Parser
{
private Set<Editor> editors = new HashSet<Editor>();
...
public void addEditor(Editor editor)
{
editors.add(editor);
}
public void removeEditor(Editor editor)
{
editors.remove(editor);
}
}

Note: see incallback and uncallback - they match via method name.

    <incallback method="addEditor" cardinality="4..n"/>
<uncallback method="removeEditor"/>

And we also put a bottom limit on how many editors actually make this bean progress from a Configured state. 
cardinality=4..n/>
Eventually, Checker gets created and checks the parser

   public void create() throws Throwable
{
Set<String> strings = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
for (Object element : elements)
strings.add(element.toString());
if (expected.equals(strings) == false)
throw new IllegalArgumentException("Illegal expected set: " + expected + "!=" + strings);
}

Bean access mode (access-mode-beans.xml)

By default we don't inspect a bean's fields; however, if  you specify a different BeanAccessMode, the fields will be part of the bean's properties.

public enum BeanAccessMode
{
STANDARD(BeanInfoCreator.STANDARD), // Getters and Setters
FIELDS(BeanInfoCreator.FIELDS), // Getters/Setters and fields without getters and setters
ALL(BeanInfoCreator.ALL); // As above but with non public fields included

Here we set a String value to a private String field:
<bean name="FieldsBean" class="org.jboss.demos.ioc.access.FieldsBean" access-mode="ALL">
<property name="string">InternalString</property>
</bean>

public class FieldsBean
{
private String string;
public void start()
{
if (string == null)
throw new IllegalArgumentException("Strings should be set!");
}
}

Bean alias (aliases-beans.xml)

Each bean can have any number of aliases. Since we treat Microcontainer component names as Objects, we don't limit the alias type. By default we don't do a system property replacement - you need to set the replace flag explicitly.

  <bean name="SimpleName" class="java.lang.Object">
<alias>SimpleAlias</alias>
<alias replace="true">${some.system.property}</alias>
<alias class="java.lang.Integer">12345</alias>
<alias><javabean xmlns="urn:jboss:javabean:2.0" class="org.jboss.demos.bootstrap.Main"/></alias>
</bean>
 
XML (or MetaData) annotations support (annotations-beans.xml)


AOP support is a first class citizen in JBoss Microcontainer, hence we can mix and match AOP aspects and plain beans. In this example we'll try to intercept a method invocation based on an annotation. The cool part is that we don't care where this annotation comes from. It could be a true class annotation or an annotation added through the xml configuration.
 
<interceptor xmlns="urn:jboss:aop-beans:1.0" name="StopWatchInterceptor" class="org.jboss.demos.ioc.annotations.StopWatchInterceptor"/>

<bind xmlns="urn:jboss:aop-beans:1.0" pointcut="execution(* @org.jboss.demos.ioc.annotations.StopWatchLog->*(..)) OR execution(* *->@org.jboss.demos.ioc.annotations.StopWatchLog(..))">
<interceptor-ref name="StopWatchInterceptor"/>
</bind>
</interceptor>
 
public class StopWatchInterceptor implements Interceptor
{
...
public Object invoke(Invocation invocation) throws Throwable
{
Object target = invocation.getTargetObject();
long time = System.currentTimeMillis();
log.info("Invocation [" + target + "] start: " + time);
try
{
return invocation.invokeNext();
}
finally
{
log.info("Invocation [" + target + "] time: " + (System.currentTimeMillis() - time));
}
}
}

And true class annotated executor:

<bean name="AnnotatedExecutor" class="org.jboss.demos.ioc.annotations.AnnotatedExecutor">
 
public class AnnotatedExecutor implements Executor
{
...
@StopWatchLog // <-- Pointcut match!
public void execute() throws Exception
{
delegate.execute();
}
}

Simple executor with xml annotation:
  <bean name="SimpleExecutor" class="org.jboss.demos.ioc.annotations.SimpleExecutor">
<annotation>@org.jboss.demos.ioc.annotations.StopWatchLog</annotation> // <-- Pointcut match!
</bean>

public class SimpleExecutor implements Executor
{
private static Random random = new Random();
public void execute() throws Exception
{
Thread.sleep(Math.abs(random.nextLong() % 101));
}
}

We add some executor invoker bean, so that we see those executors in action during deployment:


JBoss-MC-Demo  INFO [15-12-2008 13:57:39] StopWatch - Invocation [org.jboss.demos.ioc.annotations.AnnotatedExecutor@4d28c7] start: 1229345859234
JBoss-MC-Demo  INFO [15-12-2008 13:57:39] StopWatch - Invocation [org.jboss.demos.ioc.annotations.AnnotatedExecutor@4d28c7] time: 31
JBoss-MC-Demo  INFO [15-12-2008 13:57:39] StopWatch - Invocation [org.jboss.demos.ioc.annotations.SimpleExecutor@1b044df] start: 1229345859265
JBoss-MC-Demo  INFO [15-12-2008 13:57:39] StopWatch - Invocation [org.jboss.demos.ioc.annotations.SimpleExecutor@1b044df] time: 47

 

Autowire (autowire-beans.xml)

Autowiring, or contextual injection, is a common feature with IoC frameworks.
The following shows you how to use or exclude beans with autowiring.

  <bean name="Square" class="org.jboss.demos.ioc.autowire.Square" autowire-candidate="false"/>
<bean name="Circle" class="org.jboss.demos.ioc.autowire.Circle"/>
<bean name="ShapeUser" class="org.jboss.demos.ioc.autowire.ShapeUser">
<constructor>
<parameter><inject/></parameter>
</constructor>
</bean>
<bean name="ShapeHolder" class="org.jboss.demos.ioc.autowire.ShapeHolder">
<incallback method="addShape"/>
<uncallback method="removeShape"/>
</bean>
<bean name="ShapeChecker" class="org.jboss.demos.ioc.autowire.ShapesChecker"/>

In both cases - ShapeUser and ShapeChecker - it should be only Circle that is used, as Square is excluded in the contextual binding.


Bean factory (bean-factory-beans.xml)

When we want more than one instance of some bean, we need to use the bean factory pattern. The job of the Microcontainer is to configure and install the bean factory as if it were a plain bean. Our job is then to invoke the bean factory's createBean method.

By default, the Microcontainer creates a GenericBeanFactory instance, but you can configure your own factory. The only limitation is that its signature / configuration hooks are similar to the one of AbstractBeanFactory.

  <bean name="Object" class="java.lang.Object"/>
<beanfactory name="DefaultPrototype" class="org.jboss.demos.ioc.factory.Prototype">
<property name="value"><inject bean="Object"/></property>
</beanfactory>
<beanfactory name="EnhancedPrototype" class="org.jboss.demos.ioc.factory.Prototype" factoryClass="org.jboss.demos.ioc.factory.EnhancedBeanFactory">
<property name="value"><inject bean="Object"/></property>
</beanfactory>
<beanfactory name="ProxiedPrototype" class="org.jboss.demos.ioc.factory.UnmodifiablePrototype" factoryClass="org.jboss.demos.ioc.factory.EnhancedBeanFactory">
<property name="value"><inject bean="Object"/></property>
</beanfactory>
<bean name="PrototypeCreator" class="org.jboss.demos.ioc.factory.PrototypeCreator">
<property name="default"><inject bean="DefaultPrototype"/></property>
<property name="enhanced"><inject bean="EnhancedPrototype"/></property>
<property name="proxied"><inject bean="ProxiedPrototype"/></property>
</bean>

Here you can see an example of such extended BeanFactory usage:

public class EnhancedBeanFactory extends GenericBeanFactory
{
public EnhancedBeanFactory(KernelConfigurator configurator)
{
super(configurator);
}
public Object createBean() throws Throwable
{
Object bean = super.createBean();
Class<?> clazz = bean.getClass();
if (clazz.isAnnotationPresent(SetterProxy.class))
{
Set<Class> interfaces = new HashSet<Class>();
addInterfaces(clazz, interfaces);
return Proxy.newProxyInstance(
clazz.getClassLoader(),
interfaces.toArray(new Class<?>[interfaces.size()]),
new SetterInterceptor(bean)
);
}
else
{
return bean;
}
}
protected static void addInterfaces(Class<?> clazz, Set<Class> interfaces)
{
if (clazz == null)
return;
interfaces.addAll(Arrays.asList(clazz.getInterfaces()));
addInterfaces(clazz.getSuperclass(), interfaces);
}
private class SetterInterceptor implements InvocationHandler
{
private Object target;
private SetterInterceptor(Object target)
{
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
String methodName = method.getName();
if (methodName.startsWith("set"))
throw new IllegalArgumentException("Cannot invoke setters.");
return method.invoke(target, args);
}
}
}

public class PrototypeCreator
{
...
public void create() throws Throwable
{
ValueInvoker vi1 = (ValueInvoker)bfDefault.createBean();
vi1.setValue("default");
ValueInvoker vi2 = (ValueInvoker)enhanced.createBean();
vi2.setValue("enhanced");
ValueInvoker vi3 = (ValueInvoker)proxied.createBean();
try
{
vi3.setValue("default");
throw new Error("Should not be here.");
}
catch (Exception ignored)
{
}
}

 
Bean Meta Data Builder (builder-util-beans.xml)

When using the Microcontainer in your code, we suggest using BeanMetaDataBuilder to configure/create your bean metadata.
 
<bean name="BuilderUtil" class="org.jboss.demos.ioc.builder.BuilderUtil"/>
<bean name="BuilderExampleHolder" class="org.jboss.demos.ioc.builder.BuilderExampleHolder">
<constructor>
<parameter><inject bean="BUExample"/></parameter>
</constructor>
</bean>

Using this concept you don't expose your code to any Microcontainer implementation details.

public class BuilderUtil
{
private KernelController controller;
@Constructor
public BuilderUtil(@Inject(bean = KernelConstants.KERNEL_CONTROLLER_NAME) KernelController controller)
{
this.controller = controller;
}
public void create() throws Throwable
{
BeanMetaDataBuilder builder = BeanMetaDataBuilder.createBuilder("BUExample", BuilderExample.class.getName());
builder.addStartParameter(Kernel.class.getName(), builder.createInject(KernelConstants.KERNEL_NAME));
controller.install(builder.getBeanMetaData());
}
public void destroy()
{
controller.uninstall("BUExample");
}
}


 
Custom ClassLoader (classloader-beans.xml)

In the Microcontainer you can even define a custom ClassLoader per bean.

When defining a classloader as per the whole deployment, make sure you don't create a cyclic dependency -- for instance, a newly defined classloader that depends on itself.

  <classloader><inject bean="custom-classloader:0.0.0"/></classloader>
<!-- this will be explained in future article -->
<classloader name="custom-classloader" xmlns="urn:jboss:classloader:1.0" export-all="NON_EMPTY" import-all="true"/>
<bean name="CustomCL" class="org.jboss.demos.ioc.classloader.CustomClassLoader">
<constructor>
<parameter><inject bean="custom-classloader:0.0.0"/></parameter>
</constructor>
<property name="pattern">org\.jboss\.demos\.ioc\..+</property>
</bean>
<bean name="CB1" class="org.jboss.demos.ioc.classloader.CustomBean"/>
<bean name="CB2" class="org.jboss.demos.ioc.classloader.CustomBean">
<classloader><inject bean="CustomCL"/></classloader>
</bean>


Here you can see that CB2 bean uses a custom ClassLoader, which limits the loadable package scope:

public class CustomClassLoader extends ClassLoader
{
private Pattern pattern;
public CustomClassLoader(ClassLoader parent)
{
super(parent);
}
public Class<?> loadClass(String name) throws ClassNotFoundException
{
if (pattern == null || pattern.matcher(name).matches())
return super.loadClass(name);
else
throw new ClassNotFoundException("Name '" + name + "' doesn't match pattern: " + pattern);
}
public void setPattern(String regexp)
{
pattern = Pattern.compile(regexp);
}
}

 
Controller Mode (controller-mode-beans.xml)

By default the Microcontainer uses the AUTO controller mode - meaning it pushes beans as far as they go with respect to dependencies. But there are two other modes - MANUAL and ON_DEMAND which do exactly what their names suggest.

If the bean is marked as ON_DEMAND, it won't be used / installed until some other bean explicitly depends on it. In the Manual mode, it's up to the Microcontainer user to push the bean forward and backwards along the state ladder.

  <bean name="OptionalService" class="org.jboss.demos.ioc.mode.OptionalService" mode="On Demand"/>
<bean name="OptionalServiceUser" class="org.jboss.demos.ioc.mode.OptionalServiceUser"/>
<bean name="ManualService" class="org.jboss.demos.ioc.mode.ManualService" mode="Manual"/>
<bean name="ManualServiceUser" class="org.jboss.demos.ioc.mode.ManualServiceUser">
<start>
<parameter><inject bean="ManualService" fromContext="context" state="Not Installed"/></parameter>
</start>
</bean>

Note: see inject's fromContext attribute. Here, we can inject not only beans, but their unmodifiable Microcontainer component representation as well.

Check the code of OptionalServiceUser and ManualServiceUser on how to use the Microcontainer API for ON_DEMAND and MANUAL bean handling.

 
Cycle (cycle-beans.xml)

Sometimes beans depend on each other in a cycle. For instance A depends on B at construction, where B depends on A at setter. Due to the Microcontainer’s fine-grained state lifecycle separation, we can solve such problems fairly easily. 

  <bean name="cycleA" class="org.jboss.demos.ioc.cycle.CyclePojo">
<property name="dependency"><inject bean="cycleB"/></property>
</bean>
<bean name="cycleB" class="org.jboss.demos.ioc.cycle.CyclePojo">
<constructor><parameter><inject bean="cycleA" state="Instantiated"/></parameter></constructor>
</bean>
<bean name="cycleC" class="org.jboss.demos.ioc.cycle.CyclePojo">
<property name="dependency"><inject bean="cycleD"/></property>
</bean>
<bean name="cycleD" class="org.jboss.demos.ioc.cycle.CyclePojo">
<property name="dependency"><inject bean="cycleC" state="Instantiated"/></property>
</bean>
 

Demand / Supply (demand-supply-beans.xml)

Sometimes,  a dependency is not explicitly known, such as an injection; however, there may still be a dependency between two beans, such as static code usage. This should be expressed in a clear way:
  <bean name="TMDemand" class="org.jboss.demos.ioc.demandsupply.TMDemander">
<demand>TM</demand>
</bean>
<bean name="SimpleTMSupply" class="org.jboss.demos.ioc.demandsupply.SimpleTMSupplyer">
<supply>TM</supply>
</bean>

 
 Installs (install-beans.xml)

As our bean moves through states we might want to invoke some methods (actions) on other beans or the same bean.

Here we can see how Entry invokes RepositoryManager's add/removeEntry method to register/unregister itself.

  <bean name="RepositoryManager" class="org.jboss.demos.ioc.install.RepositoryManager">
<install method="addEntry">
<parameter><inject fromContext="name"/></parameter>
<parameter><this/></parameter>
</install>
<uninstall method="removeEntry">
<parameter><inject fromContext="name"/></parameter>
</uninstall>
</bean>
<bean name="Entry" class="org.jboss.demos.ioc.install.SimpleEntry">
<install bean="RepositoryManager" method="addEntry" state="Instantiated">
<parameter><inject fromContext="name"/></parameter>
<parameter><this/></parameter>
</install>
<uninstall bean="RepositoryManager" method="removeEntry" state="Configured">
<parameter><inject fromContext="name"/></parameter>
</uninstall>
</bean>


 
Lazy (lazy-beans.xml)

We might have a dependecy on a bean that is rarely used but it takes a long time to configure. We can use a lazy mock of the bean, so that the dependency gets resolved, but when we actually need the bean, we will invoke / use the target bean - hoping it has been installed by then.
  <bean name="lazyA" class="org.jboss.demos.ioc.lazy.LazyImpl">
<constructor>
<parameter>
<lazy bean="lazyB">
<interface>org.jboss.demos.ioc.lazy.ILazyPojo</interface>
</lazy>
</parameter>
</constructor>
</bean>
<bean name="lazyB" class="org.jboss.demos.ioc.lazy.LazyImpl">
<constructor>
<parameter>
<lazy bean="lazyA">
<interface>org.jboss.demos.ioc.lazy.ILazyPojo</interface>
</lazy>
</parameter>
</constructor>
</bean>
<lazy name="anotherLazy" bean="Pojo" exposeClass="true"/>
<bean name="Pojo" class="org.jboss.demos.ioc.lazy.Pojo"/>

Lifecycle (lifecycle-beans.xml)


By default the Microcontainer uses create/start/stop/destroy methods when it moves through the various states; however, we may not want the Microcontainer to invoke them. Hence we can set an ignore flag.

  <bean name="FullLifecycleBean-3" class="org.jboss.demos.ioc.lifecycle.FullLifecycleBean"/>
<bean name="FullLifecycleBean-2" class="org.jboss.demos.ioc.lifecycle.FullLifecycleBean">
<create ignored="true"/>
</bean>
<bean name="FullLifecycleBean-1" class="org.jboss.demos.ioc.lifecycle.FullLifecycleBean">
<start ignored="true"/>
</bean>


Summary

In this article, I showed you how to apply different DI concepts with the help of the JBoss Microcontainer. The Microcontainer provides a comprehensive range of DI/IoC capabilities.

Joining that with a modular design, a clean split into metadata driven deployment and an extensible state machine, you can nicely integrate any additional features you feel are missing. Nevertheless, the Microcontainer team still has some ideas on addition features it would like to add and is always open to your suggestions.

In my next article I'll present the Microcontainer’s Virtual File System (VFS) project. This will provide a nice prelude for subsequent articles on Microcontainer ClassLoading and the Deployment framework.

 

About the Author

Ales Justin was born in Ljubljana, Slovenia and graduated with a degree in mathematics from the University of Ljubljana. He fell in love with Java seven years ago and has spent most of his time developing information systems, ranging from customer service to energy management. He joined JBoss in 2006 to work full time on the Microcontainer project, currently serving as its lead. He also contributes to JBoss AS and is Seam and Spring integration specialist. He represent JBoss on 'JSR-291 Dynamic Component Support for Java SE' and 'OSGi' expert groups.

 

 

The Java Zone is brought to you in partnership with ZeroTurnaround. Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code!

Topics:

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}