Creating a simple dependency injection framework with custom annotations, reflections and JAXB
You can download the complete source as an Eclipse Java project.
Already featured in the previous parts
In the first part we have created the @Injected annotation, an Interface for our units of work (IUnitOfWork) and skeletons for the Injector and the Executor. The second part was all about gathering the Java reflections basics to process the annotated classes. Finally a basic introduction to JAXB has been featured in the third part. Today we will put it all together.
A base directory for all configurations
You already know how to create configuration instance from XML via JAXB and how to find annotated fields. Next we will define a mechanism to resolve the XML files and inject the instance into the annotated field. For our little example, we define that all configurations will be place in one directory “conf” on the root level of out project. The naming convention for the configuration XML files is canonical name of the field declaration + .xml. This allows an easy resolving of the configurations.
Let’s improve our Injector to use the “conf” director and to inject the unmarshaled instances
package dukeslittleb.tutorials.ioc;
import java.io.File;
import java.lang.reflect.Field;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
public class Injector {
private static final File CONFIG_DIRECTORY;
private static final JAXBContext JAXB_CONTEXT;
static {
// get the configuration directory
File f = new File("conf");
if (!f.exists()) {
throw new RuntimeException(f.getAbsolutePath() + " does not exist");
} else if (!f.isDirectory()) {
throw new RuntimeException(f.getAbsolutePath()
+ " is not a directory");
} else if (!f.canRead()) {
throw new RuntimeException(f.getAbsolutePath() + " is not readable");
}
CONFIG_DIRECTORY = f;
try {
// set up th JAXB context
JAXB_CONTEXT = JAXBContext
.newInstance("dukeslittleb.tutorials.ioc");
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
public static final IUnitOfWork setUp(final IUnitOfWork unit) {
System.out.println("Injector: setting up "
+ unit.getClass().getCanonicalName());
for (Field field : unit.getClass().getDeclaredFields()) {
// check if field is annotated
if (field.isAnnotationPresent(Injected.class)) {
System.out
.println(" Trying to inject into " + field.getName());
field.setAccessible(true);
try {
// inject into field
field.set(unit, JAXB_CONTEXT.createUnmarshaller()
.unmarshal(
new File(CONFIG_DIRECTORY, field.getType()
.getCanonicalName()
+ ".xml")));
} catch (Exception e) {
throw new RuntimeException(e);
}
field.setAccessible(false);
}
}
return unit;
}
}
Adding some live to the Executor
Now let’s get back to the Executor from part 1 and apply a few changes:
package dukeslittleb.tutorials.ioc;
public class Executor implements IUnitOfWork {
@Injected
private IConfiguration conf;
public void run() {
System.out.println("running " + this + " with a configuration of type "
+ conf.getClass().getCanonicalName());
}
}
And add a configuration file into conf named “dukeslittleb.tutorials.ioc.IConfguration.xml”:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <complex-executor-config> <sub-tasks> <class>dukeslittleb.tutorials.ioc. HelloWorldUnit</class> <class>dukeslittleb.tutorials.ioc. HelloConfigurationUnit</class> </sub-tasks> </complex-executor-config>
Now run IOCSampleApp. Your output should look like:
Injector: setting up dukeslittleb.tutorials.ioc.Executor Trying to inject into conf running dukeslittleb.tutorials.ioc.Executor@1ad77a7 with a configuration of type dukeslittleb.tutorials.ioc.ComplexExecutorConfig
So far, so good.
Spice it up!
Until now the Excutor behaves a bit static. What we want is some more injections. To get that we will first create another interface which extends IConfiguration: IHasSubTasks (the name says it all).
package dukeslittleb.tutorials.ioc;
import java.util.Collection;
public interface IHasSubTasks extends IConfiguration {
Collection<IUnitOfWork> getSubTasks();
}
Next we tell our Executor to execute the sub-tasks of its configuration, if the configuration is an instance of IHasSubTasks. This is how the enhanced run-method looks like:
public void run() {
System.out.println("running " + this + " with a configuration of type "
+ conf.getClass().getCanonicalName());
if (this.conf instanceof IHasSubTasks) {
for (IUnitOfWork unit : ((IHasSubTasks) this.conf).getSubTasks()) {
unit.run();
}
}
}
And then we implement the interface in ComplexExecutorConfig:
package dukeslittleb.tutorials.ioc;
...
public class ComplexExecutorConfig implements IHasSubTasks {
...
public List<IUnitOfWork> getSubTasks() {
List<IUnitOfWork> sTasks = new ArrayList<IUnitOfWork>();
for (String cn : this.tasks) {
sTasks.add(Injector.setUp(cn));
}
return sTasks;
}
}
To get this to work we also add an
IUnitOfWork setUp(String) method to the Injector to do the dynamic instancing. Neat…
public static IUnitOfWork setUp(final String cn) {
try {
return setUp((IUnitOfWork) Class.forName(cn).newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Two units of work for demonstration purposes
Finally, we create two additional IUnitOfWork implementations to make use of our new toys. Both implementations are simple and straight-forward.
package dukeslittleb.tutorials.ioc;
public class HelloWorldUnit implements IUnitOfWork {
@Override
public void run() {
System.out.println("Hello World!");
}
}
package dukeslittleb.tutorials.ioc;
public class HelloConfigurationUnit implements IUnitOfWork {
@Injected
private SimpleExecutorConfig conf;
@Override
public void run() {
System.out.println(this.conf.getMessage());
}
}
Now run IOCSampleApp again:
Injector: setting up dukeslittleb.tutorials.ioc.Executor Trying to inject into conf running dukeslittleb.tutorials.ioc.Executor@1ad77a7 with a configuration of type dukeslittleb.tutorials.ioc.ComplexExecutorConfig Injector: setting up dukeslittleb.tutorials.ioc.HelloWorldUnit Injector: setting up dukeslittleb.tutorials.ioc.HelloConfigurationUnit Trying to inject into conf Exception in thread "main" java.lang.RuntimeException: java.lang.RuntimeException: javax.xml.bind.UnmarshalException - with linked exception: [java.io.FileNotFoundException: ...
Boom - just as expected. As you can see the Injector is looking for the configuration file "dukeslittleb.tutorials.ioc.SimpleExecutorConfig.xml" to inject conf in dukeslittleb.tutorials.ioc.ComplexExecutorConfig, so let's add it:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <simple-executor-config> <message>Hello from XML</message> </simple-executor-config>
Happy Ending
Now let's do the final run of IOCSample App:
Injector: setting up dukeslittleb.tutorials.ioc.Executor Trying to inject into conf running dukeslittleb.tutorials.ioc.Executor@b8f82d with a configuration of type dukeslittleb.tutorials.ioc.ComplexExecutorConfig Injector: setting up dukeslittleb.tutorials.ioc.HelloWorldUnit Injector: setting up dukeslittleb.tutorials.ioc.HelloConfigurationUnit Trying to inject into conf Hello World! Hello from XML
That's it! Now you know how to implement your own custom runtime annotations in Java, how to access fields reflectively (as a matter-of-fact methods work just the same) and how to use JAXB for your Java XML bindings.
Filed under: java, dependency injection, diy, java, jaxb, tutorial
