Область видимости ‹context:component-scan/› и ‹context:property-placeholder/› в иерархическом контексте

Я прочитал:

Многокомпонентное сканирование

В чем разница между ApplicationContext и WebApplicationContext в Spring MVC?

аннотация @RequestMapping не работает, если ‹context:component-scan /› находится в контексте приложения, а не в контексте диспетчера (подробнее об этом один позже)

и несколько других, но ни один из них не отвечает на вопрос:

Почему область действия <context:component-scan.../> ограничена, когда она присутствует в контексте ROOT приложения Spring MVC?

Насколько я понимаю, он вызывает сканирование всех классов в указанных пакетах и ​​создает экземпляры любых bean-компонентов, стереотипных с @Component или любым из его подстереотипов (@Repository, @Service и @Controller).

Дано:

applicationContext.xml (корневой контекст)

<beans...>
    ...
    <context:component-scan base-package="com.myproject"/>
    <context:property-placeholder 
               ignore-resource-not-found="true" 
               location="classpath:default.properties, file:///etc/gallery/gallery.properties"/>
</beans>

main-servlet.xml (контекст сервлета)

<beans ...>
    ...
    <mvc:annotation-driven/>
    <mvc:resources mapping="/image/**"   location="file:/${gallery.location}" />
    <mvc:resources mapping="/css/**"     location="/css/"/>
    <mvc:resources mapping="/js/**"      location="/js/"/>
    <mvc:resources mapping="/images/**"  location="/images/"/>
    ...
</beans>

com/myproject/web/MainController.java

package com.myproject.web;
@Controller
public class MainController 
{
    ...

    @RequestMapping("/gallery/**")
    public String gallery(ModelMap modelMap, HttpServletRequest req, HttpServletResponse resp) throws IOException
    {
        ...
    }
}

Документы Spring утверждают, что любые bean-компоненты, созданные в корневом контексте, являются общими и доступными для отдельных контекстов приложения сервлета. Таким образом, два объявления <context:...> в корневом контексте должны привести к созданию bean-компонентов, видимых в контексте сервлета. Но, похоже, это не так. Мне нужно повторить ОБА <context:component-scan.../> и <context:property-placeholder.../> в контексте сервлета.

Отсутствие <context:component-scan.../> в контексте сервлета приводит к

Sep 15, 2015 10:08:16 AM org.springframework.web.servlet.PageNotFound noHandlerFound
WARNING: No mapping found for HTTP request with URI [/gallery/habitat/20150813] in DispatcherServlet with name 'main'
Sep 15, 2015 10:08:16 AM org.springframework.web.servlet.PageNotFound noHandlerFound
WARNING: No mapping found for HTTP request with URI [/error] in DispatcherServlet with name 'main'

указывая на то, что @Controller не был решен.

Отсутствие <context:property-placeholder.../> приводит к тому, что аннотация @Value с использованием ссылки на свойство не обрабатывается, что в моем случае приводит к некоторым неработающим ссылкам.

Поскольку обе эти директивы <context:.../> приводят к созданию экземпляров bean-компонентов, я не понимаю, почему bean-компоненты не видны в дочернем контексте, что прямо противоречит документации. Кроме того, разве наличие двух операторов component-scan не приводит к тому, что экземпляр bean-компонента контроллера создается дважды?

Что касается аннотации @RequestMapping, которая не работает, если ‹context:component-scan /› находится в контексте приложения, а не в контексте диспетчера, У меня есть <mvc:annotation-driven /> в контексте моего приложения, и ответы здесь не объясняют, ПОЧЕМУ необходимы два оператора component-scan.

Мне действительно неудобно использовать «магию», если я полностью не понимаю, как она работает, и не могу предсказать, как она будет себя вести, когда я что-то подправлю. Таким образом, «решение» «просто добавьте его в обоих местах и ​​двигайтесь дальше» неприемлемо.


person Jim Garrison    schedule 15.09.2015    source источник


Ответы (1)


<context:property-placeholder />

<context:property-placeholder /> регистрирует PropertySourcesPlaceholderConfigurer который является [BeanFactoryPostProcessor]. Ключ здесь BeanFactory, он действует на BeanFactory (таким образом ApplicationContext). Чтобы быть точным, он работает с BeanFactory, в котором он определен. Не с родительским или дочерним контекстами. Следовательно, вам необходимо зарегистрировать его в обоих контекстах.

Есть один недостаток в том, что 2 одинаковых <context:property-placeholder /> будут загружать один и тот же ресурс, и вы в конечном итоге дважды загрузите один и тот же файл свойств. Чтобы устранить это, объедините <context:property-placeholder /> с элементом <util:properties />. Последний загружает файл(ы) свойств и предоставляет их как bean-компонент в контексте, вы можете связать этот bean-компонент с <context:property-placeholder />, используя атрибут properties-ref. Вы загружаете свойства только в корневом контексте и просто ссылаетесь на них в дочерних контекстах.

Корневой контекст

<util:properties id="appProperties" location="classpath:default.properties, file:///etc/gallery/gallery.properties" ignore-resource-not-found="true" />
<context:property-placeholder properties-ref="appProperties" />

Дочерний контекст

<context:property-placeholder properties-ref="appProperties" />

<mvc:annotation-driven />

Что касается <mvc:annotation-driven />, который регистрирует, среди прочего, RequestMappingHandlerMapper, этот bean-компонент отвечает за обнаружение методов, аннотированных @RequestMapping в @Controller аннотированных классах. По умолчанию это делается в ApplicationContext, это определено НЕ в родительских контекстах. Вы можете установить detectHandlerMethodsInAncestorContexts на true, чтобы это произошло.

Как правило, вы можете сказать, что ваш корневой контекст должен содержать все глобальные приложения, такие как службы, репозитории, инфраструктурные компоненты, такие как источники данных и т. д. Дочерние контексты (загружаемые DispatcherServlet должны содержать только веб-материалы, такие как @Controllers.).

Просто скопировать и вставить <context:component-scan />, буквально продублировав его, — плохая (очень плохая) идея. Так как это приведет к двойному созданию экземпляров всех ваших bean-компонентов и может привести к проблемам с транзакциями, памятью и т. д. Поскольку такие вещи, как <tx:annotation-driven />, работают с АОП, а АОП также применяется с использованием BeanFactoryPostProcessor и BeanPostProcessor, как и поддержка свойств.

Корневой контекст

<context:component-scan base-package="com.myproject">
    <context:exclude-filters type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

Дочерний контекст

<context:component-scan base-package="com.myproject" use-default-filters="false">
    <context:include-filters type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
person M. Deinum    schedule 15.09.2015
comment
Спасибо, это развеяло туман. Что касается property-placeholder, означает ли это, что в памяти будет два набора свойств? Есть ли способ загрузить их один раз и поделиться коллекцией между всеми контекстами? Кроме того, спасибо за подтверждение того, что дублирование component-scan — плохая идея. Я думал, что это так, но не мог найти ничего в документах, чтобы предложить это. - person Jim Garrison; 16.09.2015
comment
Я добавил некоторую информацию о заполнителе свойства и добавил пример конфигурации для пояснений. - person M. Deinum; 16.09.2015
comment
Идеально! Теперь все начинает проясняться, спасибо. - person Jim Garrison; 16.09.2015