144
votes

I want to use a annotated prototype bean in my controller. But spring is creating a singleton bean instead. Here is the code for that:

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

  private int counter;

  public LoginAction(){
    System.out.println(" counter is:" + counter);
  }
  public String getStr() {
    return " counter is:"+(++counter);
  }
}

Controller code:

@Controller
public class HomeController {
    @Autowired
    private LoginAction loginAction;

    @RequestMapping(value="/view", method=RequestMethod.GET)
    public ModelAndView display(HttpServletRequest req){
        ModelAndView mav = new ModelAndView("home");
        mav.addObject("loginAction", loginAction);
        return mav;
    }

    public void setLoginAction(LoginAction loginAction) {
        this.loginAction = loginAction;
    }

    public LoginAction getLoginAction() {
        return loginAction;
    }
    }

Velocity template:

 LoginAction counter: ${loginAction.str}

Spring config.xml has component scanning enabled:

    <context:annotation-config />
    <context:component-scan base-package="com.springheat" />
    <mvc:annotation-driven />

I'm getting an incremented count each time. Can't figure out where am I going wrong!

Update

As suggested by @gkamal, I made HomeController webApplicationContext-aware and it solved the problem.

updated code:

@Controller
public class HomeController {

    @Autowired
    private WebApplicationContext context;

    @RequestMapping(value="/view", method=RequestMethod.GET)
    public ModelAndView display(HttpServletRequest req){
        ModelAndView mav = new ModelAndView("home");
        mav.addObject("loginAction", getLoginAction());
        return mav;
    }

    public LoginAction getLoginAction() {
        return (LoginAction) context.getBean("loginAction");
    }
}
12
I wish I could double upvote you for implementing the correct answer in your code for others to see the actual difference - Ali Nem

12 Answers

173
votes

Scope prototype means that every time you ask spring (getBean or dependency injection) for an instance it will create a new instance and give a reference to that.

In your example a new instance of LoginAction is created and injected into your HomeController . If you have another controller into which you inject LoginAction you will get a different instance.

If you want a different instance for each call - then you need to call getBean each time - injecting into a singleton bean will not achieve that.

31
votes

Since Spring 2.5 there's a very easy (and elegant) way to achieve that.

You can just change the params proxyMode and value of the @Scope annotation.

With this trick you can avoid to write extra code or to inject the ApplicationContext every time that you need a prototype inside a singleton bean.

Example:

@Service 
@Scope(value="prototype", proxyMode=ScopedProxyMode.TARGET_CLASS)  
public class LoginAction {}

With the config above LoginAction (inside HomeController) is always a prototype even though the controller is a singleton.

16
votes

Just because the bean injected into the controller is prototype-scoped doesn't mean the controller is!

11
votes

@controller is a singleton object, and if inject a prototype bean to a singleton class will make the prototype bean also as singleton unless u specify using lookup-method property which actually create a new instance of prototype bean for every call you make.

5
votes

As mentioned by nicholas.hauschild injecting Spring context is not a good idea. In your case, @Scope("request") is enough to fix it. But let say you need several instances of LoginAction in controller method. In this case, I would recommend to create the bean of Supplier (Spring 4 solution):

    @Bean
    public Supplier<LoginAction> loginActionSupplier(LoginAction loginAction){
        return () -> loginAction;
    }

Then inject it into controller:

@Controller
public class HomeController {
    @Autowired
    private  Supplier<LoginAction> loginActionSupplier;  
3
votes

Using ApplicationContextAware is tying you to Spring (which may or may not be an issue). I would recommend passing in a LoginActionFactory, which you can ask for a new instance of a LoginAction each time you need one.

3
votes

use request scope @Scope("request") to get bean for each request, or @Scope("session") to get bean for each session 'user'

2
votes

A protoype bean injected inside a singelton bean will behave like singelton untill expilictly called for creating a new instance by get bean.

context.getBean("Your Bean")
0
votes

You can create static class inside your controller like this :

    @Controller
    public class HomeController {
        @Autowired
        private LoginServiceConfiguration loginServiceConfiguration;

        @RequestMapping(value = "/view", method = RequestMethod.GET)
        public ModelAndView display(HttpServletRequest req) {
            ModelAndView mav = new ModelAndView("home");
            mav.addObject("loginAction", loginServiceConfiguration.loginAction());
            return mav;
        }


        @Configuration
        public static class LoginServiceConfiguration {

            @Bean(name = "loginActionBean")
            @Scope("prototype")
            public LoginAction loginAction() {
                return new LoginAction();
            }
        }
}
0
votes

By default, Spring beans are singletons. The problem arises when we try to wire beans of different scopes. For example, a prototype bean into a singleton. This is known as the scoped bean injection problem.

Another way to solve the problem is method injection with the @Lookup annotation.

Here is a nice article on this issue of injecting prototype beans into a singleton instance with multiple solutions.

https://www.baeldung.com/spring-inject-prototype-bean-into-singleton

-1
votes

@Component

@Scope(value="prototype")

public class TennisCoach implements Coach {

// some code

}

-11
votes

Your controller also need the @Scope("prototype") defined

like this:

@Controller
@Scope("prototype")
public class HomeController { 
 .....
 .....
 .....

}