David Barda
David Barda

Reputation: 1010

Dynamic dependency injection in spring

I was looking for a way to create a non-singleton spring bean which can be "partially" autowired.

@Component
class Example {
    private SpringBean1 bean1;
    private SpringBean2 bean2;
    private String dynamicDependancy;

    @Autowired
    public Example(SpringBean1 bean1, SpringBean2 bean2, String dynamicDepedency) {
        this.bean1 = bean1;
        this.bean2 = bean2;
        this.dynamicDepedency = dynamicDepedency;
    }
}

I want such a thing because sometimes the dependency is known only in runtime. There is a way I have thought of which is creating a kind of a factory that create a static member class, in that way I can test the static member class:

@Component
class ExampleFactory {
    private SpringBean1 bean1;
    private SpringBean2 bean2;

    @Autowired
    public ExampleFactory(SpringBean1 bean1, SpringBean2 bean2) {
        this.bean1 = bean1;
        this.bean2 = bean2;
    }

    public Example from(String dynamicDependency) {
        return new Example(bean1, bean2, dynamicDependency);
    }

    static class Example {
        private SpringBean1 bean1;
        private SpringBean2 bean2;
        private String dynamicDependancy;

        public Example(SpringBean1 bean1, SpringBean2 bean2, String 
            dynamicDependancy) {
            this.bean1 = bean1;
            this.bean2 = bean2;
            this.dynamicDependancy = dynamicDependancy;
        }
    }
}

I was looking a bit about the Prototype scope and using getBean(java.lang.String, java.lang.Object) make it harder to use dependency injection. I'd like to know if there's any "Spring way" to do such things.

Thank you.

Update: I have found another solution and post an answer at another post: https://stackoverflow.com/a/52021965/2580829

Upvotes: 1

Views: 6487

Answers (1)

millimoose
millimoose

Reputation: 39950

Your basic approach using a factory that's injected by Spring that then exposes a method to create Example instances is how I'd do this, so it's essentially correct. If you want to have Spring take care of doing this transparently, using its modern features, you can use a @Configuration class in combination with lookup method injection to create instances of Example on-demand from singleton-scoped beans.


First, the configuration class:

@Configuration
public class DemoConfiguration {
    @Autowired IFooBean fooBean;
    @Autowired IBarBean barBean;

    @Bean()
    @Scope("prototype")
    Example newExample(String name) {
        return new Example(fooBean, barBean, name);
    }
}

There should be nothing too surprising here, except maybe the name parameter to newExample. You can autowire the dependencies the container can satisfy (fooBean and barBean) as I did above, but since instances of configuration classes are created like any other bean by Spring, you can also use any other mechanism: inject an ObjectFactory or ObjectProvider into the configuration, have it implement ApplicationContextAware, or even use lookup method injection for them. This would be useful if you need to avoid fooBean and barBean being initialized early as they would if they're autowired into a configuration bean.

Don't forget setting the scope of the factory method to "prototype", otherwise Spring will just return the first bean you create using even if you pass in a different value for name.


The implementation of Example itself is analogous to the one in your question:

public class Example {
    IFooBean fooBean;
    IBarBean barBean;
    String name;

    public Example(IFooBean fooBean, IBarBean barBean, String name) {
        System.out.printf("%s(fooBean=%s, barBean=%s, name=%s)\n", this, fooBean, barBean, name);
        this.fooBean = fooBean;
        this.barBean = barBean;
        this.name = name;
    }
}

Then, at the point where you actually need the instances of Example, you use @Lookup to inject the factory method:

public interface IUsesExample {
    void doThing();
}

@Component
public class UsesExample implements IUsesExample {
    @Lookup
    protected Example getExample(String name) {return null;};

    public void doThing() {
        System.out.printf("%s.doThing(getExample() = %s)\n", this, getExample("aaa"));
        System.out.printf("%s.doThing(getExample() = %s)\n", this, getExample("bbb"));
    }
}

To use @Component and scanning, this has to be a concrete class, which means we need a dummy implementation of getExample(); Spring will use CGLIB to replace it with a call to the factory method defined in DemoConfiguration above. Spring will correctly pass parameters from the lookup method to the factory method.

For test purposes I just call getExample() twice with different values for name to demonstrate we're getting a different instance with the right things injected into it each time.


Testing this with the following small Spring Boot app:

@SpringBootApplication
public class DemoApplication {
    @Autowired IUsesExample usesExample;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @PostConstruct
    void run() {
        usesExample.doThing();
    }
}

gives the following output:

com.example.demo.FooBean@fd46303
com.example.demo.BarBean@6a62689d
com.example.demo.Example@66629f63(fooBean=com.example.demo.FooBean@fd46303, barBean=com.example.demo.BarBean@6a62689d, name=aaa)
com.example.demo.UsesExample$$EnhancerBySpringCGLIB$$68b994e8@6c345c5f.doThing(getExample() = com.example.demo.Example@66629f63)
com.example.demo.Example@6b5966e1(fooBean=com.example.demo.FooBean@fd46303, barBean=com.example.demo.BarBean@6a62689d, name=bbb)
com.example.demo.UsesExample$$EnhancerBySpringCGLIB$$68b994e8@6c345c5f.doThing(getExample() = com.example.demo.Example@6b5966e1)

That is:

  • a FooBean gets created
  • a BarBean gets created
  • an Example gets created with the above two beans and name
  • this Example is returned to UseExample
  • an different Example gets created, with the same FooBean and BarBean, and name set to "bbb" this time.

I'm assuming you're familiar with how to set up java-based configuration and component scanning and all the other plumbing the above examples rely on. I used Spring Boot to get the whole shebang in a simple way.

If you're creating Examples from other prototype-scoped beans there might be a way to pass in the value for the runtime-only dependency through the scope but I have no idea where to even begin answering how to do that, especially without knowing the actual scopes of the beans and how they relate to one another. Either way the above solution seems more straightforward and easy to understand.

Upvotes: 4

Related Questions