-1

I was new to Springboot application using the @Autowired to perform the dependency injection. We can use @Autowired directly by initiate that class object for class that has none or default parameterless constructor. But what if a class has some parameter in its constructor, and I would like to initiate it during runtime conditionally and automatically, is it possible to do that?

For example

@Component
public class SomeContext {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

@Component
public class SomeBuilder {
    private final SomeContext ctx;

    @Autowired
    public SomeBuilder(SomeContext ctx) {
        this.ctx = ctx;
    }
    public void test() {
        System.out.println("ctx name: " + ctx.getName());
    }
}

@Service
public class SomeService {
    @Autowired
    SomeBuilder someBuilder;

    public void run(SomeContext ctx) {
        if (ctx != null) {
            // I want someBuilder  to be initiated here in someway with my input ctx 
            // but NOT doing through new with constructor like below
            // someBuilder = new SomeBuilder(ctx);
            someBuilder.test();   // ctx name: null, I would expect to see "ctx name: someUser", while ctx was injected into someBuilder in any possible way
        }
    }
}

@RestController
public class HelloWorldController 
{
    @Autowired
    SomeService someService;

    @RequestMapping("/")
    public String hello() {
        SomeContext someContext = new SomeContext();
        someContext.setName("someUser");
        someService.run(someContext);

        return "Hello springboot";
    }
}
Drex
  • 3,346
  • 9
  • 33
  • 58
  • *"is it possible to do that?" Why don't you **try**, and see for yourself that it is indeed possible? – Andreas Aug 03 '21 at 02:53
  • @Andreas I wouldn't ask if that works... Please note the line I commented out `someBuilder = new SomeBuilder(ctx);` is **NOT** what I want to keep but to show an idea of expecting an automatic way to initiate `someBuilder` dynamically – Drex Aug 03 '21 at 03:42

1 Answers1

1

I'm not sure I've got your question right, but from the code it looks like you really want to create a new instance of SomeBuilder every time you call the run method of SomeService.

If so, I think the first thing to understand is that in general the injection magic happens only if the class is managed by Spring by itself. Read, if spring creates the object of the class - it will inject stuff into it otherwise you're on your own here.

The next thing to understand is that, if you have a object of class SomeBuilder managed by spring and you want to inject SomeContext into it, this SomeContext instance has to be managed by spring as well.

Bottom line, spring can deal only with objects that it manages. These objects are stored in a 'global registry' of all the objects called ApplicationContext in spring.

Now Spring has a concept of prototype scope vs. singleton scope. By Default all the beans are singletons, however you can easily alter this behavior. This has two interesting consequences:

  1. You Can create prototype objects being injected into the singleton upon each invocatino (of method run in your case, so the SomeBuilder can and I believe should be a prototype)
  2. Prototype objects are not stored in the application contexts so the capabilities of injecting stuff in there during the runtime are rather limited

With all this in mind:

If you want to create SomeContext like you do in the controller, its not managed by spring, so you can't use Injection of spring as is into the builder. The builder is a singleton, so if you inject it with a regular @Autowire into another singleton (SomeService in your case), you'll have to deal with the same instance of the builder object - think about concurrent access to the method run of SomeService and you'll understand that this solution is not really a good one.

So these are the "inaccuracies" in the presented solution.

Now, in terms of solution, you can:

Option 1 Don't manage builders in Spring, not everything should be managed by spring, in this case you'll keep your code as it is now.

Option 2

  • and this is a the solution, although pretty advanced one:

Use Java Configuration to create prototype beans in runtime with an ability to inject parameters into the bean.

Here is an example:

// Note, I've removed all the annotations, I'll use java configurations for that, read below...
public class SomeBuilder {
    private final SomeContext ctx;

    public SomeBuilder(SomeContext ctx) {
        this.ctx = ctx;
    }
    public void test() {
        System.out.println("ctx name: " + ctx.getName());
    }
}

Now the class SomeService will also slightly change:

public class SomeService {
   
    private Function<SomeContext, SomeBuilder> builderFactory;

    public void run(SomeContext ctx) {

        SomeBuilder someBuilder = builderFactory.apply(ctx);
        someBuilder.test();  

    }
}

And now you should "glue" it to spring in an advanced way with Java Configurations:

@Configuration
public class MyConfiguration {
    @Bean
    public Function<SomeContext, SomeBuilder> builderFactory() {
        return ctx -> someBuilder(ctx);
    } 

    @Bean
    @Scope(value = "prototype")
    public SomeBuilder someBuilder(SomeContext ctx) {
       return new SomeBuilder(ctx);
    }
    
    @Bean
    public SomeService someService() {
        return new SomeService(builderFactory());
    }    
}

For more details with a really similar example, see this tutorial

Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
  • thank you a lot for the explanation! I also referred to [this post option 3 and option 4](https://stackoverflow.com/questions/6739566/is-there-a-way-to-autowire-a-bean-that-requires-constructor-arguments) but I don't think that's what I want, as you suggest, **not everything should be managed by spring**, I guess that's what I'd like to verify. To dig more, the option 2 you showed here, what's the **benefit** of that vs option 1, like _performance_, _memory usage_ or _testing/mocking convenience_? Yes it looks like a _Spring Style_, but all it does is same as _new SomeBuilder(ctx)_, right? – Drex Aug 03 '21 at 04:23
  • Sometimes it makes sense to manage builders in spring. For example in cases where the builder contains other objects managed by spring, so spring can inject them into every builder instance it creates. As for the tests, in general, if you're using constructor injection - the code is mostly ready to be unit tested, but its a general claim way beyond the scope of the original question. Feel free to post another question about tests and add a link here so that I could monitor it :) – Mark Bramnik Aug 03 '21 at 16:19