Andrew has posted 2 posts at DZone. View Full User Profile

Inject4Spring: IoC principle for Spring metadata

06.13.2008
| 4143 views |
  • submit to reddit

Inject4Spring is a small library that extends Spring framework by adding inversion of control capabilities on Spring context configuration level.

That problem which should be solved

Some time ago I've blogged about necessity and possibilities of specifying dependencies in Spring context using "opposite" direction of references.

Spring is great in providing Inversion of Control (IoC) capabilities for Java code. It hides details of assembling application from code and moves it to another level - to conviguration of container that performs actual beans wiring and injection.

However, while it frees developer from details of assembling application, it actually does not use IoC principle as soon as we start working on Spring configuration level. While creating metadata (XML configuration for beans), one should always know that bean has references to another beans, should know names of that beans etc. Here we have solid structure of application and sometimes it could be just an issue.

In just two words, if we have, say, two beans defined within context, and first bean refers to other bean, that reference should be described within bean context configuration XML directly as part of referring bean definition. This is casual and "natural" Spring way of defining dependencies between beans.

However, for some types of applications that approach does not work. The major drawback of it is as follows: during definition of referring bean it's assumed that the name of bean to which it refers is known.

Unfortunately, for applications that are built using plugin architecture, that assumption becomes serious limitation, since it does not allow to create really extensible application without necessity of Spring context modification - of course, that does not corresponds to overall idea of plugins at all. And that's even more sad if we'll consider functionality that is already included into Spring - like creation of application context from several files that could be resolved dynamically (say, via direct list of their locations or via wildcards withing classpath).

Therefore, to obtain truly extensible applications, we need to have ability to "extend" existing content of Spring context.

From that point of view, the application may include, for example, one or more Spring configuration files with beans that represents "extension points" and, several dynamically loaded modules of plugins which may include beans that may be plugged into that extension points.

The following picture illustrates this concept:

 

What should be considered as extension point there? Well, the answer is pretty simple there - just properties of some beans. We have named bean, we have named property - so we could address the point where we could inject our reference pretty precise.

Of course, in extending context we need to know names of beans and properties to which we may inject beans from extending contexts. However, this issues is completely different to original approach of Spring - in such case, beans in "core" context represents a kind of dynamic API (pretty funny, but I suppose that such and context definition may be considered as API without API), and at the moment of defining beans in "core" context we are completely not aware how ones will be customized later (or even probably by third-party plugin developer).

Ok, now that we have extension points, the only thing we need to make the entire concept of such injection live is just an ability to specify that beans should be wired outside of referencing bean definition.

In other words, here we need to have some mechanism which will said Spring that beans should be wired not in "core" context, but directly in "extending" one.

The following picture illustrates difference between "normal" and "opposite" directions of references:

Now we are almost ready to move further. The only thing that we need to consider at the moment is types of references. Spring provides several standard ways to specify references between beans:

  • using ordinary reference;
  • using list of beans;
  • using map of beans;
  • using set of beans.

And, of course, there is ability to specify value of particular property. If we'll look on these ways of defining relations between beans it will be clear that it's quite possible to use opposite direction of injection to support them.

Inject4Spring overview

Well, that was background for tasks which are solved by Inject4Spring library. It's a small (about 35k in jar) library which I wrote about year ago to have support of such "opposite" directions of specifying references between beans. At the moment, we've used it in several projects developed in SoftAMIS. Inject4Spring is released under Apache License, so it could be used both in open source and commercial applications.

In general, while it could be used for Spring 1.x, primarily it's targeted to Spring 2.x, since it heavy relies on custom namespaces functionality introduced in Spring 2.0. Actually, from the usage point of view, all functionality of that library is exposed via set of custom XML tags that belongs to "inject" namespace.

Here is brief overview of possible types of dependencies in Spring and custom tags included into Inject4Spring that corresponds them:

In general, the main purpose of Inject4Spring is to provide IoC capabilities for beans configuration in Spring context (while on code level this task is solved by Spring).

