Spring Bean Instantiation Using Custom Instance Suppliers

To demonstrate customizing the instantiation logic without relying on standard reflection, define a domain entity first.

public class PersonRecord {
    private String identifier;

    public PersonRecord() {
    }

    public PersonRecord(String identifier) {
        this.identifier = identifier;
    }

    public String getIdentifier() {
        return identifier;
    }

    public void setIdentifier(String identifier) {
        this.identifier = identifier;
    }

    @Override
    public String toString() {
        return "PersonRecord{" +
                "identifier='" + identifier + '\'' +
                '}';
    }
}

Next, establish a static factory class that returns the desired instance. This replaces the default constructor invocation.

public class ManualObjectCreator {

    public static PersonRecord resolveTarget() {
        return new PersonRecord("alice_jones");
    }
}

In a Spring application context, beans are typically instantiated by the framework. To override this using an enstance supplier, implement BeanFactoryPostProcessor. Inside, cast the retrieved BeanDefinition to GenericBeanDefinition because the base interface lacks setter methods for suppliers.

public class CustomInstanceResolver implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition userDef = beanFactory.getBeanDefinition("profile");
        if (userDef instanceof GenericBeanDefinition) {
            GenericBeanDefinition genericDef = (GenericBeanDefinition) userDef;
            genericDef.setInstanceSupplier(ManualObjectCreator::resolveTarget);
            genericDef.setBeanClass(PersonRecord.class);
        }
    }
}

The XML configuration registers both the target bean and the processor itself.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="profile" class="com.example.PersonRecord"></bean>
    <bean class="com.example.CustomInstanceResolver"></bean>
</beans>

Verification is performed via a main method retrieving the bean from the context.

public class ApplicationTest {

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        PersonRecord bean = ac.getBean(PersonRecord.class);
        System.out.println(bean.getIdentifier());
    }
}

Internal Lifecycle Mechanism

Since CustomInstanceResolver implements BeanFactoryPostProcessor, it trgigers during container startup before singleton instantiation begins. The execution flow eventually leads to doCreateBean within AbstractAutowireCapableBeanFactory.

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {
	// Instantiate the bean.
	BeanWrapper instanceWrapper = null;
	
	if (mbd.isSingleton()) {
		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
	}
	
	if (instanceWrapper == null) {
		// Delegates to instantiate strategy
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	
	Object bean = instanceWrapper.getWrappedInstance();
	Class<?> beanType = instanceWrapper.getWrappedClass();
	if (beanType != NullBean.class) {
		mbd.resolvedTargetType = beanType;
	}

	// ... Post-processing and population ...
}

The critical logic resides in createBeanInstance. Instead of immediately attempting reflection or constructor injection, the framework checks for a registered instance supplier.

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
	Class<?> beanClass = resolveBeanClass(mbd, beanName);

	// Check for custom Supplier before falling back to reflection
	Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
	if (instanceSupplier != null) {
		return obtainFromSupplier(instanceSupplier, beanName);
	}

	if (mbd.getFactoryMethodName() != null) {
		return instantiateUsingFactoryMethod(beanName, mbd, args);
	}

	// Standard constructor resolution follows here...
}

When a supplier is detected, obtainFromSupplier is invoked. This method ensures thread safety by preserving the current creation context stored in a ThreadLocal variable named currentlyCreatedBean.

protected BeanWrapper obtainFromSupplier(Supplier<?> instanceSupplier, String beanName) {
	Object instance;

	String outerBean = this.currentlyCreatedBean.get();
	this.currentlyCreatedBean.set(beanName);
	try {
		// Invokes the lambda provided during configuration
		instance = instanceSupplier.get();
	} finally {
		if (outerBean != null) {
			this.currentlyCreatedBean.set(outerBean);
		} else {
			this.currentlyCreatedBean.remove();
		}
	}

	if (instance == null) {
		instance = new NullBean();
	}

	BeanWrapper bw = new BeanWrapperImpl(instance);
	initBeanWrapper(bw);
	return bw;
}

This implementation relies on the Supplier<T> functional interface. Calling .get() executes the logic defined in the ManualObjectCreator.resolveTarget method directly, completely bypassing standard reflection-based instantiation mechanisms usually reserved for classes lacking custom factories.

Tags: Spring Framework java Dependency Injection Bean Lifecycle Functional Interfaces

Posted on Wed, 22 Jul 2026 16:31:00 +0000 by iluv8250