【问题标题】:Spring root and servlet context with Java config带有 Java 配置的 Spring root 和 servlet 上下文
【发布时间】:2015-04-02 07:45:48
【问题描述】:

我在 Servlet 3.0+ 环境中运行 Spring 应用程序,以使用所有 Java 配置以编程方式配置 servlet 上下文。我的问题(详情如下):如何构建一个项目以支持对根应用程序上下文和 Web 应用程序上下文的组件扫描,而无需重复组件初始化?

据我了解,注册 Spring bean 有两种上下文。首先,根上下文是非 servlet 相关组件所在的位置。例如批处理作业、DAO 等。其次,servlet 上下文是与 servlet 相关的组件所在的位置,例如控制器、过滤器等。

我已经实现了一个 WebApplicationInitializer 来注册这两个上下文,就像 WebApplicationInitializer 中的 JavaDoc 使用 AppConfig.class 和 DispatcherConfig.class 指定的那样。

我希望两者都能自动找到它们各自的组件,因此我已将 @ComponentScan 添加到两者(这导致我的 Hibernate 实体被启动两次)。 Spring 通过扫描一些指定的基本包来找到这些组件。这是否意味着我需要将所有与 DAO 相关的对象与控制器放在一个单独的包中?如果是这样,那会很不方便,因为我通常喜欢按功能(而不是类型)打包。

代码 sn-ps...

WebApplicationInitializer:

public class AppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) throws ServletException {
        // Create the 'root' Spring application context
        AnnotationConfigWebApplicationContext rootContext =
                new AnnotationConfigWebApplicationContext();
        rootContext.register(AppConfig.class);

        // Manage the lifecycle of the root application context
        container.addListener(new ContextLoaderListener(rootContext));

        // Create the dispatcher servlet's Spring application context
        AnnotationConfigWebApplicationContext dispatcherContext =
                new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(WebAppConfig.class);

        // Register and map the dispatcher servlet
        ServletRegistration.Dynamic dispatcher =
                container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
    }

}

应用配置:

@Configuration
@ComponentScan
public class AppConfig {
}

WebAppConfig:

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
@ComponentScan(basePackageClasses = AppConfig.class)
public class WebAppConfig extends WebMvcConfigurerAdapter {
}

【问题讨论】:

    标签: spring spring-mvc


    【解决方案1】:

    只需在每个配置中定义您要扫描的内容。一般来说,您的根配置应该扫描除@Controllers 之外的所有内容,而您的网络配置应该只检测@Controllers。

    您可以通过使用@ComponentScan 注释的includeFiltersexcludeFilters 属性来完成此操作。在使用包含过滤器时,在这种情况下,您还需要通过将useDefaultFilters 设置为false 来禁用使用默认过滤器。

    @Configuration
    @ComponentScan(excludeFilters={@Filter(org.springframework.stereotype.Controller.class)})
    public class AppConfig {}
    

    为了你的WebConfig

    @Configuration
    @EnableWebMvc
    @EnableSpringDataWebSupport
    @ComponentScan(basePackageClasses = AppConfig.class, useDefaultFilters=false, includeFilters={@Filter(org.springframework.stereotype.Controller.class)})
    public class WebAppConfig extends WebMvcConfigurerAdapter {}
    

    另外需要导入@Filter注解:

    import static org.springframework.context.annotation.ComponentScan.Filter;
    

    【讨论】:

    • 非常感谢,这也解决了按功能打包的问题!
    • AppConfig: @ComponentScan( excludeFilters={ @ComponentScan.Filter(Controller.class) } )
    • WebAppConfig: @ComponentScan( basePackageClasses=AppConfig.class , useDefaultFilters = false , includeFilters = { @ComponentScan.Filter(Controller.class) } )
    • 您可以为Filter 添加静态导入。
    • 这不会为 AppConfig 定义的每个 bean 创建两个副本,一个由根上下文创建,另一个由调度程序上下文创建?
    【解决方案2】:

    简短的回答是: 是的,您应该为每个上下文定义单独的组件扫描,从而对您的项目进行不同的建模并将 DAO 提取到单独的名称空间(包)中。

    较长的答案分为两部分,一个关于 servlet 上下文,另一个关于对项目建模。

    关于根上下文和 servlet 上下文:您已经正确定义了根上下文和 servlet 上下文的不同职责。这是大多数开发人员往往会错过的理解,它非常重要。
    只是为了进一步澄清这个主题,您可以创建一个根上下文和几个 (0+) servlet 上下文。根上下文中定义的所有 bean 都可用于所有 servlet 上下文,但每个 servlet 上下文中定义的 bean 不会与其他 servlet 上下文(不同的 spring 容器)共享。

    现在,由于您有多个 spring 容器并且您对相同的包使用组件扫描,因此会创建两次 bean,每个容器一次。 为避免这种情况,您可以做一些事情:

    1. 使用一个容器而不是两个容器,这意味着您可以只定义根容器或只定义 servlet 容器(我见过很多这样的应用程序)。
    2. 将您的 bean 分离到不同的位置,并为每个容器提供其独特的组件扫描。请记住,根容器中定义的所有 bean 都可以在 servlet 容器中使用(您不需要定义它们两次)。

    最后,关于项目建模的几句话。我个人喜欢分层编写我的项目,从而将我的代码分为控制器(应用层)、业务逻辑(bl 层)和 DAO(数据库层)。 像这样的建模在很多方面都有帮助,包括您遇到的这个根/servlet 上下文问题。 关于分层架构的信息有很多,这里是一个快速的例子:wiki-Multilayered_architecture

    【讨论】:

    • 按层打包是一种不好的做法,恕我直言,因为您松散了封装,您尝试将所有内容隐藏在服务层后面,但将所有内容公开,因为您已将所有内容逐层分离。
    • 我不同意,这完全取决于您如何模块化您的项目。例如,您可以提供一个 api 和多个实现。另一个例子是,正确建模可以产生多个构建块,并使您能够共享代码的不同部分。这意味着,在此示例中,您不仅可以通过“一组控制器”调用服务层,还可以通过多种协议(tcp、rest、thrift...)调用服务层,并且它们都使用相同的服务构建块,
    • 您应该结合这两种方法......您的业务应该由功能性和其他一切(web、ws、rest、jms)构成,是您功能的一个小型集成层。您应该保护您的域,并且在技术层面上,所有内容都是公开的,您的保护就在此处。
    • 什么是“一切都是公开的”?如果您正确地为项目建模,那么您就不会失去封装或保护!反之亦然,您获得了松散耦合和更精细的粒度。唯一公开的是层之间的 API。
    • 关于分层方法:我在所有层之间有明确的分离,每一层甚至是一个不同的 maven 模块,所以在我的情况下,我永远不会有一个包含所有用户逻辑的单一包。像这样的建模迫使我分离每一层的关注点。这意味着控制器层负责“外部”API(例如 REST),其中可以对服务层进行不同的建模(一层中的更改不会影响另一层),DAL 也是如此。它还可以在其他应用程序中重用层(例如,在其他应用程序中使用“用户”数据层)
    猜你喜欢
    • 2011-05-07
    • 2012-04-09
    • 1970-01-01
    • 2015-02-15
    • 1970-01-01
    • 2013-03-20
    • 2016-01-14
    • 2014-04-10
    • 1970-01-01
    相关资源
    最近更新 更多