5.1.2 搭建 Spring MVC

基于图 5.1,看上去我们需要配置很多的组成部分。幸好,借助于最近几个 Spring 新版本的功能增强,开始使用 Spring MVC 变得非常简单了。现在,我们要使用最简单的方式来配置 Spring MVC:所要实现的功能仅限于运行我们所创建的控制器。在第 7 章中,我们会看一些其他的配置选项。

配置 DispatcherServlet

DispatcherServlet 是 Spring MVC 的核心。在这里请求会第一次接触到框架,它要负责将请求路由到其他的组件之中。

按照传统的方式,像 DispatcherServlet 这样的 Servlet 会配置在 web.xml 文件中,这个文件会放到应用的 WAR 包里面。当然,这是配置 DispatcherServlet 的方法之一。但是,借助于 Servlet 3 规范和 Spring 3.1 的功能增强,这种方式已经不是唯一的方案了,这也不是我们本章所使用的配置方法。

我们会使用 Java 将 DispatcherServlet 配置在 Servlet 容器中,而不会再使用 web.xml 文件。如下的程序清单展示了所需的 Java 类。

程序清单 5.1 配置 DispatcherServlet
package spittr.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }
  
  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] { RootConfig.class };
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class<?>[] { WebConfig.class };
  }

}

在我们深入介绍程序清单 5.1 之前,你可能想知道 spittr 到底是什么意思。这个类的名字是 SpittrWebAppInitializer,它位于名为 spittr.config 的包中。我稍后会对其进行介绍(在 5.1.3 小节中),但现在,你只需要知道我们所要创建的应用名为 Spittr。

要理解程序清单 5.1 是如何工作的,我们可能只需要知道扩展 AbstractAnnotationConfigDispatcherServletInitializer 的任意类都会自动地配置 DispatcherServlet 和 Spring 应用上下文,Spring 的应用上下文会位于应用程序的 Servlet 上下文之中。

AbstractAnnotationConfigDispatcherServletInitializer 剖析

如果你坚持要了解更多细节的话,那就看这里吧。在 Servlet 3.0 环境中,容器会在类路径中查找实现 javax.servlet.ServletContainerInitializer 接口的类, 如果能发现的话,就会用它来配置 Servlet 容器。

Spring 提供了这个接口的实现,名为 SpringServletContainerInitializer,这个类反过来又会查找实现 WebApplicationInitializer 的类并将配置的任务交给它们来完成。Spring 3.2 引入了一个便利的 WebApplicationInitializer 基础实现,也就 是 AbstractAnnotationConfigDispatcherServletInitializer 因为我们的 SpittrWebAppInitializer 扩展了 AbstractAnnotationConfigDispatcherServletInitializer(同时也就实现了 WebApplicationInitializer),因此当部署到 Servlet 3.0 容器中的时候,容器会自动发现它,并用它来配置 Servlet 上下文。

尽管它的名字很长,但是 AbstractAnnotationConfigDispatcherServletInitializer 使用起来很简便。在程序清单 5.1 中,SpittrWebAppInitializer 重写了三个方法。

第一个方法是 getServletMappings(),它会将一个或多个路径映射到Dispatcher-Servlet上。在本例中,它映射的是 "/",这表示它会是应用的默认 Servlet。它会处理进入应用的所有请求。

为了理解其他的两个方法,我们首先要理解 DispatcherServlet 和一个 Servlet 监听器(也就是 ContextLoaderListener)的关系。

两个应用上下文之间的故事

当 DispatcherServlet 启动的时候,它会创建 Spring 应用上下文,并加载配置文件或配置类中所声明的 bean。在以上程序中的 getServletConfigClasses() 方法中,我们要求 DispatcherServlet 加载应用上下文时,使用定义在 WebConfig 配置类(使用 Java 配置)中的 bean。

但是在 Spring Web 应用中,通常还会有另外一个应用上下文。另外的这个应用上下文是由 ContextLoaderListener 创建的。

我们希望 DispatcherServlet 加载包含 Web 组件的 bean,如控制器、视图解析器以及处理器映射,而 ContextLoaderListener 要加载应用中的其他 bean。这些 bean 通常是驱动应用后端的中间层和数据层组件。

实际上,AbstractAnnotationConfigDispatcherServletInitializer 会同时创建 DispatcherServlet 和 ContextLoaderListener。GetServletConfigClasses() 方法返回的带有 @Configuration 注解的类将会用来定义 DispatcherServlet 应用上下文中的 bean。getRootConfigClasses() 方法返回的带有 @Configuration 注解的类将会用来配置 ContextLoaderListener 创建的应用上下文中的 bean。

在本例中,根配置定义在 RootConfig 中,DispatcherServlet 的配置声明在 WebConfig 中。稍后我们将会看到这两个类的内容。

