Keywords: Spring Framework | Prototype Scope | Dependency Injection | Bean Scopes | ScopedProxy
Abstract: This article provides an in-depth analysis of the actual behavior of @Scope("prototype") annotation in Spring Framework dependency injection scenarios, exploring the root causes of prototype beans being incorrectly reused in singleton controllers. By comparing traditional ApplicationContext retrieval and ScopedProxy approaches, it details the correct usage patterns and implementation principles of prototype scope, helping developers avoid common Spring bean scope misuse issues.
Problem Background and Phenomenon Analysis
In Spring Framework development, developers often encounter issues where prototype-scoped beans fail to create new instances as expected during dependency injection. Taking the user-provided code as an example:
@Component
@Scope("prototype")
public class LoginAction {
private int counter;
public LoginAction(){
System.out.println(" counter is:" + counter);
}
public String getStr() {
return " counter is:"+(++counter);
}
}
The LoginAction class is marked with prototype scope, theoretically creating a new instance each time it's retrieved. However, the usage in the controller:
@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;
}
}
Results in incrementing counts with each request, indicating that the same LoginAction instance is being reused, contradicting the design intent of prototype scope.
Fundamental Mechanism of Prototype Scope
The @Scope("prototype") annotation in Spring Framework indeed means that a new instance is created each time the bean is requested from the container. The key is understanding what "request" specifically means:
- When explicitly requested via
ApplicationContext.getBean()method, each call returns a new instance - In dependency injection scenarios, the injection operation itself occurs only once, so the prototype bean instance injected into a singleton bean becomes fixed
This is the core issue: HomeController itself is a singleton, and Spring injects LoginAction into the controller during initialization via @Autowired. This injection operation executes only once, and all subsequent requests use the same LoginAction instance.
Solution 1: Explicit Retrieval via ApplicationContext
The most direct solution is to make the controller aware of ApplicationContext and explicitly retrieve the prototype bean when needed:
@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 approach ensures that each call to getLoginAction() method retrieves a new LoginAction instance from the Spring container, aligning with the expected behavior of prototype scope.
Solution 2: ScopedProxy Pattern
Spring 2.5 introduced a more elegant solution—using scope proxies:
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class LoginAction {
// Class implementation remains unchanged
}
The controller code can maintain the original dependency injection approach:
@Controller
public class HomeController {
@Autowired
private LoginAction loginAction; // Actually injects a proxy object
@RequestMapping(value="/view", method=RequestMethod.GET)
public ModelAndView display(HttpServletRequest req){
ModelAndView mav = new ModelAndView("home");
mav.addObject("loginAction", loginAction);
return mav;
}
}
ScopedProxyMode.TARGET_CLASS indicates using CGLIB to create subclass proxies, where each method call delegates to a new target instance. This maintains code simplicity while achieving true prototype behavior.
Comparative Analysis of Both Solutions
ApplicationContext approach advantages include clear logic and direct control over bean retrieval timing; disadvantages include explicit dependency on ApplicationContext in code, increasing coupling.
ScopedProxy approach advantages include concise code that aligns with dependency injection design principles; disadvantages include introducing a proxy layer that may add complexity during debugging and requires understanding of proxy mechanism workings.
Practical Application Recommendations
When choosing a solution, consider the following factors:
- If prototype bean usage is frequent and code cleanliness is desired, ScopedProxy approach is recommended
- If extreme performance requirements exist or precise control over bean creation timing is needed, ApplicationContext approach may be preferable
- In team development, standardize on one approach to avoid confusion
Regardless of the chosen approach, understanding Spring bean scope working principles is crucial. The design intent of prototype scope is to provide flexible instance management for objects that require frequent creation and destruction. Proper use of this feature can significantly enhance application flexibility and performance.