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 в конструкторе окружающего класса.

Другие вопросы по тегам