Spring根和Servlet上下文的Java配置

5

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

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

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

我希望两者都可以自动查找它们各自的组件,因此我为两者都添加了 @ComponentScan(这导致我的 Hibernate 实体被初始化两次)。Spring 通过扫描某个指定的基础包来查找这些组件。那么,这是否意味着我需要将所有 DAO 相关的对象放在一个与控制器不同的包中?如果是这样的话,那就很不方便了,因为我通常会按功能(而不是按类型)进行打包。

代码片段...

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("/");
    }

}

AppConfig:

@Configuration
@ComponentScan
public class AppConfig {
}

WebAppConfig:

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

10

只需在每个配置文件中定义要扫描的内容即可。通常情况下,根配置应该扫描所有内容,但不包括@Controller,而Web配置应仅检测@Controller

您可以通过使用includeFiltersexcludeFilters属性来实现这一点。@ComponentScan注释。在这种情况下,当使用包含过滤器时,还需要通过将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;

太棒了,非常感谢!这也解决了按功能打包的问题! - Corey
1
应用配置: @ComponentScan(excludeFilters={ @ComponentScan.Filter(Controller.class) }) - Corey
WebAppConfig: @ComponentScan( basePackageClasses=AppConfig.class , useDefaultFilters = false , includeFilters = { @ComponentScan.Filter(Controller.class) } ) - Corey
1
你可以添加一个静态导入Filter - M. Deinum
1
这样做会不会创建 AppConfig 定义的每个 bean 的两个副本,一个由根上下文创建,另一个由调度程序上下文创建? - Derek Mahar

1
简短回答是:是的,您应该为每个上下文定义单独的组件扫描,从而以不同的方式对项目进行建模,并将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

你应该结合两种方法。你的业务应该按功能进行结构化,而其他所有内容(Web、WS、REST、JMS)都是为你的功能提供小型集成层。你应该保护你的领域,并通过技术层使一切都公开化,这样就可以实现保护。 - M. Deinum
这就是整个问题所在。你想要将所有东西都保护在服务后面,但是你的存储库/dao是公开的,你所有的“领域”对象都是公开的(包括构造函数),所以你基本上失去了你试图实现的一切。如果你将所有东西放在一个单独的功能包中,比如 com.yourapp.user,你只能使用你的 User 对象或 DTO(或细粒度事件对象)来使你的服务公开。这样你就有了真正的封装和保护。 - M. Deinum
分层架构意味着每个层都有自己的领域对象 - 它们不是共享的,因此没有一个地方可以放置所有对象。控制器具有DTO,BLL具有其bl域,数据层具有DAO。它们是不同的,不以相同的方式结构化。因此,DAO在dal之外不可见,服务层也无法看到它们。 - Amit
你能详细解释一下“只使用一个容器而不是两个,这意味着你可以只定义根容器或者只定义Servlet容器(我见过很多这样的应用)”吗?这是否意味着我需要将rootContext传递给DispatcherServlet构造函数? - Corey
1
关于分层方法:我在所有层之间有明确的分离,每个层甚至是一个不同的maven模块,所以在我的情况下,我永远不会有一个包含所有用户逻辑的单一包。这样建模强制我将每个层的问题分离开来。这意味着控制器层负责“外部”API(例如REST),而服务层可以进行不同的建模(一个层中的更改不影响另一个层),数据访问层也是如此。这还使得可以在其他应用程序中重复使用层(例如,在其他应用程序中使用“用户”数据访问层)。 - Amit
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接