Duke's little B's Blog

An open source developer's perspective – by Thomas Weber

DIY: Java Dependency Injection (Part 4)

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.

Advertisement

Filed under: java, , , , ,

What’s up?

November 2009
M T W T F S S
« Oct   Jan »
 1
2345678
9101112131415
16171819202122
23242526272829
30  

Archives

More information

About the author:

Thomas is working in the internet business since 1998, dedicated to open source software and loves developing in Java.

He is a co-owner of :torweg, a full service internet agency and the company behind “pulse – the web application framework“.

Creative Commons License
This work by Thomas Weber is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.

Follow me on twitter

Follow

Get every new post delivered to your Inbox.