How to use Inject4Spring

Ok, there were too much words above. So it seems that now it's right time for some examples. Actually, these examples are from context that was used for unit tests for Inject4Spring, so the are slightly artificial. Anyway, they should reflect usage pretty clear

First of all (assuming that inject4spring.jar is included into classpath), it's necessary to add scheme provided by Inject4Spring into context. The following snippet of XML does that:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:inject="http://www.soft-amis.org/schema/inject4spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.soft-amis.org/schema/inject4spring
http://www.soft-amis.org/schema/inject4spring.xsd">

 

Here we've simply added definition for inject namespace and defined XSD scheme for it.

Ok, now lets assume that we have two pretty basic beans:

  1. one that will be considered as described in "core" context (and which provides several extension points via it's properties) - lets that bean have class InjectionTargetBean;
  2. and another one that will be injected into that bean via Inject4Spring- we'll call class of it as InjectedBean;

While it's not too important which methods and properties are declared in InjectedBean class, the structure of InjectionTargetBean seems to be more important for our example. Therefore, we'll show it structure below (please note that there are only fields shown - appropriate getters and setters are removed to save space):

public class InjectionTargetBean {
protected List<String> fValuesList = null;
protected List<InjectedBean> fBeansList = null;
protected List<InjectedBean> fBeansListToCreate = null;
protected Set<InjectedBean> fBeansSet = null;
protected Set<String> fValuesSet = null;

protected String fValue = null;
protected InjectedBean fBeanRefEmpty = null;
protected InjectedBean fBeanRefOverride = null;

protected Map<String, String> fValuesMap = null;
protected Map<String, InjectedBean> fBeansMap = null;

// constructor, setters and getters are omitted....
}

Ok, now we're almost ready. Let's assume that we have bean named InjectTarget that belongs to InjectionTargetBean class.

Here is how we could inject bean from extending context into that bean for various scenarios:

  1. Injection of bean into reference (short form)
    <bean name="InjectionBean"
    class="org.softamis.inject4spring.InjectedBean">
    <inject:to-ref target="InjectTarget"
    name="beanRefEmpty"/>
    </bean>
  2. Injection of bean into reference (verbose form)
    <inject:bean-to-ref target="InjectTarget"
    name="beanRefEmpty"
    ref="InjectionBean"/>
  3. Injection of bean into list (short form)
    <bean name="InjectionBean"
    class="org.softamis.inject4spring.InjectedBean">
    <inject:to-list target="InjectTarget" name="beansList"/>
    </bean>
  4. Injection of bean into list (verbose form)
    <inject:ref-to-list target="InjectTarget" 
    name="beansList"
    ref="InjectionBean"/>
  5. Injection of bean into set (short form)
    <bean name="InjectionBean"
    class="org.softamis.inject4spring.InjectedBean">
    <inject:to-set target="InjectTarget" name="beansSet"/>
    </bean>
  6. Injection of bean into set (verbose form)
    <inject:ref-to-set target="InjectTarget" 
    name="beansSet"
    ref="InjectionBean"/>
  7. Injection of bean into map (short form)
    <bean name="InjectionBean"
    class="org.softamis.inject4spring.InjectedBean">
    <inject:to-map target="InjectTarget"
    name="beansMap" key="injected"/>
    </bean>
  8. Injection of bean into map (verbose form)
    <inject:ref-to-map target="InjectTarget" 
    name="beansMap"
    ref="InjectionBean"
    key="injected"/>
  9. And here are several ways how to specify/override value of simple property for target bean
        <inject:value-to-property target="InjectTarget" 
    name="value"
    value="OverridenValue"/>

    <inject:value-to-list target="InjectTarget"
    name="valuesList"
    value="InjectedValue"/>

    <inject:value-to-set target="InjectTarget"
    name="valuesSet"
    value="InjectedValue"/>

    <inject:value-to-map target="InjectTarget"
    name="valuesMap"
    key="valueKey"
    value="InjectedValue"/>

Well, I hope that samples are self-illustrative and actually there is just several things that should be added there:

  1. There are two set of tags that does the same but in slightly different form. Short form of tags allows to insert custom XML tag as part of InjectionBean definition. Injection in verbose form may be used at top level (basically, on the same level where bean tag is described and it simply refers to target bean and one that should be injected.
  2. Overriding of existing values is allowed by current implementation. In other words, if initially InjectionTargetBean was configured to have particular values on simple properties, references to beans and maps with specified keys - after usage of injection tag as above old values will be overridden by new one provide by tags from inject scheme;
  3. Probably that's slightly restrictive, but during performing beans wiring, Inject4Spring performs resolving definitions of appropriate properties (ones to which injection will be performed) and requires that ones were explicitly defined before injection. In other words, even if you don't have appropriate reference at the moment of defining InjectionTargetBean in Spring context (and there injection will be performed later), it still should be declared (say, using null element). The following example illustrates that:
      
    <bean name="InjectTarget"
    class="org.softamis.inject4spring.InjectionTargetBean">
    <property name="beanRefOverride">
    <bean class="org.softamis.inject4spring.InjectedBean"
    p:value="OriginalBeanToOverride"/>
    </property>
    <property name="beanRefEmpty">
    <null/>
    </property>
    <property name="beansList">
    <list>
    <bean class="org.softamis.inject4spring.InjectedBean"
    p:value="Original"/>
    </list>
    </property>
    <property name="beansListToCreate">
    <null/>
    </property>
    <property name="beansMap">
    <map>
    <entry key="toOverride">
    <bean class="org.softamis.inject4spring.InjectedBean"
    p:value="Original"/>
    </entry>
    </map>
    </property>
    <property name="beansSet">
    <set/>
    </property>
    <property name="value" value="ValueShouldBeOverridenByInjection"/>
    <property name="valuesList">
    <list/>
    </property>
    <property name="valuesMap">
    <map/>
    </property>
    <property name="valuesSet">
    <set/>
    </property>
    </bean>

    I understand that for someone that last requirement could be restrictive, but by our experience it's quite convenient since allows to identify (more or less) possible extension points. Anyway, I"m open for your suggestions there.

How it works

I suppose that I will not dig into details too deep there. Anyway, it's open source and all source code for Inject4Spring is freely available. If somebody will be interested, I can write separate entry regarding that.

However, I'll just highlight implementation there. The general idea behind Inject4Spring is more than simple and utilizes all mechanisms provided by Spring framework. To be more precise, everything we need there is custom implementation of BeanFactoryPostProcessor, custom implementation of NamespaceHandler, small hacking of Spring bean factory internals (BeanDefinitionBuilder and related classes), strong coffee, good music and some time to mix that all pieces together.

The entire chain of processing is simple: custom NamespaceHandler is provided to Spring. During parsing custom tags, one is invoked and behind the scene creates custom BeanFactoryPostProcessor that is parametrized by list of commands that should be executed. One command, roughly speaking, is holder of data from appropriate custom tag.

As soon as BeanFactoryPostProcessor is invoked by Spring, it simply plays own list of commands and ones perform necessary setup of target bean within BeansRegistry.

So actually I've fooled Spring context there, since as soon as my BeanFactoryPostProcessor finished it's operation, resulting definition of beans in Spring BeansFactory are the same as if we describe dependencies in usual Spring way. However, despite of that, such approach solves problems addressed at the begining of my entry.

License and download

Inject4Spring is released under Apache License, so it could be used both in open source and commercial applications. At the moment, you may to download it directly from this site, but probably later I'll move it as project on SourceForge or something like that.

At the moment it's quite stable and we've used it during last year for several projects. However, if you'll have some comments, issues, requests for improvements - please do not hesitate contacting me.

Well, that's all for now. Hope you'll find it useful for you. Enjoy!

Published at DZone with permission of its author, Andrew Sazonov.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)