I've had some good feedback about the TDOBeanInjectorObserver that I released earlier this week. However, I realized that with a small bit of further effort, I could make the idea even more useful. I had put quite a bit of work and thought into handling the dependency injection and the caching of the dependencies for maximum performance. With this all in place and working, it was only a small step to create a generic BeanInjector component that could be used to autowire any component at all, not just Transfer Decorators! So we now have TDOBeanInjectorObserver v1.1 and BeanInjector v1.0, both of which are part of my ColdSpring Utilities RIAForge project.

From the outside, the TDOBeanInjectorObserver has only changed slightly. The only difference is that you now must pass a BeanInjector property to the TDOBeanInjectorObserver in your ColdSpring XML, like this:

<bean id="transferFactory" class="transfer.transferFactory">
		   <constructor-arg name="datasourcePath"><value>/project/config/datasource.xml</value></constructor-arg>
		   <constructor-arg name="configPath"><value>/project/config/transfer.xml</value></constructor-arg>
		   <constructor-arg name="definitionPath"><value>/project/transfer/definitions</value></constructor-arg>
		</bean>
		
		<bean id="transfer" factory-bean="transferFactory" factory-method="getTransfer" />
		
		<bean id="TDOBeanInjectorObserver" class="project.components.TDOBeanInjectorObserver" lazy-init="false">
			<constructor-arg name="transfer">
				<ref bean="transfer" />
			</constructor-arg>
			<property name="beanInjector">
				<ref bean="beanInjector" />
			</property>
		</bean>
		
		<bean id="beanInjector" class="project.components.BeanInjector" />
		

The main difference from the 1.0 version is that you must define a BeanInjector bean, and that you must pass this in as a property to the Observer. The only other change is that if you were using the optional constructor arguments for "suffixList" and "debugMode", these are now specified for the BeanInjector instead of the TDOBeanInjectorObserver.

The real benefit of this doesn't have much effect on the Observer. It is that anyone can now use the BeanInjector to autowire any component. Why is this useful? I'm glad you asked! ;-)

There has always been a performance penalty for managing non-Singleton (aka "transient" or "per-request") beans with ColdSpring. This is because ColdSpring goes through several phases of lookups and dependency resolution when it creates beans. With Singletons, this is never an issue because it only happens once, at application startup. However, the benefits of being able to manage non-Singleton beans with ColdSpring, such as autowiring of dependencies, were still great enough to outweigh the overhead.

Well now we can have the best of both worlds. Using the BeanInjector, you can still have any dependent beans that are managed by ColdSpring autowired into your transient object. But because of the way the BeanInjector caches the dependencies for a given component type, the overhead is virtually eliminated.

A second advantage of using the BeanInjector to autowire your transient objects is that you can maintain control over how your transient object is constructed. When you call getBean() on the ColdSpring BeanFactory, you get back a fully initialized and constructed object. This means that you have very minimal control over how the init() method of your component is called (via constructor arguments in the ColdSpring XML configuration). Using the BeanInjector, you are free to create and call the init() method on your transient objects however you want to, and then autowire them at whatever point in the process you want to.

So for example, consider the following ColdSpring XML:

<bean id="userService" class="components.userService">
			<property name="beanInjector">
				<ref bean="beanInjector" />
			</property>
		</bean>
		
		<bean id="beanInjector" class="components.BeanInjector" />
		
		<bean id="validatorFactory" class="components.ValidatorFactory" />
		

My UserService may want to create and use a transient User object. That User object might have the following function defined in it:

<cffunction name="setValidatorFactory" access="public" returntype="void" output="false" hint="I set the ValidatorFactory.">
			<cfargument name="validatorFactory" type="any" required="true" hint="ValidatorFactory" />
			<cfset variables.instance.validatorFactory = arguments.validatorFactory />
		</cffunction>
		

So now, in my UserService, I can do something like this:

<cfset var user = CreateObject('component', 'components.User').init() />
		<cfset variables.beanInjector.autowire(user) />
		

Which will create the User and then autowire it, so that the ValidatorFactory would be injected into it. So this means you could pass dynamic values to the init() method of user if you want to:

<cfset var user = CreateObject('component', 'components.User').init(someDynamicValues) />
		<cfset variables.beanInjector.autowire(user) />
		

Or even that I could create the object, autowire it, and then call init() in cases where my constructor needs to perform logic that requires the bean to be autowired already:

<cfset var user = CreateObject('component', 'components.User') />
		<cfset variables.beanInjector.autowire(user) />	
		<cfset user.init() />
		

Obviously you have to be careful with this approach since you are not creating and initializing the component in one chained method call, but in many cases where the transient object is local to a function, there is no real chance of the uninitialized object being used by something else before you call init() on it. The point is, the option is there.

In any event, I thought having a BeanInjector like this might be useful to people. Given that most of the work had already been done in the creation of the Transfer Observer, I decided to take the next step and just move that logic into a component that could autowire anything. I'm interested to hear any thoughts on this, so please comment and give your feedback. Thanks.

Comments Comments (2) | del.ico.us del.icio.us | Digg It! Digg It! | Linking Blogs Linking Blogs | 4040 Views

Comments

  • # Posted By Brian Kotek | 1/17/08 4:55 PM

    A few more ideas:

    -Have autowire() return the component so you could chain on the init() call.

    -Allow the autowire() method to take either a CFC instance or a CFC type path and it would create the CFC and return it.

    -If it creates the CFC, decide whether it would call init() internally or not before returning the instance.

  • # Posted By Luis Majano | 1/18/08 4:08 AM

    Great Stuff Brian. I just posted a few days ago an autowire interceptor for coldbox, http://www.coldboxframework.com (nightly build). It is similar to what you do in the sense that it can be used to wire anything. I go with an approach of creating metadata dictionaries, so transient objects are a breeze too since their appropriate dependency metadata is cached in its own DI dictionary. You might want to check it out and we can combine our thoughts.

    Luis