Question

Using Spring's Java Config, I need to acquire/instantiate a prototype-scoped bean with constructor arguments that are only obtainable at runtime. Consider the following code example (simplified for brevity):

@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
}

where the Thing class is defined as follows:

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;
    }
}

Notice name is final: it can only be supplied via a constructor, and guarantees immutability. The other dependencies are implementation-specific dependencies of the Thing class, and shouldn't be known to (tightly coupled to) the request handler implementation.

This code works perfectly well with Spring XML config, for example:

<bean id="thing", class="com.whatever.Thing" scope="prototype">
    <!-- other post-instantiation properties omitted -->
</bean>

How do I achieve the same thing with Java config? The following does not work using Spring 3.x:

@Bean
@Scope("prototype")
public Thing thing(String name) {
    return new Thing(name);
}

Now, I could create a Factory, e.g.:

public interface ThingFactory {
    public Thing createThing(String name);
}

But that defeats the entire point of using Spring to replace the ServiceLocator and Factory design pattern, which would be ideal for this use case.

If Spring Java Config could do this, I would be able to avoid:

  • defining a Factory interface
  • defining a Factory implementation
  • writing tests for the Factory implementation

That's a ton of work (relatively speaking) for something so trivial that Spring already supports via XML config.

Was it helpful?

Solution

In a @Configuration class, a @Bean method like so

@Bean
@Scope("prototype")
public Thing thing(String name) {
    return new Thing(name);
}

is used to register a bean definition and provide the factory for creating the bean. The bean that it defines is only instantiated upon request using arguments that are determined either directly or through scanning that ApplicationContext.

In the case of a prototype bean, a new object is created every time and therefore the corresponding @Bean method is also executed.

You can retrieve a bean from the ApplicationContext through its BeanFactory#getBean(String name, Object... args) method which states

Allows for specifying explicit constructor arguments / factory method arguments, overriding the specified default arguments (if any) in the bean definition.

Parameters:

args arguments to use if creating a prototype using explicit arguments to a static factory method. It is invalid to use a non-null args value in any other case.

In other words, for this prototype scoped bean, you are providing the arguments that will be used, not in the constructor of the bean class, but in the @Bean method invocation. (This method has very weak type guarantees since it uses a name lookup for the bean.)

Alternatively, you can use the typed BeanFactory#getBean(Class requiredType, Object... args) method which looks up the bean by type.

This is at least true for Spring versions 4+.

Note that, if you don't want to start with the ApplicationContext or BeanFactory for your bean retrieval, you can inject an ObjectProvider (since Spring 4.3).

A variant of ObjectFactory designed specifically for injection points, allowing for programmatic optionality and lenient not-unique handling.

and use its getObject(Object... args) method

Return an instance (possibly shared or independent) of the object managed by this factory.

Allows for specifying explicit construction arguments, along the lines of BeanFactory.getBean(String, Object).

For example,

@Autowired
private ObjectProvider<Thing> things;

[...]
Thing newThing = things.getObject(name);
[...]

OTHER TIPS

With Spring > 4.0 and Java 8 you can do this more type-safely:

@Configuration    
public class ServiceConfig {

    @Bean
    public Function<String, Thing> thingFactory() {
        return name -> thing(name); // or this::thing
    } 

    @Bean
    @Scope(value = "prototype")
    public Thing thing(String name) {
       return new Thing(name);
    }

}

Usage:

@Autowired
private Function<String, Thing> thingFactory;

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

    // ...
}

So now you can get your bean at runtime. This is a factory pattern of course, but you can save some time on writing specific class like ThingFactory (however you will have to write custom @FunctionalInterface to pass more than two parameters).

Since Spring 4.3, there is new way to do it, which was sewed for that issue.

ObjectProvider - It enables you just to add it as a dependency to your "argumented" Prototype scoped bean and to instantiate it using the argument.

Here is a simple example of how to use it:

@Configuration
public class MyConf {
    @Bean
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public MyPrototype createPrototype(String arg) {
        return new MyPrototype(arg);
    }
}

public class MyPrototype {
    private String arg;

    public MyPrototype(String arg) {
        this.arg = arg;
    }

    public void action() {
        System.out.println(arg);
    }
}


@Component
public class UsingMyPrototype {
    private ObjectProvider<MyPrototype> myPrototypeProvider;

    @Autowired
    public UsingMyPrototype(ObjectProvider<MyPrototype> myPrototypeProvider) {
        this.myPrototypeProvider = myPrototypeProvider;
    }

    public void usePrototype() {
        final MyPrototype myPrototype = myPrototypeProvider.getObject("hello");
        myPrototype.action();
    }
}

This will of course print hello string when calling usePrototype.

UPDATED per comment

First, I'm not sure why you say "this does not work" for something that works just fine in Spring 3.x. I suspect something must be wrong in your configuration somewhere.

This works:

-- Config File:

@Configuration
public class ServiceConfig {
    // only here to demo execution order
    private int count = 1;

    @Bean
    @Scope(value = "prototype")
    public TransferService myFirstService(String param) {
       System.out.println("value of count:" + count++);
       return new TransferServiceImpl(aSingletonBean(), param);
    }

    @Bean
    public AccountRepository aSingletonBean() {
        System.out.println("value of count:" + count++);
        return new InMemoryAccountRepository();
    }
}

-- Test File to execute:

