Spring Boot动态创建相似Bean替代重复代码的可行性及问题排查
Yes, dynamically creating multiple similar beans to replace repetitive @Bean methods is absolutely feasible in Spring—you just need to use the right lifecycle hook to ensure the beans are registered early enough for Spring to pick them up properly.
Why Your Current Approach Isn't Working
Your @PostConstruct attempt registers singletons after the Spring application context has already completed most of its initialization. Specifically, the Spring MVC handler mappings that map PubHessianServiceExporter beans to their URL endpoints would have already scanned the context for these beans before your @PostConstruct method runs. Even though you're registering the beans, they aren't added to the handler mappings, so they don't become accessible via their URLs.
The Correct Approach: Use BeanDefinitionRegistryPostProcessor
To fix this, you should use BeanDefinitionRegistryPostProcessor, which runs before any beans are instantiated. This allows you to register bean definitions dynamically, ensuring they're part of the context from the start and picked up by the handler mappings correctly.
Here's how to implement it:
Step 1: Create the Registry Post Processor
This class will read your sign-sys-ids configuration and register a PubHessianServiceExporter bean for each ID:
import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; public class HessianServiceExporterRegistry implements BeanDefinitionRegistryPostProcessor, EnvironmentAware { private Environment environment; @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { // Read the system IDs from configuration String[] signSysIds = environment.getProperty("sign-sys-ids", String[].class, new String[]{}); for (String sysId : signSysIds) { int id = Integer.parseInt(sysId); // Build the bean definition for PubHessianServiceExporter BeanDefinitionBuilder exporterBuilder = BeanDefinitionBuilder.genericBeanDefinition(PubHessianServiceExporter.class); exporterBuilder.addPropertyValue("service", StudentAppServiceFactory.curriculumScheduleServices.get(id)); exporterBuilder.addPropertyValue("serviceInterface", CurriculumScheduleService.class); // Register the bean with the URL path as its name (matches your original @Bean naming) String beanName = "/svc/curriculumScheduleService/" + sysId; registry.registerBeanDefinition(beanName, exporterBuilder.getBeanDefinition()); } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // No additional processing needed here } }
Step 2: Register the Post Processor in Your Configuration
Update your HessianFactory configuration to include the post processor bean:
@Configuration @Order(Integer.MIN_VALUE) public class HessianFactory { @Bean public HessianServiceExporterRegistry hessianServiceExporterRegistry() { return new HessianServiceExporterRegistry(); } }
Step 3: Configure Your System IDs
Make sure your sign-sys-ids property is correctly set in your application configuration (e.g., application.properties):
sign-sys-ids=1,2,3,5
Key Notes
- Bean Naming: We use the URL path as the bean name, just like your original
@Beanmethods. This is critical because Spring's default handler mapping for Hessian exporters uses the bean name as the endpoint URL. - Lifecycle Timing:
BeanDefinitionRegistryPostProcessorruns early, ensuring the bean definitions are present when Spring scans forHessianServiceExporterbeans to map to URLs. - Dependency Availability: Ensure
StudentAppServiceFactory.curriculumScheduleServicesis populated before the post processor runs. If this is a static factory, initialize the map before the Spring context starts; if it's a bean, mark it as@DependsOnor ensure it's initialized early.
This approach eliminates the repetitive @Bean methods while maintaining the same functionality as your original code.
内容的提问来源于stack exchange,提问作者lester.liang

