Spring 3.1 конфигурация Java и внутренние компоненты
@Bean
public TimedRepository timedRepository(RealRepository repo) {
return new TimedRepository(repo, timer); // Adds some metrics
}
@Bean
public RealRepository realRepository(DataSource ds) {
return new RealRepository(ds); // The real jdbc implementation
}
В старые времена XML я настраивал реальное хранилище как анонимный внутренний компонент. Возможно ли сделать что-то подобное с новым подходом к настройке Java? Создание реального хранилища внутри timedRepository
фабричный метод не вариант, потому что я хочу, чтобы Spring забрал аннотации на RealRepository
,
Мотивация состоит в том, чтобы избегать любых других компонентов, чтобы получить реальную реализацию репозитория. Я должен также упомянуть, что оба бина реализуют Repository
интерфейс, который будет использоваться любыми bean-компонентами в зависимости от хранилища (им не нужно знать о TimedRepository
или же RealRepository
,
3 ответа
Поздний ответ, но это возможно в Spring Core 4+ (и, возможно, Spring Core 3) с некоторыми хитростями.
Хотя стандартная семантика Spring не поддерживает создание внутреннего bean-компонента с помощью JavaConfig, внутренняя функциональность вокруг внутренних bean-компонентов может быть использована для получения тех же результатов.
Внутренние бобы производятся во время разрешения значения свойства BeanDefinitionValueResolver
(увидеть BeanDefinitionValueResolver#resolveValueIfNecessary
). Понятие "внутренние бины" в Spring в первую очередь заключено в этом преобразователе значений (который является единственным производителем внутренних бинов) и в фабриках бинов под термином "содержащиеся бины" (из родительского класса). DefaultSingletonBeanRegistry
).
Мы можем обмануть Spring в создании дополнительных внутренних бинов, определив свойство как 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
}
}
Очевидная проблема с этим решением состоит в том, что оно требует ручной обработки через 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()));
}
}
}
Будет несколько преимуществ и предостережений от этого. Одним из больших преимуществ является то, что жизненный цикл бина будет управляться контейнером Spring IoC, а это означает, что обратные вызовы жизненного цикла (такие как @PostConstruct
а также @PreDestroy
) будет называться. Бином можно автоматически управлять в соответствии с жизненным циклом родителя. Предостережения включают в себя то, что bean-компоненты не могут быть введены как параметры фабричного метода (хотя с небольшой работой вы могли бы это исправить) и что проксирование AOP не будет применено к этим методам внутри @Configuration
классы (т.е. realRepository()
никогда не должен вызываться, так как он не будет ссылаться на внутренний компонент-одиночка - вместо этого всегда следует ссылаться на поле экземпляра). Дальнейшее проксирование (аналогично ConfigurationClassEnhancer.BeanMethodInterceptor
) необходимо добавить, чтобы применить это.
Я не думаю, что есть эквивалент внутренних или локальных компонентов при использовании конфигурации на основе Java. Возможно, я бы также попытался создать RealRepository в методе bean-компонента TimedRepositories, запросив все зависимости в сигнатуре метода. Но если вам действительно нужна весна, чтобы позаботиться о зависимостях RealRepository, вам нужно использовать фабрику бинов.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class ConfigTest {
@Autowired TimedRepository timedRepo;
@Test
public void testRepository() {
Assert.assertNotNull(timedRepo);
}
@Configuration
static class TimedRepositoryConfiguration {
@Autowired
private AutowireCapableBeanFactory beanFactory;
@Bean
public TimedRepository timedRepository() {
RealRepository realRepository = (RealRepository) beanFactory.createBean(RealRepository.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, true);
return new TimedRepository(realRepository);
}
public RealRepository realRepository() {
return new RealRepository();
}
}
static class RealRepository {
}
static class TimedRepository {
private RealRepository realRepo;
public TimedRepository(RealRepository r) {
this.realRepo = r;
}
}
}
Вы можете просто создать экземпляр бобов вручную:
public class BeanThatDependsOnRealRepository() {
private final Repository repository;
@Inject
public BeanThatDependsOnRealRepository(DataSource dataSource) {
this.repository = new RealRepository(dataSource);
}
}
По сути, это то, что анонимный внутренний компонент делает в XML. Вы только что явно сконструировали его и получили его зависимости от Spring в конструкторе окружающего класса.