迟到的答案,但这在 Spring Core 4+(可能还有 Spring Core 3)中是可能的,但有一些技巧。
虽然标准 Spring 语义不支持使用 JavaConfig 创建内部 bean,但可以利用内部 bean 周围的内部功能来产生相同的结果。
内部 bean 在属性值解析期间由BeanDefinitionValueResolver
(请参阅BeanDefinitionValueResolver#resolveValueIfNecessary
)生成。Spring 中“内部 bean”的概念主要包含在这个值解析器(它是内部 bean 的唯一生产者)和术语“包含的 bean”(来自父类DefaultSingletonBeanRegistry
)下的 bean 工厂中。
我们可以通过将属性定义为 a 来诱骗 Spring 生成额外的内部 bean BeanDefinition
,根据中提出的解析策略BeanDefinitionValueResolver
:
@Configuration
public class MyConfiguration {
private static Logger logger = LoggerFactory.getLogger(MyConfiguration.class);
private RealRepository realRepository;
private Timer timer;
public MyConfiguration(@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") RealRepository realRepository, Timer timer) {
this.realRepository = realRepository;
this.timer = timer;
logger.info("Constructed MyConfiguration {}", this);
}
@Bean
public TimedRepository timedRepository() {
TimedRepository timedRepository = new TimedRepository(this.realRepository, this.timer);
logger.info("Created timed repo: {}", timedRepository);
return timedRepository;
}
public RealRepository realRepository(DataSource dataSource) {
RealRepository realRepository = new RealRepository(dataSource);
logger.info("Created real repo: {}", realRepository);
return realRepository;
}
@Override
public String toString() {
return "MyConfiguration{" +
"realRepository=" + realRepository +
", timer=" + timer +
'}';
}
}
@Component
public class InnerBeanInjectionBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
@Override
public int getOrder() {
// Preempt execution of org.springframework.context.annotation.ConfigurationClassPostProcessor
return 0;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
String[] beanDefinitionNameList = ((ConfigurableListableBeanFactory) registry).getBeanNamesForType(MyConfiguration.class, true, false);
assert beanDefinitionNameList.length == 1;
BeanDefinition configurationBeanDefinition = registry.getBeanDefinition(beanDefinitionNameList[0]);
BeanDefinition realRepositoryBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MyConfiguration.class)
.setScope(BeanDefinition.SCOPE_SINGLETON)
.setFactoryMethod("realRepository")
.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR)
.getBeanDefinition();
configurationBeanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue(realRepositoryBeanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// Do nothing
}
}
此解决方案的明显问题是它需要通过 a 进行手动处理BeanDefinitionRegistryPostProcessor
,这在此需要大量工作才能获得小幅收益。我会建议如下:
- 创建自定义注释(例如,
@InnerBean
)
- 在需要的地方将此注释附加到
@Configuration
类和候选组件类中的方法
- 调整
BeanDefinitionRegistryPostProcessor
以扫描类以查找带注释的@InnerBean
静态方法(组件类应在中扫描#postProcessBeanFactory
,配置类应在中扫描#postProcessBeanDefinitionRegistry
)
- 将 bean 定义附加到包含 bean 定义的自动装配的构造函数字段(或 setter 字段,如果这是您的约定)
下面是一个例子:
@Target(ElementType.METHOD)
public @interface InnerBean {
}
@Configuration
public class MyConfiguration {
private static Logger logger = LoggerFactory.getLogger(MyConfiguration.class);
private RealRepository realRepository;
private Timer timer;
public MyConfiguration(@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") RealRepository realRepository, Timer timer) {
this.realRepository = realRepository;
this.timer = timer;
logger.info("Constructed MyConfiguration {}", this);
}
@Bean
public TimedRepository timedRepository() {
TimedRepository timedRepository = new TimedRepository(this.realRepository, this.timer);
logger.info("Created timed repo: {}", timedRepository);
return timedRepository;
}
@InnerBean
public static RealRepository realRepository(DataSource dataSource) {
RealRepository realRepository = new RealRepository(dataSource);
logger.info("Created real repo: {}", realRepository);
return realRepository;
}
@Override
public String toString() {
return "MyConfiguration{" +
"realRepository=" + realRepository +
", timer=" + timer +
'}';
}
}
@Component
public class InnerBeanInjectionBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
private static Logger logger = LoggerFactory.getLogger(InnerBeanInjectionBeanFactoryPostProcessor.class);
private Set<BeanDefinition> processedBeanDefinitionSet = new HashSet<>();
@Override
public int getOrder() {
// Preempt execution of org.springframework.context.annotation.ConfigurationClassPostProcessor
return 0;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry;
String[] configBeanDefinitionNames = beanFactory.getBeanNamesForAnnotation(Configuration.class);
Arrays.stream(configBeanDefinitionNames)
.map(beanFactory::getBeanDefinition)
.filter(this::isCandidateBean)
.peek(this.processedBeanDefinitionSet::add)
.forEach(this::autowireInnerBeans);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
Arrays.stream(beanFactory.getBeanDefinitionNames())
.map(beanFactory::getBeanDefinition)
.filter(this::isCandidateBean)
.filter(beanDefinition -> !this.processedBeanDefinitionSet.contains(beanDefinition))
.forEach(this::autowireInnerBeans);
}
private boolean isCandidateBean(BeanDefinition beanDefinition) {
return beanDefinition.getBeanClassName() != null && beanDefinition.getBeanClassName().startsWith("com.example.demo.");
}
private void autowireInnerBeans(BeanDefinition beanDefinition) {
// Get @InnerBean methods
assert beanDefinition instanceof AnnotatedBeanDefinition;
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
Set<MethodMetadata> innerBeanMethods = annotatedBeanDefinition.getMetadata().getAnnotatedMethods(InnerBean.class.getName());
// Attach inner beans as constructor parameters
for (MethodMetadata method : innerBeanMethods) {
String innerBeanName = method.getMethodName();
if (!method.isStatic()) {
logger.error("@InnerBean definition [{}] is non-static. Inner beans must be defined using static factory methods.", innerBeanName);
continue;
}
BeanDefinition innerBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(beanDefinition.getBeanClassName())
.setScope(BeanDefinition.SCOPE_SINGLETON)
.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR)
.setFactoryMethod(innerBeanName)
.getBeanDefinition();
beanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue(new ConstructorArgumentValues.ValueHolder(innerBeanDefinition, method.getReturnTypeName(), method.getMethodName()));
}
}
}
这样做会有一些好处和注意事项。一个很大的好处是 bean 生命周期将由 Spring IoC 容器管理,这意味着将调用生命周期回调(例如@PostConstruct
和@PreDestroy
)。bean 可以根据 parent 的生命周期进行自动管理。需要注意的是,bean 不能作为工厂方法参数注入(尽管您可能需要做一些工作来解决这个问题),并且 AOP 代理不会应用于@Configuration
类中的这些方法(即,realRepository()
永远不应该被调用)不引用单例内部 bean——相反,应始终引用实例字段)。需要添加进一步的代理(类似于ConfigurationClassEnhancer.BeanMethodInterceptor
)才能应用此功能。