Adding Todo Lists (Autowiring our Handler) - ColdBox Series Part 8c
As Luis stated in the comments section of my last post, we need to set up autowiring in our handler component. This is really cool stuff. I did not do this the first time around because my post was getting to long and because I wanted to demonstrate both ways of doing it.
What is autowiring?
When you are using object factories (like Lightwire and ColdSpring) in your ColdBox applications, you can set up dependency injection for handlers, plugins and interceptors. This is done using the Autowire Interceptor.So what dependency do we have in our List.cfc handler? Here is what it looks like now:
<cfcomponent display="List Handlers" output="false" extends="coldbox.system.eventhandler">
<cffunction name="addList" access="public" returntype="void" output="false">
<cfargument name="Event" required="true" type="coldbox.system.beans.requestcontext" />
<cfset event.setValue('xehDoit', 'list.doAddList') />
<cfset event.setValue('btn', 'Add')>
<cfset event.setView('listForm') />
</cffunction>
<cffunction name="doAddList" access="public" returntype="void" output="false">
<cfargument name="Event" required="true" type="coldbox.system.beans.requestcontext" />
<cfset var todoService = getPlugin("ioc").getBean("todoService") />
</cffunction>
</cfcomponent>
Do you see the getPlugin() call in the doListAdd() handler? That is calling an object that the handler depends on to function properly. And if we were able to see into the future we would see that most, if not all, of the methods in this handler will also need that object. What a perfect opportunity for setting up autowiring. Wouldn't it be nice if we did not need to call this plugin for every method?
<cfset var todoService = getPlugin("ioc").getBean("todoService") />
With autowiring, the todoService will be automatically injected into our variables scope (or another scope) and will always be available. So, first we need to go to our coldbox.xml.cfm file in our /config directory and set up the autowire interceptor.
Setting up autowiring
In the coldbox.xml.cfm file, find the <interceptors> tags and insert this block in between the beginning and end tags.<Interceptor class="coldbox.system.interceptors.autowire">
<Property name="debugMode">true</Property>
<Property name="completeDIMethodName">onDICOmplete</Property>
<Property name="enableSetterInjection">false</Property>
</Interceptor>
This tells ColdBox to use the autowire interceptor. It also sets up an optional method that will automatically be called after the dependency injection is completed. We won't be using that today. And the last property disables setter injection. With setter injection disabled, we only need a cfproperty tag to let ColdBox know what to insert. Honestly, I am not sure why we would want to turn it on. Maybe someone else knows and would like to enlighten us in the comments.
Now that we have modified the coldbox.xml.cfm file, we can change our handler to support autowiring. This is done with a bit of metadata.
<cfcomponent display="List Handlers" autowire="true" output="false" extends="coldbox.system.eventhandler">
Now ColdBox knows to autowire this handler, next we tell it what to inject. So we add a <cfproperty> tag in the pseudo-constructor area of the component. The pseudo-constructor area is in between the <cfrcomponent> tag and the first function.
<cfproperty name="todoService" type="ioc" scope="variables" />
There. That was simple. Now the todoService (the "name" attribute must match the name as defined in ColdSpring or Lightwire) will automatically be placed into the variables scope when this handler is called. So we can get rid of the long getPlugin() call.