@Test
public void prototypeTest() {
    // create the spring container using the ServiceConfig @Configuration class
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ServiceConfig.class);
    Object singleton = ctx.getBean("aSingletonBean");
    System.out.println(singleton.toString());
    singleton = ctx.getBean("aSingletonBean");
    System.out.println(singleton.toString());
    TransferService transferService = ctx.getBean("myFirstService", "simulated Dynamic Parameter One");
    System.out.println(transferService.toString());
    transferService = ctx.getBean("myFirstService", "simulated Dynamic Parameter Two");
    System.out.println(transferService.toString());
}

Using Spring 3.2.8 and Java 7, gives this output:

value of count:1
com.spring3demo.account.repository.InMemoryAccountRepository@4da8692d
com.spring3demo.account.repository.InMemoryAccountRepository@4da8692d
value of count:2
Using name value of: simulated Dynamic Parameter One
com.spring3demo.account.service.TransferServiceImpl@634d6f2c
value of count:3
Using name value of: simulated Dynamic Parameter Two
com.spring3demo.account.service.TransferServiceImpl@70bde4a2

So the 'Singleton' Bean is requested twice. However as we would expect, Spring only creates it once. The second time it sees that it has that bean and just returns the existing object. The constructor (@Bean method) is not invoked a second time. In deference to this, when the 'Prototype' Bean is requested from the same context object twice we see that the reference changes in the output AND that the constructor (@Bean method) IS invoked twice.

So then the question is how to inject a singleton into a prototype. The configuration class above shows how to do that too! You should pass all such references into the constructor. This will allow the created class to be a pure POJO as well as making the contained reference objects immutable as they should be. So the transfer service might look something like:

public class TransferServiceImpl implements TransferService {

    private final String name;

    private final AccountRepository accountRepository;

    public TransferServiceImpl(AccountRepository accountRepository, String name) {
        this.name = name;
        // system out here is only because this is a dumb test usage
        System.out.println("Using name value of: " + this.name);

        this.accountRepository = accountRepository;
    }
    ....
}

If you write Unit Tests you will be ever so happy you created the classes this without all the @Autowired. If you do need autowired components keep those local to the java config files.

This will call the method below in the BeanFactory. Note in the description how this is intended for your exact use case.

/**
 * Return an instance, which may be shared or independent, of the specified bean.
 * <p>Allows for specifying explicit constructor arguments / factory method arguments,
 * overriding the specified default arguments (if any) in the bean definition.
 * @param name the name of the bean to retrieve
 * @param args arguments to use if creating a prototype using explicit arguments to a
 * static factory method. It is invalid to use a non-null args value in any other case.
 * @return an instance of the bean
 * @throws NoSuchBeanDefinitionException if there is no such bean definition
 * @throws BeanDefinitionStoreException if arguments have been given but
 * the affected bean isn't a prototype
 * @throws BeansException if the bean could not be created
 * @since 2.5
 */
Object getBean(String name, Object... args) throws BeansException;

You can achieve a similar effect just by using an inner class:

@Component
class ThingFactory {
    private final SomeBean someBean;

    ThingFactory(SomeBean someBean) {
        this.someBean = someBean;
    }

    Thing getInstance(String name) {
        return new Thing(name);
    }

    class Thing {
        private final String name;

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

        void foo() {
            System.out.format("My name is %s and I can " +
                    "access bean from outer class %s", name, someBean);
        }
    }
}

If you need to create a qualified bean you can do it this way:

@Configuration
public class ThingConfiguration {

   @Bean
   @Scope(SCOPE_PROTOTYPE)
   public Thing simpleThing(String name) {
       return new Thing(name);
   }

   @Bean
   @Scope(SCOPE_PROTOTYPE)
   public Thing specialThing(String name) {
       Thing thing = new Thing(name);
       // some special configuration
       return thing;
   }

}

// Usage 

@Autowired
private ApplicationContext context;

AutowireCapableBeanFactory beanFactory = context.getAutowireCapableBeanFactory();
((DefaultListableBeanFactory) beanFactory).getBean("specialThing", Thing.class, "name");

Late answer with a slightly different approach. That is a follow up of this recent question that refers this question itself.

Yes, as that was said you can declare the prototype bean that accepts a parameter in a @Configuration class that allows to create a new bean at each injection.
That will make this @Configuration class a factory and to not give this factory too much responsibilities, this should not include other beans.

@Configuration    
public class ServiceFactory {

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

}

But you can also inject that configuration bean to create Things :

@Autowired
private ServiceFactory serviceFactory;

public void onRequest(Request request) {
    //request is already validated
    String name = request.getParameter("name");
    Thing thing = serviceFactory.thing(name); // create a new bean at each invocation
    // ...    
}

It is both type-safe and concise.

Nice solutions until now. But I want to post yet another alternative. Spring has the @Lookup annotation. From javadoc:

An annotation that indicates 'lookup' methods, to be overridden by the container to redirect them back to the BeanFactory for a getBean call.

you can declare your Thing as prototype bean:

@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Thing {

    @Autowired
    private SomeComponent someComponent;

    @Autowired
    private AnotherComponent anotherComponent;

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

then you can create instances by creating a method like createThing below in any other bean:

@Controller
public class MyController {

    @Autowired
    private ApplicationContext appCtx;

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

        //System.out.println(thing.getName()); //prints name
    }
    
    //or public. And can be put in any @Component (including @Configuration)
    @Lookup
    protected Thing createThing(String name) {
        throw new UnsupportedOperationException("Method implemented by Spring.");
    }
}

in your beans xml file use the attribute scope="prototype"

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top