Spring Prototype Bean Injection: ServiceLocator vs ObjectFactoryCreating
This article explains how to define and inject prototype-scoped beans in Spring using @Scope, ServiceLocatorFactoryBean, and ObjectFactoryCreatingFactoryBean, demonstrates configuration via annotations and XML, shows code examples, compares the two FactoryBean approaches, and includes tips for bean name mapping and custom exceptions.
Environment: Spring 6.1.2
1. Introduction
In Spring the default bean scope is singleton. You can change the scope with the @Scope annotation or the scope attribute in XML.
Annotation based
<code>@Scope("prototype")
public class Person {}
</code>XML based
<code><bean class="com.pack.Person" scope="prototype"></bean>
</code>Defining beans with different scopes is simple, but injecting beans of different scopes correctly—especially injecting prototype beans into singleton beans—requires special techniques.
Two useful classes
ServiceLocatorFactoryBean
ObjectFactoryCreatingFactoryBean
These classes provide three main benefits:
Injection of beans with different scopes
Decoupling from Spring-specific APIs
More flexible and controllable bean access
2. Practical examples
2.1 Define a prototype bean
<code>@Component
@Scope("prototype")
public class PersonService {}
</code>2.2 ServiceLocatorFactoryBean
Define a factory interface:
<code>public interface ServiceFactory {
PersonService getService();
}
</code>Configure the ServiceLocatorFactoryBean:
<code>@Bean
public ServiceLocatorFactoryBean serviceFactory() {
ServiceLocatorFactoryBean factory = new ServiceLocatorFactoryBean();
factory.setServiceLocatorInterface(ServiceFactory.class);
return factory;
}
</code>Use it in a controller:
<code>@Component
public class PersonController {
@Resource
private ServiceFactory serviceFactory;
public void save() {
System.out.println(serviceFactory.getService());
}
}
ApplicationContext context = ...;
PersonController pc = context.getBean(PersonController.class);
pc.save();
pc.save();
pc.save();
</code>Running the test prints a different instance each time, confirming prototype injection.
Bean name mapping and custom exception
You can define methods with a bean name parameter to select a specific bean when multiple beans of the same type exist.
<code>public interface ServiceFactory {
PersonService getService();
PersonService getService(String beanName);
}
</code>Configure name mappings:
<code>@Bean
ServiceLocatorFactoryBean serviceFactory() {
ServiceLocatorFactoryBean factory = new ServiceLocatorFactoryBean();
factory.setServiceLocatorInterface(ServiceFactory.class);
Properties mappings = new Properties();
mappings.put("s1", "dao01");
mappings.put("s2", "dao02");
factory.setServiceMappings(mappings);
return factory;
}
</code>Resulting output shows different bean instances for the two names.
You can also set a custom exception class for lookup failures:
<code>ServiceLocatorFactoryBean factory = new ServiceLocatorFactoryBean();
factory.setServiceLocatorExceptionClass(PackServiceLocatorException.class);
</code>2.3 ServiceLocatorFactoryBean implementation
<code>public class ServiceLocatorFactoryBean implements FactoryBean<Object>, BeanFactoryAware, InitializingBean {
private Class<?> serviceLocatorInterface;
private Constructor<Exception> serviceLocatorExceptionConstructor;
private Properties serviceMappings;
private Object proxy;
public Object getObject() { return this.proxy; }
public Class<?> getObjectType() { return this.serviceLocatorInterface; }
public void afterPropertiesSet() {
this.proxy = Proxy.newProxyInstance(
this.serviceLocatorInterface.getClassLoader(),
new Class<?>[] { this.serviceLocatorInterface },
new ServiceLocatorInvocationHandler());
}
private class ServiceLocatorInvocationHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return invokeServiceLocatorMethod(method, args);
}
private Object invokeServiceLocatorMethod(Method method, Object[] args) throws Exception {
Class<?> returnType = getServiceLocatorMethodReturnType(method);
String beanName = tryGetBeanName(args);
if (StringUtils.hasLength(beanName)) {
return beanFactory.getBean(beanName, returnType);
} else {
return beanFactory.getBean(returnType);
}
}
}
}
</code>2.4 ObjectFactoryCreatingFactoryBean
This FactoryBean returns an ObjectFactory that, when invoked, retrieves a bean from the BeanFactory, typically a prototype bean, without exposing the BeanFactory directly.
<code>@Scope("prototype")
@Component
public class PersonService {}
@Configuration
public class AppConfig {
@Bean
ObjectFactoryCreatingFactoryBean serviceFactory() {
ObjectFactoryCreatingFactoryBean factory = new ObjectFactoryCreatingFactoryBean();
factory.setTargetBeanName("personService");
return factory;
}
}
@Component
public class PersonController {
@Resource
private ObjectFactory<PersonService> serviceFactory;
public void save() {
System.out.println(serviceFactory.getObject());
System.out.println(serviceFactory.getObject());
}
}
</code>2.5 Comparison
ServiceLocatorFactoryBean : No dependency on Spring-specific interfaces, but requires runtime class generation.
ObjectFactoryCreatingFactoryBean : Relies on Spring APIs.
Both approaches enable prototype bean injection while keeping the consuming code clean.
End of article.
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.