Mastering Spring Bean Scopes: Custom Scope Creation and Prototype Injection
This article explains how Spring's @Scope annotation determines bean lifecycles, demonstrates creating and registering a custom scope, and shows how to correctly inject prototype-scoped beans into singleton components using scoped proxies.
1. Bean Scope in Spring
In Spring 5.3.23, the @Scope annotation defines a bean's lifecycle. By default, beans are singletons ( ConfigurableBeanFactory.SCOPE_SINGLETON). When a bean is created, Spring checks the bean definition's scope and creates the instance accordingly.
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType,
@Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
String beanName = transformedBeanName(name);
Object bean;
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// use existing singleton
} else {
try {
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// guarantee initialization of dependent beans
// create bean instance based on scope
if (mbd.isSingleton()) {
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()) {
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;
}
}
return (T) bean;
}If the bean's scope is neither singleton nor prototype, Spring looks up the corresponding Scope implementation from the internal scopes map. If the custom scope is not registered, an exception is thrown.
2. Defining a Custom Scope
To create a custom scope, implement the org.springframework.beans.factory.config.Scope interface.
public class CustomScope implements Scope {
private Object target;
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
return target != null ? target : objectFactory.getObject();
}
@Override
public Object remove(String name) {
target = null;
return "success";
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
// no-op
}
@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 {
// no additional processing needed
}
}Use the custom scope on a bean:
@Component
@Scope("custom")
public class ApplyScopeBean {
// bean logic
}3. Prototype Injection into a Singleton
When a singleton bean depends on a prototype bean, you must enable a scoped proxy so that a new instance is obtained for each injection point.
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ApplyScopeBean {
// prototype bean logic
}Alternatively, you can request a new instance manually via BeanFactory#getBean() each time you need it.
4. Example Controller Using Custom and Prototype Scopes
@RestController
@RequestMapping("/refresh")
@Scope(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");
}
}In this controller, the bean ApplyScopeBean is defined with a custom scope, and the controller itself is prototype‑scoped to demonstrate the lifecycle behavior. Deleting the bean via the remove endpoint forces Spring to create a new instance on the next request.
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.
