Backend Development 9 min read

Master Spring’s ObjectFactory & FactoryBean: Real-World Usage

This article explains the differences between Spring’s ObjectFactory and FactoryBean interfaces, demonstrates how they are used internally for bean creation, dependency injection, and servlet API injection, and shows how to customize ObjectFactory behavior to resolve bean conflicts and inject specific implementations.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Spring’s ObjectFactory & FactoryBean: Real-World Usage

1 Interface Comparison

ObjectFactory is a simple functional interface:

<code>@FunctionalInterface
public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}
</code>

FactoryBean is a factory bean interface used to customize object creation:

<code>public interface FactoryBean<T> {
    // Return the actual object
    T getObject() throws Exception;
    // Return the object type
    Class<?> getObjectType();
    // Whether it is a singleton; if true the created object is cached
    boolean isSingleton();
}
</code>

Note: If a class implements FactoryBean and you want to obtain the bean itself, prefix the bean name with '&'.

When a property is of type ObjectFactory or ObjectProvider, Spring injects a DependencyObjectProvider that creates the object only when

getObject()

is called.

2 Practical Applications

2.1 Creating Bean Instances

ObjectFactory is widely used in Spring source code, e.g., in

AbstractBeanFactory

:

<code>public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
    protected <T> T doGetBean(String name, @Nullable Class<T> requiredType,
                              @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
        // other code
        if (mbd.isSingleton()) {
            // getSingleton’s second argument is an ObjectFactory (lambda)
            sharedInstance = getSingleton(beanName, () -> {
                try {
                    return createBean(beanName, mbd, args);
                } catch (BeansException ex) {
                    // Explicitly remove instance from singleton cache...
                    destroySingleton(beanName);
                    throw ex;
                }
            });
            beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
        // other code
    }
}
</code>

The

getSingleton

method creates beans according to different scopes (singleton, prototype, request, session) using an ObjectFactory.

2.2 Servlet API Injection

In controllers, Request and Response objects are injected via ObjectFactory interfaces.

The context instantiated at container startup is

AnnotationConfigServletWebServerApplicationContext

, which calls

postProcessBeanFactory

:

<code>public class AnnotationConfigServletWebServerApplicationContext {
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        super.postProcessBeanFactory(beanFactory);
        if (this.basePackages != null && this.basePackages.length > 0) {
            this.scanner.scan(this.basePackages);
        }
        if (!this.annotatedClasses.isEmpty()) {
            this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
        }
    }
}
</code>
ServletWebServerApplicationContext

further registers web scopes and resolvable dependencies for Request, Response, Session, and WebRequest, all implemented as ObjectFactory:

<code>public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext {
    @Override
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        beanFactory.addBeanPostProcessor(new WebApplicationContextServletContextAwareProcessor(this));
        beanFactory.ignoreDependencyInterface(ServletContextAware.class);
        registerWebApplicationScopes();
    }
    private void registerWebApplicationScopes() {
        ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(getBeanFactory());
        WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory());
        existingScopes.restore();
    }
}
</code>

2.3 Custom ObjectFactory

When multiple beans of the same type exist, Spring throws

NoUniqueBeanDefinitionException

. Besides

@Primary

and

@Qualifier

, you can resolve it by registering a resolvable dependency.

Example of a custom

BeanFactoryPostProcessor

that registers a specific bean:

<code>@Component
public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // beanFactory is DefaultListableBeanFactory; it maintains a map of resolvableDependencies
        beanFactory.registerResolvableDependency(AccountDAO.class, beanFactory.getBean("accountBDAO"));
    }
}
</code>

Custom

ObjectFactory

implementation:

<code>public class AccountObjectFactory implements ObjectFactory<AccountDAO> {
    @Override
    public AccountDAO getObject() throws BeansException {
        return new AccountBDAO();
    }
}
// Register in BeanFactoryPostProcessor
beanFactory.registerResolvableDependency(AccountDAO.class, new AccountObjectFactory());
</code>

During bean property population,

DefaultListableBeanFactory

searches for candidates, including those registered via

registerResolvableDependency

. If the registered object is an

ObjectFactory

, its

getObject()

method is invoked to obtain the actual bean.

<code>protected Map<String, Object> findAutowireCandidates(@Nullable String beanName,
                                                    Class<?> requiredType,
                                                    DependencyDescriptor descriptor) {
    String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
            this, requiredType, true, descriptor.isEager());
    Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
    for (Map.Entry<Class<?>, Object> entry : this.resolvableDependencies.entrySet()) {
        Class<?> autowiringType = entry.getKey();
        if (autowiringType.isAssignableFrom(requiredType)) {
            Object autowiringValue = entry.getValue();
            autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
            if (requiredType.isInstance(autowiringValue)) {
                result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
                break;
            }
        }
    }
    // ... add other candidates
    return result;
}

// AutowireUtils.resolveAutowiringValue
public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
    if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
        ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
        if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
            return Proxy.newProxyInstance(requiredType.getClassLoader(),
                    new Class<?>[] {requiredType},
                    new ObjectFactoryDelegatingInvocationHandler(factory));
        } else {
            return factory.getObject();
        }
    }
    return autowiringValue;
}
</code>

End of article.

JavaBackend DevelopmentSpringDependency InjectionFactoryBeanObjectFactory
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.