需要注意的是,通过 AbstractAnnotationConfigDispatcherServletInitializer 来配置 DispatcherServlet 是传统 web.xml 方式的替代方案。如果你愿意的话,可以同时包含 web.xml 和 AbstractAnnotationConfigDispatcherServletInitializer,但这其实并没有必要。

如果按照这种方式配置 DispatcherServlet,而不是使用 web.xml 的话,那唯一问题在于它只能部署到支持 Servlet 3.0 的服务器中才能正常工作,如 Tomcat 7 或更高版本。Servlet 3.0 规范在 2009 年 12 月份就发布了,因此很有可能你会将应用部署到支持 Servlet 3.0 的 Servlet 容器之中。如果你还没有使用支持 Servlet 3.0 的服务器,那么在 AbstractAnnotationConfigDispatcherServletInitializer 子类中配置 DispatcherServlet 的方法就不适合你了。你别无选择,只能使用 web.xml 了。我们将会在第 7 章学习 web.xml 和其他配置选项。但现在,我们先看一下以上程序中所引用的 WebConfig 和 RootConfig,了解一下如何启用 Spring MVC。

启用 Spring MVC

我们有多种方式来配置 DispatcherServlet,与之类似,启用 Spring MVC 组件的方法也不仅一种。以前,Spring 是使用 XML 进行配置的,你可以使用 <mvc:annotation-driven> 启用注解驱动的 Spring MVC。

我们会在第 7 章讨论 Spring MVC 配置可选项的时候,再讨论 <mvc:annotation>。不过,现在我们会让 Spring MVC 的搭建过程尽可能简单并基于 Java 进行配置。

我们所能创建的最简单的 Spring MVC 配置就是一个带有 @EnableWebMvc 注解的类:

package spittr.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
public class WebConfig {
}

这可以运行起来,它的确能够启用 Spring MVC,但还有不少问题要解决:

  • 没有配置视图解析器。如果这样的话,Spring 默认会使用 BeanNameViewResolver,这个视图解析器会查找 ID 与视图名称匹配的 bean,并且查找的 bean 要实现 View 接口,它以这样的方式来解析视图。

  • 没有启用组件扫描。这样的结果就是,Spring 只能找到显式声明在配置类中的控制器。

  • 这样配置的话,DispatcherServlet 会映射为应用的默认 Servlet,所以它会处理所有的请求,包括对静态资源的请求,如图片和样式表(在大多数情况下,这可能并不是你想要的效果)。

因此,我们需要在 WebConfig 这个最小的 Spring MVC 配置上再加一些内容,从而让它变得真正有用。如下程序清单中的 WebConfig 解决了上面所述的问题。

程序清单 5.2 最小但可用的 Spring MVC 配置
package spittr.web;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {

  @Bean
  public ViewResolver viewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".jsp");
    return resolver;
  }
  
  @Override
  public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    configurer.enable();
  }
}

在程序清单 5.2 中第一件需要注意的事情是 WebConfig 现在添加了 @ComponentScan 注解,因此将会扫描 spitter.web 包来查找组件。稍后你就会看到,我们所编写的控制器将会带有 @Controller 注解,这会使其成为组件扫描时的候选 bean。因此,我们不需要在配置类中显式声明任何的控制器。

接下来,我们添加了一个 ViewResolver bean。更具体来讲,是 InternalResourceViewResolver。我们将会在第 6 章更为详细地讨论视图解析器。我们只需要知道它会查找 JSP 文件,在查找的时候,它会在视图名称上加一个特定的前缀和后缀(例如,名为 home 的视图将会解析为 /WEB-INF/views/home.jsp)。

最后,新的 WebConfig 类还扩展了 WebMvcConfigurerAdapter 并重写了其 configureDefaultServletHandling() 方法。通过调用 DefaultServletHandlerConfigurer 的 enable() 方法,我们要求 DispatcherServlet 将对静态资源的请求转发到 Servlet 容器中默认的 Servlet 上,而不是使用 DispatcherServlet 本身来处理此类请求。

WebConfig 已经就绪,那 RootConfig 呢?因为本章聚焦于 Web 开发,而 Web 相关的配置通过 DispatcherServlet 创建的应用上下文都已经配置好了,因此现在的 RootConfig 相对很简单:

package spittr.config;

import java.util.regex.Pattern;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableMvc;

@Configuration
@ComponentScan(basePackages={"spittr"}, 
    excludeFilters={
        @Filter(type=FilterType.ANNOTATION, value=EnableWebMvc.class)
    })
public class RootConfig {
}

唯一需要注意的是 RootConfig 使用了 @ComponentScan 注解。这样的话,在本书中,我们就有很多机会用非 Web 的组件来充实完善 RootConfig。

现在,我们基本上已经可以开始使用 Spring MVC 构建 Web 应用了。此时,最大的问题在于,我们要构建的应用到底是什么。

Last updated