DEV Community

Tino Steinort
Tino Steinort

Posted on

Dependency Injection with the BeanRepository

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:

public class ArgumentPrinter {

    private final ArgsProvider argsProvider;

    /**
     * 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.
     */
    public ArgumentPrinter(final ArgsProvider argsProvider) {
        this.argsProvider = argsProvider;
    }

    public void printCommandLineArgs() {

        // Isn't it a nice use case? :-)
        System.out.println("Commandline args[]:");

        for (String arg : argsProvider.get()) {
            System.out.println(" * '" + arg + "'");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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.
 */
public class StartupListener extends ApplicationStartedListener {

    private final ArgumentPrinter argumentPrinter;

    public StartupListener(final ArgumentPrinter argumentPrinter) {
        this.argumentPrinter = argumentPrinter;
    }

    @Override
    public void onEvent(final ApplicationStartedEvent event) {

        // the entry point

        System.out.println("Application started");

        argumentPrinter.printCommandLineArgs();
    }
}
Enter fullscreen mode Exit fullscreen mode

At this point we miss just a starter class, and the configuration of the BeanRepository.

public class SimpleExampleApp implements BeanRepositoryConfigurator {

    public static void main(String[] args) {

        // Set args manually without configuring IDE run configs
        // -> not needed for real applications
        args = new String[] {"argument 1", "argument 2", "value"};

        // Start the application with dependency injection support
        //  1.: parameter: commandline args[]
        //  2.: parameter: Some class that implements BeanRepositoryConfigurator
        BeanRepositoryApplication.run(args, new SimpleExampleApp());
    }

    @Override
    public void configure(final BeanRepository.BeanRepositoryBuilder builder) {

        // 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 beans

        builder
                           // 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
    }
}

Enter fullscreen mode Exit fullscreen mode

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.

  • other scopes
  • providers
  • factories
  • aliases
  • ...

Where can I get it?

Try it out:

<dependency>
  <groupId>com.github.tinosteinort</groupId>
  <artifactId>beanrepository</artifactId>
  <version>1.7.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Get insights:

GitHub logo tinosteinort / beanrepository

A Dependency Injection / Service Locator Library

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
  • no request or session scope
  • no initialisation code allowed in constructor
    • constructor may be called…

Top comments (0)