Discover Spring Boot’s New BeanFactoryInitializer – Early Bean Initialization Made Easy
Spring Boot 3.5 introduces the BeanFactoryInitializer extension point, allowing developers to safely pre‑instantiate specific beans before the regular singleton pool, replace unsafe BeanFactoryPostProcessor hacks, fetch remote configurations, and programmatically register @Tool‑annotated beans, all demonstrated with concrete code examples and a working test.
Before Spring 6.2, initializing infrastructure beans before regular singleton creation required calling getBean() inside a BeanFactoryPostProcessor. This violated the post‑processor’s purpose, caused beans to miss BeanPostProcessor handling, and produced "is not eligible for getting processed" startup warnings. The SmartInitializingSingleton hook executed too late for many scenarios.
BeanFactoryInitializer – native lifecycle hook
Spring 6.2 introduces BeanFactoryInitializer, invoked immediately before preInstantiateSingletons(). This provides a precise insertion point for early bean loading without side effects.
What BeanFactoryInitializer enables
Legally and safely instantiate a bean before the regular singleton pool while still passing through all BeanPostProcessor s.
Fetch remote configuration (e.g., from Nacos or Apollo) during early startup.
Programmatically register special singleton beans based on custom conditions before other singletons are created.
Real‑world case: automatic registration of @Tool beans
The goal is to collect all beans annotated with @Tool (used for function calling) and register them automatically, avoiding manual addition.
1. Tool bean registry
@Component
public class ToolBeanRegistry {
private static final java.util.Map<String, Class<?>> toolBeans = new java.util.concurrent.ConcurrentHashMap<>();
public void addMcpToolBean(String beanName, Class<?> clazz) {
toolBeans.computeIfAbsent(beanName, key -> clazz);
}
public java.util.Map<String, Class<?>> getMcpToolBean() {
return java.util.Collections.unmodifiableMap(toolBeans);
}
}2. Custom BeanFactoryInitializer
@Component
public class ToolBeanFactoryInitializer implements BeanFactoryInitializer<ConfigurableListableBeanFactory> {
private static final Class<Tool> TOOL_TYPE = Tool.class;
private final ToolBeanRegistry registry;
public ToolBeanFactoryInitializer(ToolBeanRegistry registry) {
this.registry = registry;
}
@Override
public void initialize(ConfigurableListableBeanFactory beanFactory) {
String[] beanNames = beanFactory.getBeanDefinitionNames();
for (String beanName : beanNames) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
String beanClassName = definition.getBeanClassName();
if (beanClassName == null) {
Object source = definition.getSource();
if (source instanceof MethodMetadata methodMetadata) {
beanClassName = methodMetadata.getReturnTypeName();
}
}
if (beanClassName != null) {
try {
Class<?> clazz = Class.forName(beanClassName);
java.lang.reflect.Method[] methods = org.springframework.util.ReflectionUtils.getAllDeclaredMethods(clazz);
for (java.lang.reflect.Method method : methods) {
if (org.springframework.core.annotation.AnnotationUtils.findAnnotation(method, TOOL_TYPE) != null) {
this.registry.addMcpToolBean(beanName, clazz);
break;
}
}
} catch (ClassNotFoundException ignored) {}
}
}
}
}This initializer parses BeanDefinition metadata without triggering early bean instantiation, safely collecting all methods annotated with @Tool and storing their bean names and classes in the registry.
3. FactoryBean that creates the callback provider
@Component
public class ToolCallbackFactoryBean implements FactoryBean<ToolCallbackProvider>, BeanFactoryAware {
private BeanFactory beanFactory;
private final ToolBeanRegistry registry;
public ToolCallbackFactoryBean(ToolBeanRegistry registry) {
this.registry = registry;
}
@Override
public ToolCallbackProvider getObject() throws Exception {
java.util.List<Object> tools = new java.util.ArrayList<>();
this.registry.getMcpToolBean().forEach((key, clazz) -> {
Object toolBean = this.beanFactory.getBean(key, clazz);
tools.add(toolBean);
});
java.util.List<ToolCallback> toolCallbacks = java.util.List.of(ToolCallbacks.from(tools.toArray()));
return ToolCallbackProvider.from(toolCallbacks);
}
@Override
public Class<?> getObjectType() { return ToolCallbackProvider.class; }
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; }
}The FactoryBean retrieves the actual @Tool beans from the container using the registry metadata, assembles them into a list of ToolCallback objects, and exposes a single ToolCallbackProvider for core business logic.
4. Demo @Tool beans
@Component
public class DemoTools {
@Tool(name = "weather", description = "获取给定城市的天气预报")
public String weather(String city) {
return "-5℃ 到 -15℃";
}
}
@Component
public class MedicineTools {
private final MedicineService medicineService;
public MedicineTools(MedicineService medicineService) { this.medicineService = medicineService; }
@Tool(name = "searchMedicine", description = "通过名称查询符合条件的药品")
public java.util.Collection<MedicineDTO> searchMedicine(@ToolParam(description = "药品名称", required = true) String name) {
// ... implementation ...
}
}After starting the application, the log confirms that both tools are automatically registered.
Thus, BeanFactoryInitializer provides a clean, zero‑side‑effect hook for early container interaction, suitable for early infrastructure setup, remote configuration loading, and automatic discovery of custom‑annotated beans.
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.
