Автоматически настраивайте bean-компоненты в контексте корневого и диспетчерского приложений с помощью одной аннотации.

Допустим, я создал аннотацию с именем @EnableFeauture, которая импортирует класс конфигурации bean-компонента EnableFeatureConfiguration. Эта аннотация обычно размещается поверх конфигурации диспетчера. Должны быть bean-компоненты, такие как преобразователи представлений и т. д., принадлежать этой конфигурации диспетчера, но некоторые bean-компоненты действительно принадлежат корневому контексту.

Как я могу определить эти компоненты без необходимости в другой аннотации? Моей первой мыслью было автосвязывание WebApplicationContext и вызов context.getParentBeanFactory() для регистрации bean-компонентов, но я не уверен, что это лучший способ достичь моей цели. Как это обычно делается?


ОБНОВИТЬ

Чтобы немного прояснить проблему. Я работаю над проектом по интеграции механизма шаблонов с Spring MVC. Проект состоит из следующих категорий/частей:

  • Конфигурация
  • Аннотация, например. EnableFeature (импорт конфигурации)
  • Вид
  • ViewResolver
  • Фабрика шаблонов

Логически все категории классов могут существовать в контексте веб-приложения. Однако фабрика шаблонов может использоваться и другими службами (электронная почта и т. д.). Службы, которые в основном существуют в корневом контексте. Итак, я в основном спрашиваю, как я могу сделать фабрику доступной для корневого контекста чистым способом. Я хотел бы, чтобы конфигурация требовала как можно меньше. На данный момент для установки требуется только одна аннотация, размещенная поверх класса конфигурации диспетчера.


person Bart    schedule 22.08.2014    source источник
comment
С трудом улавливаю ваш вопрос. Чтобы уточнить: 1) Следующая цитата из Spring ref относится к вам [...] у вас обычно будет корневой WebApplicationContext, загруженный через Spring ContextLoaderListener, и дочерний WebApplicationContext, загруженный через Spring DispatcherServlet.? 2) Вы хотите добавить дополнительные bean-компоненты в свой дочерний контекст с помощью собственной аннотации, некоторые из новых bean-компонентов даже в родительский контекст. Если да, то как вы хотите различать? Содержит ли код в вашей аннотации логику для принятия решения о регистрации компонента в дочернем или родительском?   -  person Hille    schedule 24.08.2014
comment
Не могли бы вы уточнить, чего вы пытаетесь достичь (функциональный уровень)? И почему вы думаете, что для этого вам нужна пользовательская аннотация?   -  person Serge Ballesta    schedule 24.08.2014
comment
Смотрите мой обновленный вопрос. Я надеюсь, что это сделает его менее запутанным   -  person Bart    schedule 25.08.2014
comment
@Hille У меня есть конфигурация ImportAware, которая импортируется с помощью аннотации @EnableFeature, которая настраивает некоторые компоненты. Однако один компонент (фабрика шаблонов) лучше подходит для корневого контекста. Но я не уверен, как добиться этого чистым способом.   -  person Bart    schedule 25.08.2014
comment
@SergeBallesta, надеюсь, теперь стало понятнее :)   -  person Bart    schedule 25.08.2014
comment
@Bart, вам нужно использовать чистый Spring или Spring Boot также является возможным решением?   -  person Jakub Kubrynski    schedule 25.08.2014
comment
@JakubKubrynski Я не использую Spring Boot   -  person Bart    schedule 25.08.2014
comment
Таким образом, у вас есть аннотация, которая должна быть размещена ТОЛЬКО в классе конфигурации, связанном с сетью (где вы, предположительно, получите доступ к WebApplicationContext), и в этом классе есть bean-компоненты, которые должны быть зарегистрированы в WebApplicationContext, и bean-компоненты, которые должны быть зарегистрированы в контекст корневого приложения (родительский элемент WebApplicationContext). И вам нужен способ определить это поведение, поместив одну аннотацию в класс конфигурации, связанный с сетью. Если да, то я думаю, что getParentBeanFactory — ваш лучший выбор.   -  person Andrei Stefan    schedule 25.08.2014
comment
Что касается чистого решения, я бы взглянул на Spring Boot, где условное поведение, связанное с компонентом, определяется с помощью аннотаций. Они используют Spring @Conditional аннотация для определения различных условий, которые должны определять, что должно произойти при выполнении определенного условия.   -  person Andrei Stefan    schedule 25.08.2014
comment
@AndreiStefan Спасибо за ваш вклад. Я рассмотрю подход @Conditional и то, как он используется.   -  person Bart    schedule 25.08.2014
comment
Хм, теперь, когда я пробую этот подход, я не уверен, что он такой прямолинейный... Точнее, как бы вы сказали Spring не использовать результат аннотированного метода @Bean для его регистрации в веб-приложении. контекст, но с контекстом корневого приложения?!   -  person Andrei Stefan    schedule 25.08.2014
comment
Я могу ошибаться, но для достижения того, что вам нужно, вам нужно испортить внутренности Spring. Класс @Configuration связан с контекстом приложения, и bean-компоненты внутри этого класса будут зарегистрированы в этом контексте приложения (так работает Spring). В вашем случае вы хотите войти в этот жизненный цикл и заставить Spring зарегистрировать bean-компонент в классе @Configuration с другим контекстом приложения, чем тот, который изначально связан с классом конфигурации.   -  person Andrei Stefan    schedule 25.08.2014
comment
В этом случае, я полагаю, вам может понадобиться BeanFactoryPostProcessor, зарегистрированный в контексте веб-приложения, и вручную зарегистрировать те компоненты, которые вам нужны, в корневом контексте. К сожалению, я не уверен, что это чистый способ, так как вам нужно будет вручную зарегистрировать определения компонентов внутри метода postProcessBeanFactory, используя что-то вроде getParentBeanFactory().registerSingleton(). Но в этом случае есть несколько вопросительных знаков, связанных с тем, насколько сложен ваш bean-компонент (есть ли у него зависимости?, является ли он сам зависимостью?, если да, то где находятся эти зависимости — root или web?).   -  person Andrei Stefan    schedule 25.08.2014
comment
@AndreiStefan Возможно, ты что-то понял. Я не боюсь регистрировать bean-компоненты вручную, так как я все равно планировал это сделать. Использование BeanFactoryPostProcessor может быть правильным способом изменить определения компонентов. Сегодня я обдумаю эту идею и отчитаюсь. Если у вас есть другие предложения, пожалуйста, поделитесь ими со мной :)   -  person Bart    schedule 25.08.2014
comment
Давайте продолжим обсуждение в чате.   -  person Bart    schedule 25.08.2014
comment
@Bart, я предоставил свой тестовый код в чате.   -  person Andrei Stefan    schedule 26.08.2014


