遅い答えですが、これはSpring Core 4+(およびおそらくSpring Core 3)で可能です。
標準の Spring セマンティクスは JavaConfig を使用した内部 Bean の作成をサポートしていませんが、内部 Bean に関する内部機能を利用して同じ結果を生成できます。
内部 Bean は、 によるプロパティ値の解決中に生成されますBeanDefinitionValueResolver
( を参照BeanDefinitionValueResolver#resolveValueIfNecessary
)。Spring 内の「内部 Bean」の概念は、主にこの値リゾルバー (内部 Bean の唯一のプロデューサー) 内と、「含まれる Bean」という用語 (親クラスからDefaultSingletonBeanRegistry
) の下の Bean ファクトリ内に含まれています。
BeanDefinition
に示されている解決戦略に従って、プロパティを として定義することにより、Spring をだまして追加の内部 Bean を生成させることができます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
}
}
このソリューションの明らかな問題はBeanDefinitionRegistryPostProcessor
、 を介して手動で処理する必要があることです。代わりに私が提案するのは次のとおりです。
- カスタム注釈を作成します (例:
@InnerBean
)
- 必要に応じて、この注釈を
@Configuration
クラスおよび候補コンポーネント クラスのメソッドに添付します。
- で注釈付き静的メソッドのクラスをスキャンするように を調整します (コンポーネント クラスは でスキャンし、
BeanDefinitionRegistryPostProcessor
構成クラスはでスキャンする必要があります) 。@InnerBean
#postProcessBeanFactory
#postProcessBeanDefinitionRegistry
- Bean 定義を、含まれている Bean 定義の autowired コンストラクター フィールド (またはそれが規則である場合はセッター フィールド) にアタッチします。
次に例を示します。
@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()));
}
}
}
これを行うことには、いくつかの利点と注意事項があります。大きな利点の 1 つは、Bean ライフサイクルが Spring IoC コンテナーによって管理されることです。つまり、ライフサイクル コールバック ( や など@PostConstruct
)@PreDestroy
が呼び出されます。Bean は、親のライフサイクルに従って自動的に管理できます。警告には、Bean をファクトリ メソッド パラメーターとして注入できないこと (ただし、少し作業すればこれを修正できる可能性があります) と、AOP プロキシが@Configuration
クラス内のこれらのメソッドに適用されないこと (つまり、realRepository()
呼び出されないようにする必要があること) が含まれます。シングルトンの内部 Bean を参照しないでください。代わりに、インスタンス フィールドを常に参照する必要があります)。これを適用するには、さらにプロキシ ( と同様ConfigurationClassEnhancer.BeanMethodInterceptor
) を追加する必要があります。