Tuesday, December 30, 2008

Model-Glue-esque Bean Factory, Redux

After using my latest attempt at a Model-Glue inspired bean factory a little longer, I ran into a major problem: circular dependencies.

In my application (and most others), services will need to be able to talk to each other. For example, my application has a productService and a categoryService, and each service will need an instance of the other injected into it.

In ColdSpring, you can get around circular dependencies by using properties (setter injection) rather than constructor arguments. However, I want to use the "beans" scope to inject all my dependencies rather than having to create the necessary setProductService() and setCategoryService() methods.

I solved the problem by re-working my DynamicBeanFactory. I updated the init() method to construct all the beans when the bean factory is created, then inject the "beans", "helpers" and "includes" as necessary. Doing this also allowed me to leave the getBean() method alone, which is kinda nice.

Here's the updated DynamicBeanFactory:


<cfcomponent output="false" extends="coldspring.beanutils.DynamicXMLBeanFactory">

<!------>

<cffunction name="init" access="public" output="false" returntype="any">
<cfargument name="pathToColdspringXML" required="true" type="string" />
<cfargument name="params" required="false" default="" />

<cfset var local = StructNew() />
<cfset var i = "" />

<!--- dynamic properties that get passed into the bean factory --->
<cfif not isStruct(arguments.params)>
<cfset arguments.params = StructNew() />
</cfif>

<!--- init the bean factory --->
<cfset super.init() />

<!--- load the beans --->
<cfset loadBeansFromDynamicXmlFile(arguments.pathToColdspringXML,arguments.params) />

<!--- construct each bean, which puts it in the singleton cache --->
<cfloop collection="#variables.beanDefs#" item="i">
<cfset constructBean(i) />
</cfloop>

<!--- if a "helpers" bean is defined, inject it into all the helpers and beans --->
<cfif StructKeyExists(variables.singletonCache,"helpers")>

<cfset local.helpers = variables.singletonCache.helpers.getHelpers(this) />

<cfset local.injectedHelpers = StructNew() />

<cfloop collection="#local.helpers#" item="i">

<cfset local.bean = local.helpers[i] />

<!--- inject a function to set variables into the "variables" scope --->
<cfset local.bean.__injectVariable = variables.injectVariable />

<!--- inject the "helpers" scope --->
<cfset local.bean.__injectVariable("helpers",local.injectedHelpers) />

<!--- remove the temporary inject function --->
<cfset StructDelete(local.bean,"__injectVariable") />

<cfset local.injectedHelpers[i] = local.bean />

</cfloop>

</cfif>

<cfloop collection="#variables.singletonCache#" item="i">

<!--- set the bean from the cache --->
<cfset local.bean = variables.singletonCache[i] />

<!--- inject a function to set variables into the "variables" scope --->
<cfset local.bean.__injectVariable = variables.injectVariable />

<!--- inject the "helpers" scope if it's defined --->
<cfif StructKeyExists(variables.singletonCache,"helpers")>
<cfset local.bean.__injectVariable("helpers",local.injectedHelpers) />
</cfif>

<!--- get the bean's meta data to see if there are any "beans" or "includes" defined --->
<cfset local.metaData = getMetaData(local.bean) />

<!--- create a container for all the beans that will be injected --->
<cfset local.beans = StructNew() />

<!--- check to see if the bean needs to inject any beans --->
<cfif StructKeyExists(local.metaData,"beans")>

<!--- include each bean in the order specified --->
<cfloop list="#local.metaData.beans#" index="local.injectedBeanName">

<cfset local.injectedBeanName = trim(local.injectedBeanName) />

<!--- insert each bean into the local beans struct, which will be injected after the loop --->
<cfset local.beans[local.injectedBeanName] = variables.singletonCache[local.injectedBeanName] />

</cfloop>

</cfif>

<!--- inject the "beans" scope --->
<cfset local.bean.__injectVariable("beans",local.beans) />

<!--- check for any included beans --->
<cfif StructKeyExists(local.metaData,"includes")>

<!--- include each bean in the order specified --->
<cfloop list="#local.metaData.includes#" index="local.includedBeanName">

<cfset local.includedBeanName = trim(local.includedBeanName) />

<!--- get the bean from cache --->
<cfset local.includedBean = variables.singletonCache[local.includedBeanName] />

<!--- get all the public functions in the included bean and copy them to the main bean --->
<!--- even though private functions aren't copied, references to them from within copied functions will still work --->
<cfloop collection="#local.includedBean#" item="local.includedBeanVariableName">

<!--- only copy the function if it isn't defined yet --->
<cfif not StructKeyExists(local.bean,local.includedBeanVariableName)>

<!--- put the function into the object (the "this" scope) --->
<cfset local.bean[local.includedBeanVariableName] = local.includedBean[local.includedBeanVariableName] />

<!--- set the function into the variables scope, otherwise would need to reference the function as this.fn() --->
<cfset local.bean.__injectVariable(local.includedBeanVariableName,local.includedBean[local.includedBeanVariableName]) />

</cfif>

</cfloop>

</cfloop>

</cfif>

<!--- remove the temporary inject function --->
<cfset StructDelete(local.bean,"__injectVariable") />

</cfloop>

<cfreturn this />

</cffunction>

<!------>

<cffunction name="injectVariable" access="public" output="false" returntype="void" hint="I get injected into a bean in order to set variables">
<cfargument name="key" required="true" />
<cfargument name="value" required="true" />

<cfset variables[arguments.key] = arguments.value />

<cfreturn />

</cffunction>

<!------>

</cfcomponent>

No comments:

Post a Comment