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");
}
}
This question is related to
spring
spring-mvc
Your controller also need the @Scope("prototype")
defined
like this:
@Controller
@Scope("prototype")
public class HomeController {
.....
.....
.....
}
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.
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();
}
}
}
@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.
use request scope @Scope("request")
to get bean for each request, or @Scope("session")
to get bean for each session 'user'
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;
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.
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")
Just because the bean injected into the controller is prototype-scoped doesn't mean the controller is!
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
@Component
@Scope(value="prototype")
public class TennisCoach implements Coach {
// some code
}
Source: Stackoverflow.com