MetaFactory is a dependency injection mechanism which is based on the assumption that each component has different implementation flavors. MetaFactory is meant for services, i.e. components which offers some methods via public interface. MetaFactory is based on linking and aliasing, consider following example.
Lets say we have a CalculatorService that can add to numbers.
svn |
---|
| ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorService.java |
---|
| ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorService.java |
---|
|
|
We will have at least one real implementation:
svn |
---|
| ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorServiceImpl.java |
---|
| ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorServiceImpl.java |
---|
|
|
and a mock for testing:
svn |
---|
| ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorServiceMock.java |
---|
| ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorServiceMock.java |
---|
|
|
Usually, at least in our world, we would have more impls, but lets stick with two. So, if you are testing a construct like this in spring you will have to setup a real and a test context. Which also means that you'll have to add each new service to both contexts. Each time you add a new one.
Our approach is slightly different, we make the system know about all available implementations and switch the one used by using aliases. Another difference in our approach is that we configure factories, not implementation classes, allowing more complicated instantiation schemes. Here an example of a factory:
svn |
---|
| ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorServiceFactory.java |
---|
| ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/CalculatorServiceFactory.java |
---|
|
|
So how does it work? Somewhere in your app you will have some initialization code, this code can be a context listener, a call from main method or whatever (for example reading a config file). This code will add the knowledge of available instances to the MetaFactory. Another piece of code will also setup the used aliases, therefor configuring the system, which of the implementations of a service should be used. This small test demonstrates it:
svn |
---|
| ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/VerySimpleTest.java |
---|
| ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/VerySimpleTest.java |
---|
|
|
Running this test will produce following output:
Code Block |
---|
On mock called 2 + 2
On impl called 2 + 2
|
ensuring you that both testcases resolved and instantiated different instances.
So now we moved the config from xml into java, fine, but we are still using strings which are not refactoring-proof or compile safe. What have we won? Nothing. Therefore we added a better Example:
svn |
---|
| ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/VerySimpleTestWithExtensions.java |
---|
| ano-prise/trunk/test/junit/net/anotheria/anoprise/metafactory/docs/VerySimpleTestWithExtensions.java |
---|
|
|
Now you see, that we only use classes for service factory registration and resolver and no strings anymore. The built-in enum Extension contains many useful constants but you are not limited to them. The real advantage of this method is the compiler will prevent you from configuring an instance of FooService as CalculatorService which could happen if you configure it in an xml file.
Extensions modify the name of the class. They say stuff like if you need an instance of a CalculatorService use the Remote one. Following Extensions are supported by built-in classes:
svn |
---|
| ano-prise/trunk/java/net/anotheria/anoprise/metafactory/Extension.java |
---|
| ano-prise/trunk/java/net/anotheria/anoprise/metafactory/Extension.java |
---|
|
|