Ответы (2)


Мне потребовалось некоторое время, чтобы четко понять, что вы хотите сделать, и последствия за его пределами. Поиск корневого контекста приложения из сервлета был бы легкой частью, context.getParentBeanFactory(), или непосредственно context.getParent() дает его немедленно в любом классе ApplicationContextAware или путем прямого внедрения ApplicationContext.

Сложность заключается в том, что во время инициализации контекста приложения сервлета корневой контекст приложения уже был обновлен. Если я посмотрю, что происходит в Tomcat:

  • во время развертывания контекст корневого приложения полностью инициализируется и обновляется
  • затем при первом запросе DispatcherServlet дочерний контекст инициализируется корневым контекстом в качестве родительского.

Это означает, что при инициализации контекста сервлета уже слишком поздно внедрять bean-компоненты в корневой контекст: все одноэлементные bean-компоненты уже созданы.

Там может быть обходной путь, все со своими недостатками:

  • register a new Configuration class in parent context and do a new refresh().IMHO, this would be the least bad solution as normally WebApplicationContextes support multiple refresh. The potentials problems are :
    • all other beans must be initialized once without the new beans : the configuration must be tolerant to non existing beans
    • другие компоненты (фильтры, безопасность, DAO и т. д.) уже могут быть запущены, и их необходимо тщательно протестировать на предмет горячего обновления контекста.
  • create an intermediate ApplicationContext with current root as parent containing beans that should not go into servlet application context, and then create the servlet application context with this intermediate context as parent.
    • no problem for the root application context that is not even aware of the whole operation
    • но ни один bean-компонент корневого контекста не может быть введен с любым новым bean-компонентом
  • зарегистрировать все новые bean-компоненты непосредственно в корневом контексте. Хорошо, все в порядке для корневой инициализации, и компоненты контекста сервлета будут иметь доступ ко всем компонентам. Но если один новый bean-компонент необходимо внедрить с bean-компонентом из контекста сервлета, вам придется сделать это вручную при инициализации контекста сервлета, с тщательными тестами (или молитвами), что его нельзя использовать до этого... и у вас будет несколько загрязнение корневого контекста bean-компонентами, относящимися только к сервлету
  • use only only root context and an empty servlet context.
    • ok, each bean has access to any other one
    • но это нарушает разделение между корневым контекстом и контекстом сервлета и добавляет некоторое загрязнение корневому контексту.

