Mastering Spring @Scope: Custom Scopes, Prototypes, and Injection Techniques
This guide explains how Spring's @Scope annotation determines bean lifecycles, shows the underlying source code handling singleton and prototype scopes, demonstrates creating and registering a custom scope, and provides practical examples of using custom and prototype-scoped beans with proper proxy configuration in Spring Boot.
Environment: Spring 5.3.3
1. Role of Scope
Using the @Scope annotation you can specify a bean's scope; by default beans are singletons (ConfigurableBeanFactory.SCOPE_SINGLETON = "singleton").
When a bean instance is created, Spring checks the BeanDefinition's scope to decide how to instantiate it. The relevant source code is shown below.
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// other code
} else {
// other code
try {
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
// other code
// Create bean instance.
// 根据BeanDefinition中定义的Scope创建实例
// 判断如果是单例
if (mbd.isSingleton()) {
// 如果是单例Bean会将Bean保存到缓存中singletonObjects
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 判断如果是原型(多例)
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean " + beanName);
}
Scope scope = this.scopes.get(scopeName);
// 当集合中也不存在时抛出异常
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
} catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
} catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// other code
return (T) bean;
}The code shows checks for singleton and prototype scopes; if neither matches, Spring looks up a custom Scope from the internal scopes map. If the custom scope is not registered, an exception is thrown; otherwise Spring invokes Scope#get to obtain the bean instance.
2. Defining a Custom Scope
Implement the Scope interface:
public class CustomScope implements Scope {
private Object target;
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
return target != null ? target : objectFactory.getObject();
}
// If this method is called, subsequent injections of @Scope("custom") will invoke objectFactory.getObject().
@Override
public Object remove(String name) {
target = null;
return "success";
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return null;
}
}Register the custom scope with the bean factory, typically via a BeanDefinitionRegistryPostProcessor:
@Component
public class CustomScopeRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope("custom", new CustomScope());
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
}
}Use the custom scope on a bean:
@Component
@Scope("custom")
public class ApplyScopeBean {
}Example controller demonstrating both a prototype‑scoped bean and the custom scope:
@RestController
@RequestMapping("/refresh")
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class RefreshController implements ApplicationContextAware {
@Resource
private ApplyScopeBean scopeBean;
@Resource
private CustomScope customScope;
@GetMapping("/custom")
public String custom() {
return scopeBean.getCustom();
}
@GetMapping("/remove")
public Object remove() {
return customScope.remove("applyScopeBean");
}
}Setting the controller itself as prototype allows observing the effect of creating new instances after each request; alternating calls to the endpoints will create fresh beans when the previous ones are removed.
3. Injecting Prototype Beans into Singletons
When a bean is declared with @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) and needs to be injected into a singleton, you must enable proxy mode so that each method call obtains a fresh instance:
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ApplyScopeBean {
}Without the proxy, the singleton will hold a single instance created at injection time. Alternatively, you can request a new instance directly from BeanFactory#getBean() each time.
End of tutorial.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