1) Your object dependencies are all nicely laid out as properties on top. You do not need to scan the object for setters of any kind.
2) You do not need to create additional getters/setters if your object doesn't really need to create them. Why create them just for Dependency Injection? Objects should be shy!
Those are my main reasons to prefer DI via conventions.
The nice thing about ColdBox is you can use which ever approach suits your need. I tend to let ColdSpring handle all dependency injection, so that I can see all dependencies in one config file (I don't use ColdSpring's autowire capabilities).
I can see what you are saying about the setting injection. I too am glad that we have the choice. For injecting into handlers, I feel like the autowiring is best for me, since I am not worried about handler portability or using the handlers without the framework.
>I also like having a clear API (with hints) especially when I'm extending objects
You still have this with cfproperty. You can add hints, and extra metadata to cfproperty like you would on functions. Also, CFDoc or the CFCViewer plugin can document your objects and you can get a clear definition of a class' dependencies.
On another note, setters and getters are to me useless, unless they provide a specifc behavior to an object. I prefer my objects to be SHY and only expose what they need to. Dependency injection is not a reason for me to expose setters when they can be injected without exposing setters or getters. However, there are always cases where you expose them. But, like you said it, choice is the key, as everybody has choices and ways to do it.
I guess my preference has always been to create Shy objects.
Whilst I've looked at ColdBox before I've never really got into the details (although I've used Frameworks for a long time), so I guess I'm largely stuck in my ways! I think you make a good point Luis about making object SHY. It's just odd to me (and I'm happy to admit many things I used to find odd I do all the time now!) to have ColdSpring and ColdBox doing autowiring. Having said that I do use Paul Marcotte's Transient Factory with Brian Kotek's Bean Injector as well as ColdSpring so I'm already using multiple tools for DI!
One question I do have is in the docs, it uses "instance" instead of "variables" for the cfproperty tag. Are they interchangeable?
If you use "variables" it places it injected objects into the variables scope. If you use "instance" it places them in an "instance" struct in the variables scope.
Just need to work out why my sidebar has vanished now...
========================================================
Message:
ERROR 10:42:57.953 AM coldbox.system.interceptors.Autowire
Error autowiring china-buy.handlers.list. Bean creation exception in model.todos.todoService Could not find the ColdFusion Component or Interface model.todos.todoService.:Ensure that the name is correct and that the component or interface exists.
========================================================
Yes, I DO have a cfc named "todoService.cfc" in wwwroot/china-buy/model/todos/
My config looks like this:
-------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<Config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.coldbox.org/schema/config_3.0.0.xsd&quo...;
<Settings>
<!-- Application Setup-->
<Setting name="AppName" value="china-buy"/>
<Setting name="EventName" value="event" />
<!-- Development Settings -->
<Setting name="DebugMode" value="true"/>
<Setting name="DebugPassword" value=""/>
<Setting name="ReinitPassword" value=""/>
<Setting name="HandlersIndexAutoReload" value="true"/>
<Setting name="ConfigAutoReload" value="false"/>
<!-- Implicit Events -->
<Setting name="DefaultEvent" value="General.index"/>
<Setting name="RequestStartHandler" value="Main.onRequestStart"/>
<Setting name="RequestEndHandler" value=""/>
<Setting name="ApplicationStartHandler" value="Main.onAppInit"/>
<Setting name="SessionStartHandler" value=""/>
<Setting name="SessionEndHandler" value=""/>
<Setting name="MissingTemplateHandler" value=""/>
<!-- Extension Points -->
<Setting name="UDFLibraryFile" value="includes/helpers/ApplicationHelper.cfm" />
<Setting name="PluginsExternalLocation" value="" />
<Setting name="ViewsExternalLocation" value=""/>
<Setting name="LayoutsExternalLocation" value="" />
<Setting name="HandlersExternalLocation" value="" />
<Setting name="RequestContextDecorator" value=""/>
<!-- Error/Exception Handling -->
<Setting name="ExceptionHandler" value=""/>
<Setting name="onInvalidEvent" value=""/>
<Setting name="CustomErrorTemplate" value="" />
<!-- Application Aspects -->
<Setting name="HandlerCaching" value="false"/>
<Setting name="EventCaching" value="false"/>
<Setting name="ProxyReturnCollection" value="false"/>
<Setting name="FlashURLPersistScope" value="session"/>
</Settings>
<!--Model Integration -->
<Models>
<DefinitionFile>config/ModelMappings.cfm</DefinitionFile>
</Models>
<LogBox>
<Appender name="coldboxTracer" class="coldbox.system.logging.appenders.ColdboxTracerAppender" />
<!-- Root Logger Definition -->
<Root levelMin="FATAL" levelMax="INFO" appenders="*" />
<!-- ColdBox Package Logging -->
<Category name="coldbox.system" levelMax="INFO" />
</LogBox>
<Layouts>
<DefaultLayout>Layout.Main.cfm</DefaultLayout>
</Layouts>
<Interceptors>
<!-- USE ENVIRONMENT CONTROL -->
<Interceptor class="coldbox.system.interceptors.EnvironmentControl">
<Property name="configFile">config/environments.xml.cfm</Property>
</Interceptor>
<!-- USE AUTOWIRING -->
<Interceptor class="coldbox.system.interceptors.Autowire">
<Property name="debugMode">true</Property>
<Property name="completeDIMethodName">onDICOmplete</Property>
<Property name="enableSetterInjection">false</Property>
</Interceptor>
<!-- USE SES -->
<Interceptor class="coldbox.system.interceptors.SES">
<Property name="configFile">config/routes.cfm</Property>
</Interceptor>
</Interceptors>
<!--IOC Integration-->
<IOC>
<Framework type="coldspring" reload="true" objectCaching="false">/config/coldspring.xml.cfm</Framework>
<!--<ParentFactory type="coldspring">definition file</ParentFactory>-->
</IOC>
</Config>
My coldspring xml looks like this:
-----------------------------------------
<beans default-autowire="byName">
<bean id="todoGateway" class="model.todos.todoGateway">
<property name="dsn">
<value>todos</value>
</property>
</bean>
<bean id="todoService" class="model.todos.todoService">
<property name="todoGateway">
<ref bean="todoGateway" />
</property>
</bean>
</beans>
My handler looks like this:
----------------------------------
<cfcomponent display="List Handlers" output="false" extends="coldbox.system.eventhandler">
<cfproperty name="todoService" type="ioc" scope="variables" hint=""/>
<cffunction name="addList" access="public" returntype="void" output="false">
<cfargument name="Event" required="true" type="any" />
<cfset event.setValue('xehDoit', 'list.doAddList') />
<cfset event.setValue('btn', 'Add')>
<cfset event.setView('listForm') />
</cffunction>
<cffunction name="doAddList" access="public" returntype="void" output="false">
<cfargument name="Event" required="true" type="any" />
<cfset var todoService = getPlugin("ioc").getBean("todoService") />
</cffunction>
</cfcomponent>
<IOC>
<Framework type="coldspring" reload="true" objectCaching="false">/config/coldspring.xml.cfm</Framework>
<!--<ParentFactory type="coldspring">definition file</ParentFactory>-->
</IOC>
Geez, don't I get weekends off?
;)
I suspect this is the same problem you were having with the coldbox event object. ColdFusion cannot find them because you are working in a subfolder.
Your model components are actually at china-buy.model.*, but you are referencing them at model.*
You can solve this in one of two way.
1. Set up a mapping called /model that points to expandPath('/china-buy/model')
2. Have all of your references point to china-buy.model.* (replace * with the Component name), example china-buy.model.todos.todoService
I would do option 1.
Hope this helps.
Thanks, all fixed. I decided to go for option 2, rather than create one mapping for "/model". After all, I have quite a few different apps in wwwroot.
By the way, GREAT tutorial. I'm getting there slowly.
If you create your mapping in Application.cfc instead of in the administrator, you can make it specific to that application.
Thanks, glad you like the tutorials.