Dependency Injection with code generation
Why another Dependency Injection framework?
One thing nearly all DI frameworks have in common, is the use of reflection to obtain the extra information needed to inject the objects.
The downside of this approach is that reflection in general (and particularly in Flash) is pretty slow. This will probably not matter much for smaller application, but for large apps you can save several seconds of start-up time if you are not using reflection.
Unfortunately there is no solution out there beside a project from Joa Ebert, who is using a different approach. I wrote about this in another blog entry.
Another solution would be to use code generation.
With code generation you can inspect your code base at compile-time and write the information needed to a class.
I have added such a project to the nucleo library at github.
So how does it look like and how is it used?
Here are a few code snippets from the mojito example at github:
First we need the generated class for the constructor parameter keys. This will be created by an ant task, more about this later.
[code lang="actionscript3"]public class ConstructorParameters extends AConstructorParameters { protected override function config():void { constructorParameterKeys[BarProvider] = [IWaitress]; constructorParameterKeys[Bar] = [IWaitress]; constructorParameterKeys[Client] = ["theFewSpanishWords", IBar]; constructorParameterKeys[Waitress] = ["isInTheMood"]; } }[/code]
You need to create the Config class which contains the mappings of the objects used for injection.
It passes an instance of the ConstructorParameters class to the AConfig super class.
[code lang="actionscript3"]public class MojitoConfig extends AConfig { public function MojitoConfig() { super(new ConstructorParameters()); } override protected function setup():void { mapInterface(IClient).toClass(Client).asSingleton(); mapParameterName("theFewSpanishWords").toInstance("¡Muchas gracias guapa!"); mapInterface(IBar).toProvider(BarProvider).asSingleton(); mapInterface(IWaitress).toClass(Waitress).asSingleton(); mapParameterName("isInTheMood").toInstance(true); } }[/code]
Then the setup can be done. After that you can access some root object and use this.
[code lang="actionscript3"]// first we need our Config. This is the place where our injection // mappings are defined. var config:MojitoConfig = new MojitoConfig(); // createInjector is a package level function used for convenience // to get the injector injector = createInjector(config); // we take the root object out of the injector. The other objects will // be injected just in time when they are needed. var touristInBarcelona:IClient = injector.getObject(IClient);[/code]
Beside this, there are no traces left from the framework.
The Classes which gets created by the DI container are straight classes with constructor parameters totally unaware of the framework.
They don’t need any “Inject” Metadata tag. They does not know anything from the DI framework and how they get created. This is not their responsibility.
I think this is violated by the use of the Metadata at other DI frameworks. There the class has the information inside itself that a DI container is used for creating an instance of this class. I think this should be only the responsibility of the outer world using this class like a factory or the DI container.
But back to the project.
How does it work internally?
Basically the injection works pretty simple as a chained instantiation of all the dependent objects, starting with the first object requested from the injector:
[code lang="actionscript3"]var touristInBarcelona:IClient = injector.getObject(IClient);[/code]
This will create an instance of the class which is mapped to the key IClient. In our case it is the Client class.
[code lang="actionscript3"]mapInterface(IClient).toClass(Client).asSingleton();[/code]
For creating the Client class it looks up in the ConstructorParameters Class and get the information to create the 2 arguments needed there:
The instance mapped to “theFewSpanishWords” and an object of the class mapped with the key IBar.
[code lang="actionscript3"]constructorParameterKeys[Client] = ["theFewSpanishWords", IBar];[/code]
For creating an object of the Bar class it will need other objects as well, so the chain goes on like this until all objects needed are resolved.
The binding key can be an Instance, a Class or a String (named annotation).
For the named annotation I use a simple convention:
The parameter name must be the same as the annotation name in the Config class, like the “theFewSpanishWords” in the example. I think there is no reason not to use this simple and sensible convention and it makes life much easier and there is no need to add a Metadata for packing in this information.
I only support constructor injection as I prefer this style and it lets the class stay free of any Metadata.
Property or method injection could also be added but then you need to mark them with Metadata. As they would be invoked directly after the class is created i don’t see any reason why not to add these injected data to the constructor.
Now have a look to the code generation:
I used the as3-commons-jasblocks project which is using ANTLR. With this Java project you can read in your sources and it creates an object tree with all the elements of your code (abstract syntax tree). From this you can comfortably grab the constructor parameters and write (again with jasblocks) them into a class file.
I packed this into an ant task, as ant is pretty easy to use, good integrated into the IDEs and well known.
In the ant task you need to setup 2 parameters:
- The source directories (as comma seperated list)
- The target directory for the generated Class.
To automatically trigger the code generation for the ConstructorParameters Class before the compilation, you can use the built-in support for ant tasks of the IDE.
In FlashBuilder and IntelliJ there are easy ways to achieve this. So if the setup guarantees to always run the ant task before the compilation, the ConstructorParameters Class will be always up to date.
Here a quick description how to do this in FlashBuilder:
- Right-click in the project and open the project properties.
- Under Builder add a new Ant Builder before the Flex (compile) builder.
- Under Main point to the build file: eg. ${workspace_loc}/${project_name}/build/build.xml
- Under JRE: select separate JRE
That’s it.
In IntelliJ you need to set an “Execute On” event (“Before Compilation”) to the ant task target. Then it will be executed just before the compilation.
If you don’t like to use the code generation you can also write this class by yourself of course, you just have to maintain it and it violates the DRY principle.
A few words about code generation in general:
For some people code generation has a kind of bad smell and they don’t like to rely on it.
I think it is a very powerful tool to get around the limitations of the language and to outsource boiler plate code.
And if you use Flex you are using it anyway even if you are not aware of it. Flex use a lot of code generation behind the scenes (add the -keep compiler flag to see it) for creating ActionScript code out of MXML, css files, RPC or Data Binding.
I think for the coding experience and productivity it is not only the potential of the language which counts, but also the features of the tools (IDE) you are working with.
What would you (as a developer writing code) benefit much from a type system if you would write your code in a primitive text editor without any code completion or error highlighting (the old Flash IDE was like this, I cannot imagine anymore how to work that way).
With these tools you can get also over the shortcomings of a language.
So I think when you hit the limitations of the language or runtime, it is valid to go in this direction and add features to help you to write clean and fast code.
There are also some limitations and the project is at the moment just a kind of “proof of concept”. It is not bulletproof yet and not tested in a production environment!
So use it at your own risk. I added comments and TODOs in the Java source code about known issues.
One limitation is about external library (swc) files.
With the as3-commons-jasblocks project you can only inspect the code base you have as source code, but not the code compiled into libraries.
There are some solutions (like flagstone) to access this as well, but I have not added this yet.
With the providers (see docs and example code) you always can get around any problems with classes out of your control like 3rd party libraries.
Beside this I think DI should only be used in a limited scope of a project (module).
Comments Off on Dependency Injection with code generation
Category: Actionscript,Flash,Flex