1

How do you create a prototype-scoped @Bean with runtime arguments? With getBean(String name, Object... args)? My question is a consequence of this question.

Why is this approach not used or mentioned in the Spring IoC documentation?

Is this a normal approach? Is there a more correct approach for create a prototype @Bean with runtime arguments?

If it is not normal approach, so could you explain why? Pay attention, what i need set my arguments through constructor, not through setters.

@Autowired
private ApplicationContext appCtx;

public void onRequest(Request request) {
    //request is already validated
    String name = request.getParameter("name");
    Thing thing = appCtx.getBean(Thing.class, name);

    //System.out.println(thing.getName()); //prints name
}

-

public class Thing {

    private final String name;

    @Autowired
    private SomeComponent someComponent;

    @Autowired
    private AnotherComponent anotherComponent;

    public Thing(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}
  • Really, the answer's [here](https://stackoverflow.com/questions/812415/why-is-springs-applicationcontext-getbean-considered-bad/812573?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa). Basically, you're still disobeying DI if you do this with prototype beans – Dovmo May 25 '18 at 21:27
  • @Dovmo Looked this answer. But I don't understand how else can I create objects that come from a client? That have different data all the time. – test5436223525 May 25 '18 at 21:34
  • In terms of constructor injection, you don't have many other options, afaik. I'm only aware of a method like I suggested below – Dovmo May 25 '18 at 21:37
  • @Dovmo Do you mean we should will have to refuse to implement your data inject to constructor if we use Dependency Injection? – test5436223525 May 25 '18 at 21:39
  • 1
    As far as I know, yes. Since you need to use the constructor, it looks like the only option for you is to use `getBean` – Dovmo May 25 '18 at 22:10

3 Answers3

0

In terms of constructor injection, no. However, you can give Thing an init method and use an ObjectFactory:

@Autowired
private ObjectFactory<Thing> thingFactory;

public void onRequest(Request request) {
    //request is already validated
    Thing thing = thingFactory.getObject();
    thing.init("name");

    //System.out.println(thing.getName()); //prints name
}

With thing being:

@Component
@Scope("prototype")
public class Thing {

    private String name;

    @Autowired
    private SomeComponent someComponent;

    @Autowired
    private AnotherComponent anotherComponent;

    public init(String name) {
        this.name = name;
    }

}

Unfortunately, the name can't be final since it's not via the constructor. Would love to see if there are better ways to do this with constructor injection.

Dovmo
  • 8,121
  • 3
  • 30
  • 44
0

It is possible with the help of a bean factory:

@Configuration
public class ThingProvider {

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Thing create(String name) {
        return new Thing(name);
    }

}

Usage:

@Component
public class SomeBean {

    @Autowired
    private ThingProvider thingProvider;

    public void onRequest(Request request) {

        String name = request.getParameter("name");
        Thing thing = myProvider.create(name);
    }

}

What is often brought forward as an argument against appCtx.getBean(Thing.class, name) is that it requires to hard-wire Spring specific classes. Plus, there is no compile time check in case the Thing constructor changes.

Markus Pscheidt
  • 6,853
  • 5
  • 55
  • 76
0

In that case, your Thing class might mix constructor dependencies and runtime arguments, something like this:

public class Thing {

    private final SomeComponent someComponent;
    private final AnotherComponent anotherComponent;

    private final String name;

    @Autowired
    public Thing(SomeComponent someComponent,
                 AnotherComponent anotherComponent,
                 String name) {
        this.someComponent = someComponent;
        this.anotherComponent = anotherComponent;
        this.name = name;
    }

    public String getName() {
        return this.name;
    } 
}

Then you can wrap them in configuration:

@Configuration
public class BeanConfig {

    @Autowired
    private SomeComponent someComponent;

    @Autowired
    private final AnotherComponent anotherComponent;

    @Bean
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public Thing createThing(String name) {
        return new Thing(someComponent, anotherComponent, name);
    }
}

Usage:

@Autowired
private BeanFactory beanFactory;

public void onRequest(Request request) {
    String name = request.getParameter("name");
    Thing thing = beanFactory.getBean(Thing.class, name);
}

It supposed to work, not sure if this is a good practice though.

István Békési
  • 993
  • 4
  • 16
  • 27