如何基于配置文件动态创建Spring Bean并注入至其他Bean?
Hey there! Let's break down how to dynamically create Spring Beans from both .properties and application.yaml files, then inject them with @Autowired—I'll walk you through practical, actionable examples so you can implement this easily.
Let's start with a common scenario: you have configuration values in a .properties file, and you want to turn those into Spring Beans that can be injected anywhere in your app.
1.1 Step 1: Bind Properties to a Configuration Class
First, define a class to hold your configuration values using @ConfigurationProperties. This lets Spring automatically map your .properties entries to object fields.
Suppose your app.properties looks like this:
# Single bean configuration app.payment-service.api-key=sk_test_123 app.payment-service.timeout=3000 # Multiple beans (a list of users) app.users[0].id=1 app.users[0].name=Alice app.users[1].id=2 app.users[1].name=Bob
Create a corresponding configuration class:
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.List; @Data @Component @ConfigurationProperties(prefix = "app") public class AppProperties { // For single bean private PaymentServiceProperties paymentService; // For multiple beans private List<User> users; @Data public static class PaymentServiceProperties { private String apiKey; private int timeout; } @Data public static class User { private int id; private String name; } }
Note: Don't forget to add spring-boot-configuration-processor as a dependency if you want IDE support for your properties.
1.2 Step 2: Dynamically Register Beans
You have two main approaches here—choose based on your use case:
Option A: Simple @Bean Method (For Fixed/Predefined Beans)
If you know the type of Bean you want to create upfront, use a @Configuration class to define Beans using the bound properties:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.ApplicationContext; @Configuration public class BeanConfig { private final ApplicationContext applicationContext; public BeanConfig(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } // Create a single PaymentService Bean from properties @Bean public PaymentService paymentService(AppProperties appProperties) { AppProperties.PaymentServiceProperties props = appProperties.getPaymentService(); return new PaymentService(props.getApiKey(), props.getTimeout()); } // Register each User from the list as a separate Bean public void registerUserBeans(AppProperties appProperties) { List<User> users = appProperties.getUsers(); BeanDefinitionRegistry registry = (BeanDefinitionRegistry) applicationContext.getAutowireCapableBeanFactory(); users.forEach(user -> { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(User.class); builder.addPropertyValue("id", user.getId()); builder.addPropertyValue("name", user.getName()); // Register with unique Bean name registry.registerBeanDefinition("user-" + user.getId(), builder.getBeanDefinition()); }); } }
Option B: Dynamic Registration with BeanDefinitionRegistryPostProcessor
For more flexibility (e.g., creating beans based on arbitrary property keys), implement BeanDefinitionRegistryPostProcessor to register beans directly with Spring's registry:
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @Component public class DynamicBeanRegistrar implements BeanDefinitionRegistryPostProcessor { private final Environment environment; public DynamicBeanRegistrar(Environment environment) { this.environment = environment; } @Override public void postProcessBeanDefinitionRegistry(org.springframework.beans.factory.support.BeanDefinitionRegistry registry) { // Read user count dynamically from properties int userCount = Integer.parseInt(environment.getProperty("app.user-count", "0")); for (int i = 0; i < userCount; i++) { String userId = environment.getProperty("app.users[" + i + "].id"); String userName = environment.getProperty("app.users[" + i + "].name"); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(User.class); builder.addPropertyValue("id", userId); builder.addPropertyValue("name", userName); // Register Bean with unique name registry.registerBeanDefinition("dynamicUser-" + i, builder.getBeanDefinition()); } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { // Empty implementation for this use case } }
1.3 Step 3: Inject and Use the Beans
Now you can inject these beans anywhere using @Autowired:
import org.springframework.stereotype.Service; import java.util.List; import org.springframework.beans.factory.annotation.Qualifier; @Service public class UserService { // Inject single bean @Autowired private PaymentService paymentService; // Inject all User beans (Spring collects all beans of type User) @Autowired private List<User> allUsers; // Inject a specific User bean using @Qualifier @Autowired @Qualifier("user-1") private User alice; // Use the beans in your logic public void printUsers() { allUsers.forEach(user -> System.out.println("User: " + user.getName())); } }
Good news: most of the process is identical to .properties files! Spring's Environment abstraction handles both formats uniformly—only the configuration file syntax changes.
2.1 Step 1: Define Your YAML Configuration
Here's the equivalent of the earlier .properties file in application.yaml:
app: payment-service: api-key: sk_test_123 timeout: 3000 users: - id: 1 name: Alice - id: 2 name: Bob
2.2 Step 2: Reuse the Configuration Class & Bean Registration
The AppProperties class we created earlier works exactly the same for YAML—no changes needed. Similarly, the @Configuration class or BeanDefinitionRegistryPostProcessor implementations will function identically, since they read from the Spring Environment (not directly from the file).
2.3 Step 3: Inject and Use (Same as Properties)
You can inject the beans the same way as before—Spring doesn't care if the config came from YAML or properties.
- Enable Configuration Properties: If you don't use
@Componenton your@ConfigurationPropertiesclass, add@EnableConfigurationProperties(AppProperties.class)to your main application class or a@Configurationclass. - Bean Names: Always use unique names when registering dynamic beans to avoid conflicts. Use
@Qualifierto specify which bean to inject if you have multiple beans of the same type. - Validation: Add
@Validatedto your configuration class and JSR-380 annotations (like@NotBlank,@Min) to validate your properties at startup.
内容的提问来源于stack exchange,提问作者Harsh Yadav

