@Scope("prototype") bean scope not creating new bean
Asked Answered
T

12

162

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");
    }
}
Tool answered 1/10, 2011 at 17:53 Comment(1)
I wish I could double upvote you for implementing the correct answer in your code for others to see the actual differenceVergne
L
193

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.

Leverhulme answered 1/10, 2011 at 18:0 Comment(3)
I made the controller ApplicationContextAware and did getBean and I'm getting the fresh bean every time. Thanks guys!!!Tool
How does this work if the bean would have had request scope instead of prototype scope. Would you still need to retrieve the bean with context.getBean(..)?Concerto
Or use a scoped proxy, i.e. @Scope(value="prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)Carlisle
R
43

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.

Rossanarosse answered 30/3, 2018 at 13:57 Comment(1)
So we dont have it now in spring 5 ?Teerell
V
19

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

Veilleux answered 1/10, 2011 at 18:1 Comment(0)
C
12

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

Community answered 13/12, 2012 at 17:29 Comment(0)
C
6

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;  
Chetnik answered 27/7, 2017 at 18:51 Comment(1)
I would suggest injecting springs ObjectFactory which serves the same purpose as supplier, but can be defined as a normal @Bean by which I mean no need to return a lambda.Finality
M
3

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.

Milliary answered 1/10, 2011 at 19:47 Comment(4)
There's already Spring-specific annotations, though; doesn't seem like that's much of a concern.Veilleux
@Dave, Good point. There are alternatives for some of the DI stuff (JSR 311), but it may be harder to rid yourself of everything Spring dependent in this example. I suppose I am really just advocating the factory-method here...Milliary
+1 for injecting a singleton LoginActionFactory into the Controller, but factory-method doesn't seem like it would solve the issue as it just creates another spring bean via the factory. Injecting that bean into the singleton Controller won't address the problem.Agent
Good point Brad, I will remove that suggestion from my answer.Milliary
R
3

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

Recreant answered 19/3, 2015 at 14:36 Comment(0)
A
2

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")
Adellaadelle answered 8/12, 2018 at 17:2 Comment(0)
I
1

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

Inconvenient answered 19/5, 2020 at 6:45 Comment(0)
B
0

class with @Configuration

@Bean
@RequestScope //composed annotation for @Scope(value=...proxyMode=...)
public LoginAction requestScopedBean() {
    return new LoginAction ();
}
@Controller
public class HomeController {

    @Resource(name = "requestScopedBean")
    private LoginAction loginAction;

    //...your code here

Bereave answered 21/6, 2022 at 11:54 Comment(0)
S
-1

@Component

@Scope(value="prototype")

public class TennisCoach implements Coach {

// some code

}

Simplicity answered 28/6, 2019 at 6:48 Comment(0)
T
-12

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

like this:

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

}
Tart answered 14/4, 2012 at 14:40 Comment(1)
why do u think controller also needs to be prototype?Quittance

© 2022 - 2024 — McMap. All rights reserved.