<context:component-scan.../> 和 <context:property-placeholder.../> 在分层上下文中的范围

Scope of <context:component-scan.../> and <context:property-placeholder.../> in hierarchical contexts

我已阅读:

Multiple component-scan

What is the difference between ApplicationContext and WebApplicationContext in Spring MVC?

@RequestMapping annotation not working if <context:component-scan /> is in application context instead of dispatcher context(稍后详细介绍)

和其他几个人,但其中 none 回答了问题:

<context:component-scan.../> 出现在 Spring MVC 应用程序的 ROOT 上下文中时,为什么它的范围受到限制?

我的理解是,它会导致扫描指定包中的所有 类,并实例化任何使用 @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(servlet 上下文)

<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 都是共享的,并且可用于各个 servlet 应用程序上下文。因此,根上下文中的两个 <context:...> 声明应该生成在 servlet 上下文中可见的 bean。但事实并非如此。我需要在 servlet 上下文中重复 <context:component-scan.../><context:property-placeholder.../>

在 servlet 上下文中省略 <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 annotation not working if <context:component-scan /> is in application context instead of dispatcher context,我的应用上下文中确实有 <mvc:annotation-driven />,这里的答案没有解释为什么需要两个 component-scan 语句。

使用 "magic" 我真的很不舒服,除非我完全理解它是如何工作的 并且可以预测当我调整某些东西时它将如何表现 。所以"just add it in both places and move on"的"solution"是不可接受的。

<context:property-placeholder />

<context:property-placeholder /> 注册了一个 PropertySourcesPlaceholderConfigurer,这是一个 [BeanFactoryPostProcessor]。这里的关键是BeanFactory,它作用于BeanFactoryApplicationContext就是这样的东西)。准确地说,它在其定义的 BeanFactory 上运行。而不是在父上下文或子上下文上。因此,您需要在两种情况下都进行注册。

有 2 个相同的缺点 <context:property-placeholder /> 两者都会加载相同的资源,并且您最终会加载相同的属性文件两次。要消除这种情况,请将 <context:property-placeholder /><util:properties /> 元素结合使用。后者加载属性文件并将它们作为 bean 在上下文中公开,您可以使用 properties-ref 属性将此 bean 连接到 <context:property-placeholder />。您只需在根上下文中加载属性并在子上下文中简单地引用它们。

根上下文

<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负责检测在@Controller注解类中用@RequestMapping注解的方法].默认情况下,它在 ApplicationContext 中执行此操作,它在父上下文中的 NOT 中定义。您可以将 detectHandlerMethodsInAncestorContexts 属性 设置为 true 来实现这一点。

根据经验,你可以说你的根上下文应该包含所有应用程序全局,如服务、存储库、基础设施 bean,如数据源等。子上下文(由 DispatcherServlet 加载应该只包含 web 相关@Controllers 等材料)。

只是复制粘贴 <context:component-scan />,从字面上复制它,是一个糟糕(非常糟糕)的主意。因为这将导致将所有 bean 实例化两次,并可能导致事务、内存等问题。因为 <tx:annotation-driven /> 与 AOP 一起使用,并且 AOP 也使用 BeanFactoryPostProcessorBeanPostProcessor 应用就像 属性 支持一样。

根上下文

<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>