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.
<code>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;
}</code>The code shows checks for singleton and prototype scopes; if neither matches, Spring looks up a custom Scope from the internal
scopesmap. If the custom scope is not registered, an exception is thrown; otherwise Spring invokes
Scope#getto obtain the bean instance.
2. Defining a Custom Scope
Implement the
Scopeinterface:
<code>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;
}
}
</code>Register the custom scope with the bean factory, typically via a
BeanDefinitionRegistryPostProcessor:
<code>@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 {
}
}
</code>Use the custom scope on a bean:
<code>@Component
@Scope("custom")
public class ApplyScopeBean {
}
</code>Example controller demonstrating both a prototype‑scoped bean and the custom scope:
<code>@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");
}
}
</code>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:
<code>@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ApplyScopeBean {
}
</code>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.
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.