Oh no, it happend again! There is it, another dependency injection framework...
Why?
In the past I want to start a project which should be runnable in the java sandbox, and I want to use dependency injection. First I thought I can use just the dependency mechanism of Spring (spring-context), but that was not the case: Spring widely uses reflection, which is not allowed in the sandbox.
Nearly the same result for my next try: Guice. Now, when I tried this for my needs, there was an error with some system properties, which are not allowed to read from within the sandbox. Today I'm not able to remember which property and error exactly.
At this point, I don't want to try other frameworks, but I know there are a lot of them. For sure there are frameworks without using reflection. I thought it could be pretty cool, to create an own DI framework, and see what problems I have to solve, to match my expectations. And an other point: coding is fun.
Original expectations
some kind of dependency injection
singleton and protoype beans
no reflections and annotations
configuration in java code
These expectations expands after a while. As well as I finished the project for which the BeanRepository was initiated.
What happend?
However, the first result was a Service Locator. A bean has to ask activly for an other beans. Over the time the API evolved and now, I think I can say it is a mix of a Service Locator and Dependency Injection framework. It depends what you want to do, and how you do it. In this link Martin Fowler describes his idea about both approaches.
Can I see some code?
This is a very simple example with three classes. The usecase of this program is just to print out the command line parameter.
This is our usecase, just a service:
publicclassArgumentPrinter{privatefinalArgsProviderargsProvider;/**
* The ArgsProvider is responsible for getting the command line arguments,
* so we need it as a dependency. The ArgsProvider bean is provided by
* the BeanRepository.
*/publicArgumentPrinter(finalArgsProviderargsProvider){this.argsProvider=argsProvider;}publicvoidprintCommandLineArgs(){// Isn't it a nice use case? :-)System.out.println("Commandline args[]:");for(Stringarg:argsProvider.get()){System.out.println(" * '"+arg+"'");}}}
The BeanRepository is kind of a container, so we need an entry point to execute our own code:
/**
* Every bean, that implements ApplicationStartedListener, will be triggered
* right after the BeanRepository is built. This should be used to execute code
* on startup.
*/publicclassStartupListenerextendsApplicationStartedListener{privatefinalArgumentPrinterargumentPrinter;publicStartupListener(finalArgumentPrinterargumentPrinter){this.argumentPrinter=argumentPrinter;}@OverridepublicvoidonEvent(finalApplicationStartedEventevent){// the entry pointSystem.out.println("Application started");argumentPrinter.printCommandLineArgs();}}
At this point we miss just a starter class, and the configuration of the BeanRepository.
publicclassSimpleExampleAppimplementsBeanRepositoryConfigurator{publicstaticvoidmain(String[]args){// Set args manually without configuring IDE run configs// -> not needed for real applicationsargs=newString[]{"argument 1","argument 2","value"};// Start the application with dependency injection support// 1.: parameter: commandline args[]// 2.: parameter: Some class that implements BeanRepositoryConfiguratorBeanRepositoryApplication.run(args,newSimpleExampleApp());}@Overridepublicvoidconfigure(finalBeanRepository.BeanRepositoryBuilderbuilder){// This method is used to configure the available beans an how// they depends on each other.// The scheme of the singleton method is:// 1.: type of the bean// 2.: reference to the constructor of the bean// 3., 4., 5., 6., 7.: the types of the needed beansbuilder// type of the bean // constructor ref // type of reference.singleton(StartupListener.class,StartupListener::new,ArgumentPrinter.class)// type of the bean // constructor ref // type of reference.singleton(ArgumentPrinter.class,ArgumentPrinter::new,ArgsProvider.class);// the ArgsProvider is a bean which is provided by the BeanRepository, to get// the command line args}}
There is a limitation of beans that can be referenced. Currently a bean can have only 5 or less dependencies. Because no reflection is used, stuff is implemented by manually coding. In this case: create interfaces with one method with different amount of parameters. For my cases it was enough.
An other point is, that cyclic references are not supported directly. It is possible, but with some extra coding. See this example.
Other stuff
There are other functionalities, which are not listed in this post.
BeanRepository - Dependency Injection / Service Locator
This framework is the implementation of a mix of the Service Locator Pattern and a
the Dependency Injection Pattern. These patterns are described by Martin Fowler in
this article. The BeanRepository
does not use reflection for injecting beans. Because of that fact, it can be used in
the Java sandbox, where reflection is not allowed.
Features
simple, self-explanatory and failsafe configuration in Java code
no use of reflection or annotations
constructor injection
support for singletons, prototypes and instances
provider
factories
aliases for beans
fail fast on start up
execute code after initialisation of the bean (post construct)
configurable if singletons are lazy initialised or not
detect beans of a specific type
modularity possible
Limitations
cyclic references not supported directly. But if needed, see
CyclicReferenceExampleApp
for a solution