Мой вывод состоит в том, что наличие одной части конфигурации для двух разных контекстов приложения немного противоречит философии Spring, и я бы посоветовал вам использовать два отдельных класса конфигурации, один для корневого контекста, а другой для контекста сервлета. Но если хотите, я могу уточнить обновление корневого контекста из контекста сервлета (1-е решение).

Если вы хотите внедрить bean-компоненты в корневой контекст из функции, которая была бы объявлена ​​в контексте сервлета с единственной точкой конфигурации, вы можете использовать что-то вроде:

@Configuration
public class FeatureConfig implements ApplicationContextAware {
    static boolean needInit = true;

    @Override
    // Register the configuration class into parent context and refreshes all
    public void setApplicationContext(ApplicationContext ac) throws BeansException {
        AnnotationConfigWebApplicationContext parent = 
                (AnnotationConfigWebApplicationContext) ((AnnotationConfigWebApplicationContext) ac).getParent();
        if (needInit) { // ensure only one refresh
            needInit = false;
            parent.register(RootConfig.class);
            parent.refresh();
            ((AnnotationConfigWebApplicationContext) ac).refresh();
        }
    }

    @Configuration
    @Conditional(NoParentContext.class)
    // Can only be registered in root context
    public static class RootConfig {
        // configuration to be injected in root context ...
    }

    // special condition that the context is root
    public static class NoParentContext implements Condition {

        @Override
        public boolean matches(ConditionContext cc, AnnotatedTypeMetadata atm) {
            logger.debug(" {} parent {}", cc.getBeanFactory(), cc.getBeanFactory().getParentBeanFactory());
            return (cc.getBeanFactory().getParentBeanFactory() == null);
        }
    }

    // other beans or configuration that normally goes in servlet context
}

С таким классом @Configuration достаточно иметь аннотацию @import(FeatureConfig.class) в классе конфигурации для контекста приложения класса DispatcherServlet.

Но я не смог найти способ разрешить настройку до обычной инициализации контекста приложения сервлета. В результате любой bean-компонент из специальной конфигурации может быть внедрен только в корневой контекст с @Autowired(required=false), потому что корневой контекст будет обновляться дважды, первый раз без специального класса конфигурации, второй раз с ним.

person Serge Ballesta    schedule 26.08.2014
comment
Я пришел к выводу, что это больше проблем, чем стоит попробовать изменить корневой контекст. Однако ваш комментарий к аннотации @Conditional дал мне некоторое направление, и мне нравится гибкость. Теперь я разрешаю добавление bean-компонента, использующего @Bean, в корневую конфигурацию и позволяю конфигурации сервлета условно настроить свой собственный экземпляр. Если бы вы могли расширить @Conditional в своем ответе, я мог бы принять его как ответ, если не будет найдено лучшего решения (в чем я сомневаюсь). Вот суть моего текущего решения. - person Bart; 27.08.2014
comment
Спасибо за ваше время и усилия. Я очень ценю это. - person Bart; 27.08.2014
comment
@Bart: у меня может быть другое решение, использующее конфигурацию Java. Я надеюсь, что через пару дней смогу подробно рассказать об этом (включая подробности об использовании @Conditional) - person Serge Ballesta; 28.08.2014
comment
Прохладный! Таким образом, часть needIt предотвратит множественную регистрацию и удержит контексты от непрерывного цикла обновления. Я поиграю с этой идеей, как только получу изменения. Наслаждайтесь щедростью :) - person Bart; 30.08.2014

Насколько я понимаю, все, что вам нужно сделать, это предоставить пользовательскую конфигурацию, которая будет импортирована путем аннотирования класса @Configuration с помощью @EnableFeature. Затем вам просто нужно включить пользовательские компоненты в свой класс EnableFeatureConfiguration.

@Configuration
public class EnableFeatureConfiguration {

  @Bean
  public MyBean myBean() {
    return MyBean();
  }
}

Тогда ваш EnableFeature выглядит так:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(EnableFeatureConfiguration.class)
public @interface EnableFeature {
}

И это все. В проекте вы должны использовать:

@Configuration
@EnableFeature
public class MySpringConfig {
}
person Jakub Kubrynski    schedule 24.08.2014
comment
Это именно то, что я делаю прямо сейчас. Проблема в том, что аннотация обычно размещается поверх конфигурации контекста веб-приложения (как и должно быть). То, что я ищу, - это решение для размещения определенного компонента в корневом контексте. Сделать его доступным для служб в этом корневом контексте. - person Bart; 25.08.2014
comment
@Bart, это будет какой-то общедоступный фреймворк или просто индивидуальное решение? - person Jakub Kubrynski; 25.08.2014
comment
Я не уверен, что понимаю ваш вопрос, но это будет публичное решение. Не уверен, что можно назвать это фреймворком :) - person Bart; 25.08.2014