Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
An object that is scoped into request, session, server, cachebox or application scopes and if wired into a persisted object will remain around even when this object has expired from the scope. This is called scope-widening injection and is a problem that must be addressed by NOT injecting them into persisted objects directly but by using WireBox's provider approach. This guarantees that the object's scope lifecycle will be maintained and your singleton or other persistent objects will be decoupled from the scope by accessing the target object via its provider.
For example, let's say you have a handler that wires in a user object that is scoped into session scope, but the handler itself is scoped as a singleton:
So when the handler is created and persisted as a singleton, the user object gets created, stored in session and also referenced into the lifecycle of the handler object. So now, if the user expires from session, the handler does not know about it, because all it knows it that a direct reference to that out of context object still remains. So if the user needed things in session to exist, this will now fail. This problem is much like how Hibernate and detached objects work. Objects are no longer in session, they are detached. This scope widening issue is resolved by NOT injecting the user object directly into the handler but by using a provider.
→ Scope Widening Injection Solution: Object Providers
Below is my favorite approach to solving the issue which is by using provided methods:
That's it! My getUser()
method will be replaced by WireBox with a proxy provider method that will request from the WireBox injector the user mapping instance.
Let's get funky now! We have seen how to inject objects and how to scope objects. However, we need to talk about a cool WireBox feature called object providers. We learned that when you request an object from WireBox it creates it and injects it immediately. However, sometimes we need more control like:
Delay construction of the dependency until some point in time during your controlled execution. Maybe you don't want to construct some dependencies until some feature in your application is enabled.
You need multiple instances of a class. Like a User service producing transient users, or our espresso machine creating espressos.
You need to access scoped objects that might need reconstruction. Maybe you want to check the cache first for existence or a ColdFusion scope in order to avoid scope widening injection.
You have some old legacy funkiness for building stuff that has to remain as its own factory.
All of these areas are where WireBox Providers can really save the day. WireBox offers an automatic way to create providers for you by creating generic provider classes (wirebox.system.ioc.Provider
) that will be configured to provide the mapping you want, then injected instead of the real object requested.
This happens whenever you use the provider DSL injection namespace or annotate methods with a provider
annotation. It also gives you an interface (wirebox.system.ioc.IProvider
), which is very simple, which you can implement in order to register your own complex providers with WireBox.
You would usually do the latter if you have legacy code you need to abstract out, had funky construction processes, etc. Let's start by looking at how registering custom providers works first and then how to use the automatic WireBox providers.
You can inject automatic object providers by using the provider injection DSL namespace. This will inject a WireBox provider class (wirebox.system.ioc.Provider
) that follows our Provider pattern with one method on it: get()
that will provide you with the requested mapped object.
The difference between custom providers here is that WireBox will create a virtual provider object for you dynamically at runtime, configure it to retrieve a specific type of mapping and then use that for you. The provider namespace will take everything after it and evaluate it as either a named mapping or a full injection DSL string.
For example, inject="provider:MyService"
will inject a provider of MyService
objects, so it will look for a MyService
ID in the binder. However, you can also get mega funky and do this: inject="provider:logbox:logger:{this}"
and WireBox will create a provider of logbox:logger:{this}
.
Caution Remember that the value of the provider can be a simple ID or a full injection DSL.
That's it! You basically use the provider:{mapping}
injection DSL to tell a property, setter or argument that you want a provider object instead of the real deal. This will allow you to delay construction of such an object or avoid the nasty pitfall of scope widening injection.
If you need to abstract old legacy code or have funky construction processes, we would recommend you build your own provider objects. This means that you will create a component that implements wirebox.system.ioc.IProvider
(one get()
method) and then you can map it. Once mapped, you can use it anywhere WireBox listens for providers:
The Injection DSL →
The mapping DSL
Here is the interface you need to implement:
The CFC you build will need to be mapped so it can be retrieved by name and also so if it needs DI or any other WireBox funkiness, it can get it. So let's look at our FunkyEspressoProvider that we needed to create since we have some old legacy machines that we need to revamp:
Finally we map to the provider using the .toProvider()
mapping method in the binder so anytime somebody requests an Espresso we can get it from our funky provider. Please note that I also map the provider because it also has some DI needed.
Cool! That's it, anytime you request an Espresso
, WireBox will direct its construction to the provider you registered it with.
This is a feature where you can mark methods in your components with a special provider
annotation so they can serve the objects you requested automatically for you. This is an amazing feature as it will take the original method signature and replace the method for you with one that will serve the provided objects for you automatically. How insane is that! You deserve some getting jiggy wit it (chapter 4) dancing!
Wow! That's it! Yep, just create an empty method signature and annotated with provider={mapping}
and then WireBox will read these annotated methods and replace them for you at runtime so when you call etEspresso()
it actually calls the WireBox injector and requests a new espresso instance and it returns it.
Caution Please note that the visibility of provided methods does not matter to WireBox. It can provide
public, private, or packaged
visibilities with no problem at all.
You can also use our cool mapping DSL to create mappings that refer to provided objects by using the DSL construction type:
You can use the following mapping methods to map to virtual providers by using their dsl arguments:
mapDSL()
mapDSL()
property(name="",dsl="")
setter(name="",dsl="")
methodArg(name="",dsl="")
Thanks to our ColdBox Evangelist, Brad Wood, we have a feature in our Providers that you can leverage its onMissingMethod()
to proxy calls into the provided object itself. So let's say our provided object has a method called sayHello()
, then with an injected provider you must do this:
That is great, but you can proxy calls into the provider itself by removing the extra get()
call and doing this:
The WireBox provider object (wirebox.system.ioc.Provider
) has an onMissingMethod()
function that will take all missing method calls and proxy them to the provided object. Now, this is great but be ready to lose on performance if you use this approach. That is the only caveat to this approach, is that you will be impacted by performance, not crazy, but try it.
The mapping destination toProvider()
can also take a closure that will be executed whenever that mapping is requested. This allows you to add your own custom provider construction code inline without creating a standalone provider object that implements our provider interface. By leveraging closures you can really get funky and more concise in your coding. This closure will have the following signature and it must return the instance requested:
Here is an example of how to accomplish this: