Autowiring From The ColdBox Cache
ColdBox 2.6 introduces the ability to autowire ANY object using a combination of metadata, the autowire interceptor or the bean factory plugin. We will see a combination of all these steps in order to autowire some event handlers. The first part will show you how to place some objects in the cache by using an application start handler called main and how to autowire itself. Then we will create another event handler that uses some of the objects we placed on cache and how to let ColdBox autowire them for you.
Step1: Autowire Interceptor
The first step is to declare the usage of the autowire interceptor in our application. This interceptor reads metadata out of interceptors, plugins, and handlers. It can then determine how to autowire them with dependencies, either by reading off the cfproperty tags or try to match by reading of the setter methods in your handler. Example: setConfig(), setUserService(). I recommend you also first read the Autowire Guide.
<Interceptors> <Interceptor class="coldbox.system.interceptors.autowire" /> </Interceptors>
As you can see, it is super easy to define your autowire interceptor in your coldbox.xml.cfm. This tells the framework to load up the autowire interceptor and let it do its goodness!
Autowire Interceptor Properties
The following is a brief summary of the autowire interceptor properties.
- debugmode : boolean A boolean variable to log debug information. Default is false.
- completeDIMethodName : string The name of the method to call once the interceptor wires up an object. Default is onDIComplete.
- enableSetterInjection : boolean A boolean variable to disable/enable setter injection. Default is true.
Step2: Autowire Review
Just to review how the autowire interceptor works, here are a few pointers. The autowire interceptor detects if a handler, plugin or interceptor needs to be wired (injected) with dependencies if it contains the metadata autowire=true in its cfcomponent declaration:
<cfcomponent name="myHandler" output="false" autowire="true">
This tells the interceptor, ok, let's autowire it. Second, the interceptor needs to know WHAT to inject and it can either use the cfproperty tag or setter methods.
cfproperty
You can add cfproperty tags that describe the relationships this object has:
<cfproperty name="UserService" type="ioc" scope="variables" /> <cfproperty name="SecurityService" type="ocm" scope="instance" />
In order for the autowire to pick them up, you must declare them with two types:
- ioc : which means look for dependencies in the IoC factories like coldspring or lightwire
- ocm : which means look for dependencies in the ColdBox cache.
If the autowire detects any of these types, it will then look for the scope attribute, this is the scope of where they will be injected and it can be: this, variables, instance, anything. If the scope is not defined, the autowire will inject in the private scope of variables. Ok, you are now in the world of descriptive OO, where you describe object relationships.
setter injection
You can also use setter injection if you prefer, although in my opinion its a little less descriptive than cfproperties because any object can have setters that don't necessarily are external dependencies. Anyway, in order to re-create what we had above in the cfproperty sample, look below:
<cffunction name="setUserService" output="false" returntype="void" access="private"> <cfargument name="UserService" type="model.UserService"> <cfset variables.UserService = arguments.UserService> </cffunction> <cffunction name="setSecurityService" output="false" returntype="void" access="private"> <cfargument name="SecurityService" type="model.SecurityService"> <cfset instance.SecurityService = arguments.SecurityService> </cffunction>
As you can see, these setter methods are private and they way the we know the dependency is by looking at the method signature. The autowire interceptor gets all the methods that start with set and then loop over them and try to see if the remaining words are a match for a bean in the IoC or an object in the cache. As you can see, its less descriptive than using cfproperty, but it also does the trick.
Step 3: Application Start Handler
Ok, enough reviews. Let's create a handler that contains an event that will be executed when our application starts up. This event is defined in the coldbox.xml.cfm and it looks like this:
<Setting name="ApplicationStartHandler" value="main.onAppInit" />
Now let's create this event and basically create some model objects and store them in the cache. One thing to consider is that even if I determine that this main handler needs autowiring and it occurs before I place objects in the cache, it won't fail because it checks if the objects are in the cache. Therefore, after our application start method, we will manually autowire the handler and then consequent events from this handler will already use the right dependencies. You might ask yourself, why add the autowire tag then? I add it, because in development I do not use handler caching so development can go smoother. So what happens is that the manual autowiring works for that request, consequent request would fail because the handler has just been created, thus the autowire tag. The autowire interceptor will read it and do automatic autowiring, only this time it won't fail because our objects are now in the cache. If you don't get this right now, let it sink in, look at the code and you will get it.
<cfcomponent name="main" output="false" autowire="true"> <!--- Handler Dependencies: Autowired by ColdBox ---> <cfproperty name="ConfigService" type="ocm" scope="instance" /> <cfproperty name="SecurityService" type="ocm" scope="instance" /> <!--- On App init Event Action ---> <cffunction name="onAppInit" access="public" returntype="void" output="false"> <cfargument name="Event" type="any"> <cfscript> var oConfigService = CreateObject("component",getSetting('AppMapping') & ".model.ConfigService").init(getDatasource('myDSN')); var oSecurityService = CreateObject("component",getSetting('AppMapping') & ".model.SecurityService").init(getDatasource('myDSN')); /* Cache Them as Singletons */ getColdboxOCM().set("SecurityService",oSecurityService,0); getColdboxOCM().set("ConfigService",oConfigService,0); /* Autowire Yourself (if cached then awesome, if not using caching, then at least we placed our dependency in the cache) */ getPlugin("beanFactory").autowire(target=this,useSetterInjection=false); </cfscript> </cffunction> <!--- ON request Start Method ---> <cffunction name="onRequestStart" access="public" returntype="void" output="false"> <cfargument name="Event" type="any"> <cfscript> var rc = event.getCollection(); /* Maintenance Mode Check using dependency */ if( not instance.ConfigService.getSettings().isEnabled ){ event.overrideEvent('general.maintenance'); } /* Security */ if( not instance.SecurityService.isAuthorized() ){ event.overrideEvent('users.login'); } </cfscript> </cffunction> </cfcomponent>
The second action is the request start handler that executes on every request. You can see that it uses both of our dependencies by just looking into their specific scopes. The most interesting line is the last line of the app start handler:
/* Autowire Yourself (if cached then awesome, if not using caching, then at least we placed our dependency in the cache) */
getPlugin("beanFactory").autowire(target=this,useSetterInjection=false);
This line calls the bean factory plugin and tells it to autowire something and in our case it is ourselves. There are more arguments to this autowire method that you can read in the Coldbox API, but the basics are shown. This call, autowires this handler with dependencies from the OCM or IoC if declared. Cool, let's create another handler and set its dependencies:
Step 4: Another Handler
This is just another handler that uses the security service for some validations.
<cfcomponent name="users" output="false" autowire="true"> <!--- Handler Dependencies: Autowired by ColdBox ---> <cfproperty name="SecurityService" type="ocm" scope="instance" /> <cffunction name="userCheck" access="public" returntype="void" output="false"> <cfargument name="Event" type="any"> <cfscript> var rc = event.getCollection(); if( instance.SecurityService.isUserValid(event.getValue('username','')) ){ event.renderData(type='plain',true); } else{ event.renderData(type='plain',false); } </cfscript> </cffunction> </cfcomponent>
Again, we have declared the autowire metadata to true and created some cfproperties to describe our relationships. Then in our userCheck action we use it normally. You can also see that we use the new event.renderData() method to quickly return results to the browser or coldbox proxy. So we can assume that this call is mostly coming from an ajax call.
Summary
So we have learned how to use the autowire interceptor, the bean factory and some metadata to get our objected autowired. We have also learned that we can autowire from an IoC container like Coldspring and lightwire, but also from the coldbox cache. All by simply using descriptive metadata.
