【译】DispatcherServlet

1.1. DispatcherServlet

见 Reactive 技术栈中的等效内容

Spring MVC和其他许多Web框架一样,是围绕前端控制器模式设计的,其中一个中央 Servlet,即 DispatcherServlet,为请求处理提供了一个共享算法,而实际工作则由可配置的委托组件执行。这种模式很灵活,支持多样化的工作流程。

DispatcherServlet 和其他Servlet一样,需要根据 Servlet 规范,使用Java配置或在 web.xml 中进行声明和映射。反过来,DispatcherServlet 使用Spring配置来发现它在请求映射、视图解析、异常处理 方面需要的委托组件。

  • 下面这个Java配置的例子注册并初始化了 DispatcherServlet,它是由Servlet容器自动检测到的(见 Servlet 配置):

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

除了直接使用 ServletContext API外,你还可以扩展 AbstractAnnotationConfigDispatcherServletInitializer 并覆写特定的方法(见 上下文(Context)层次结构下的例子)。

对于编程式用例,GenericWebApplicationContext 可以作为 AnnotationConfigWebApplicationContext 的替代。请参阅 GenericWebApplicationContext

javadoc 了解详情。

  • 以下是 web.xml 配置的例子,它注册并初始化了 DispatcherServlet:
<web-app>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/app-context.xml</param-value>
  </context-param>

  <servlet>
    <servlet-name>app</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value></param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>app</servlet-name>
    <url-pattern>/app/*</url-pattern>
  </servlet-mapping>

</web-app>

Spring Boot遵循不同的初始化顺序。Spring Boot不是挂入Servlet容器的生命周期,而是使用Spring配置来启动自己和嵌入式Servlet容器。FilterServlet 声明在Spring配置中被检测到,并在Servlet容器中注册。更多细节请参见 Spring Boot文档

1.1.1. 上下文(Context)层次结构

DispatcherServlet 期望有一个 WebApplicationContext(普通 ApplicationContext 的扩展)用于自己的配置。WebApplicationContext 有一个与 ServletContext 和与之相关的 Servlet 的链接。它也被绑定到 ServletContext,这样应用程序就可以使用 RequestContextUtils 上的静态方法来查询 WebApplicationContext,如果他们需要访问它的话。

对于许多应用程序,有一个单一的 WebApplicationContext 是简单的,也是足够的。也可以有一个上下文层次结构,一个根 WebApplicationContext 被多个 DispatcherServlet(或其他 Servlet)实例共享,每个实例都有自己的子 WebApplicationContext 配置。参见 ApplicationContext的附加功能 ,以了解更多关于上下文层次结构的特性。

根(root) WebApplicationContext 通常包含基础设施Bean,例如需要在多个 Servlet 实例中共享的数据存储库和业务服务。这些Bean有效地被继承,并且可以在 Servlet 特定的子 WebApplicationContext 中被重写(也就是重新声明),该 WebApplicationContext 通常包含给定 Servlet 的本地Bean。下面的图片显示了这种关系:

下面的例子配置了一个 WebApplicationContext 的层次结构:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/app1/*" };
    }
}

如果不需要应用上下文层次结构,应用程序可以通过 getRootConfigClasses() 返回所有配置,并从 getServletConfigClasses() 返回 null 配置。

下面的例子显示了 web.xml 的等价物:

<web-app>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/root-context.xml</param-value>
  </context-param>

  <servlet>
    <servlet-name>app1</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/app1-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>app1</servlet-name>
    <url-pattern>/app1/*</url-pattern>
  </servlet-mapping>

</web-app>

如果不需要 application context 层次结构,应用程序可以只配置一个 “root” context,并将 contextConfigLocation Servlet参数留空。

1.1.2. 特殊的 Bean 类型

见 Reactive 技术栈中的等效内容

DispatcherServlet 委托给特殊的Bean来处理请求并呈现适当的响应。我们所说的 "特殊Bean" 是指实现框架契约的Spring管理的 Object 实例。这些实例通常有内置的约定,但你可以自定义它们的属性,并扩展或替换它们。

下表列出了由 DispatcherServlet 检测到的特殊Bean类:

Bean 类型

说明

HandlerMapping

将一个请求和一个用于前后处理的拦截器

列表一起映射到一个处理程序(handler)。该映射基于一些标准,其细节因 HandlerMapping 的实现而异。两个主要的 HandlerMapping 实现是 RequestMappingHandlerMapping(支持 @RequestMapping 注解的方法)和 SimpleUrlHandlerMapping(维护 URI path pattern 到处理程序(handler)的明确注册)。

HandlerAdapter

帮助 DispatcherServlet 调用映射到请求的处理程序(handler),不管处理程序实际上是如何被调用的。例如,调用一个有注解的controller需要解析注解的问题。HandlerAdapter 的主要目的是将 DispatcherServlet 从这些细节中屏蔽掉。

HandlerExceptionResolver

解决异常的策略,可能将它们映射到处理程序、HTML error 视图或其他目标。请参阅 Exceptions

ViewResolver

将处理程序返回的基于 String 的逻辑视图名称解析为实际的 View(视图),并将其渲染到响应。参见 视图(View)解析

视图技术

LocaleResolver

, LocaleContextResolver

解析客户端使用的 Locale,可能还有他们的时区,以便能够提供国际化的视图。参见 Locale

ThemeResolver

解析你的web应用可以使用的主题(theme)--例如,提供个性化的布局。见 主题(Theme)

MultipartResolver

在一些 multipart 解析库的帮助下,解析一个 multipart 请求(例如,浏览器表单文件上传)的抽象。参见 Multipart 解析器

FlashMapManager

存储和检索 "输入" 和 "输出" FlashMap,可用于将属性从一个请求传递到另一个请求,通常跨越重定向。见 Flash Attributes

1.1.3. Web MVC 配置

见 Reactive 技术栈中的等效内容

应用程序可以声明在 特殊的 Bean 类型 中列出的处理请求所需的基础设施 Bean。 DispatcherServlet 检查 WebApplicationContext 中的每个特殊Bean。如果没有匹配的Bean类型,它将回到 DispatcherServlet.properties 中所列的默认类型。

在大多数情况下,MVC 配置 是最好的起点。它用Java或XML声明所需的Bean,并提供一个更高级别的配置回调(callback)API来定制它。

Spring Boot依靠MVC Java配置来配置Spring MVC,并提供许多额外的便利选项。

1.1.4. Servlet 配置

在Servlet环境中,你可以选择以编程方式配置Servlet容器,作为一种选择,或者与 web.xml 文件相结合。下面的例子注册了一个 DispatcherServlet:

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

WebApplicationInitializer 是Spring MVC提供的一个接口,它可以确保你的实现被检测到并自动用于初始化任何Servlet 3容器。WebApplicationInitializer 的一个抽象基类实现名为 AbstractDispatcherServletInitializer,通过覆盖指定 Servlet 映射(mapping)和 DispatcherServlet 配置位置的方法,使得注册 DispatcherServlet 更加容易。

对于使用基于Java的Spring配置的应用程序,建议这样做,如下例所示:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

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

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

如果你使用基于XML的Spring配置,你应该直接从 AbstractDispatcherServletInitializer 扩展,如下例所示:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

AbstractDispatcherServletInitializer 还提供了一种方便的方法来添加 Filter 实例,并让它们自动映射到 DispatcherServlet,正如下面的例子所示:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] {
            new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
    }
}

每个 filter 都根据其具体类型添加了一个默认名称(name),并自动映射到 DispatcherServlet。

AbstractDispatcherServletInitializer 的 isAsyncSupported protected 方法提供了一个单一的地方来启用对 DispatcherServlet 和所有映射到它的 filter 的异步支持。默认情况下,这个标志被设置为 true。

最后,如果你需要进一步定制 DispatcherServlet 本身,你可以复写 createDispatcherServlet 方法。

1.1.5. 流程(Processing)

见 Reactive 技术栈中的等效内容

DispatcherServlet 处理请求的方式如下:

  • WebApplicationContext 被搜索并作为一个属性(attribute)绑定在请求(request)中,controller和进程中的其他元素可以使用。它默认被绑定在 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE key 下。
  • locale 解析器被绑定到请求上,以便让流程中的元素在处理请求(渲染视图、准备数据等)时解析要使用的 locale。如果你不需要locale解析,你就不需要 locale 解析器(resolver)。
  • theme 解析器被绑定在请求上,以让诸如视图等元素决定使用哪个主题。如果你不使用主题,你可以忽略它。
  • 如果你指定了一个 multipart file 解析器,请求将被检查为 multipart file。如果发现了multipart,该请求将被包裹在一个 MultipartHttpServletRequest 中,以便由流程中的其他元素进一步处理。关于 multipart 处理的进一步信息,请参见 Multipart 解析器
  • 一个适当的处理程序(handler)被搜索到。如果找到一个处理程序,与该处理程序相关的执行链(预处理程序、后处理程序和 controller)被运行,以准备渲染的模型(model)。另外,对于有注解的 controller,响应可以被渲染(在 HandlerAdapter 中)而不是返回一个视图。
  • 如果有 model 返回,视图就会被渲染。如果没有返回 model(也许是由于预处理器或后处理器拦截了请求,也许是出于安全原因),就不会渲染视图,因为该请求可能已经被满足了。

在 WebApplicationContext 中声明的 HandlerExceptionResolver Bean被用来解决请求处理过程中抛出的异常。这些异常解析器允许自定义处理异常的逻辑。更多的细节请看 Exceptions

对于HTTP缓存支持,处理程序可以使用 WebRequest 的 checkNotModified 方法,以及《Controller 的HTTP缓存》中描述的注解 controller 的进一步选项。

你可以通过在 web.xml 文件的 Servlet 声明中添加 Servlet 初始化参数(init-param 元素)来定制单个 DispatcherServlet 实例。下表列出了支持的参数:

参数

说明

contextClass

实现 ConfigurableWebApplicationContext 的类,将由该Servlet实例化和本地配置。默认情况下,使用 XmlWebApplicationContext。

contextConfigLocation

传递给上下文实例(由 contextClass 指定)的字符串,表示可以在哪里找到上下文。该字符串可能由多个字符串组成(使用逗号作为分隔符)以支持多个上下文。如果多个上下文位置的bean被定义了两次,那么最新的位置优先。

namespace

WebApplicationContext 的命名空间。默认为 [servlet-name]-servlet。

throwExceptionIfNoHandlerFound

当一个请求没有找到处理程序(handler)时,是否会抛出 NoHandlerFoundException。然后可以用 HandlerExceptionResolver(例如,通过使用 @ExceptionHandler controller 方法)来捕获该异常,并像其他一样处理。默认情况下,这被设置为 false,在这种情况下,DispatcherServlet 将响应状态设置为404(NOT_FOUND)而不引发异常。请注意,如果 default servlet handling

也被配置了,未解析的请求总是被转发到 default servlet,并且永远不会出现404。

1.1.6. 路径匹配(Path Matching)

Servlet API将完整的请求路径作为 requestURI 公开,并进一步将其细分为 contextPath、servletPath 和 pathInfo,其值因Servlet的映射方式而异。从这些输入中,Spring MVC 需要确定用于映射处理程序(handler)的查找路径,如果适用的话,应该排除 contextPath 和任何 servletMapping 前缀。

servletPath 和 pathInfo 是经过解码的,这使得它们不可能直接与完整的 requestURI 进行比较以得出 lookupPath,这使得有必要对 requestURI 进行解码。然而,这也引入了自己的问题,因为路径可能包含编码的保留字符,如 "/" 或 ";",在它们被解码后又会改变路径的结构,这也会导致安全问题。此外,Servlet容器可能会在不同程度上对 servletPath 进行规范化处理,这使得它进一步无法对 requestURI 进行 startsWith 比较。

这就是为什么最好避免依赖基于前缀的 servletPath 映射类型所带来的 servletPath。如果 DispatcherServlet 被映射为带有 "/" 的默认 Servlet,或者没有 "/*" 的前缀,并且Servlet容器是4.0以上的,那么Spring MVC就能够检测到Servlet映射类型,并完全避免使用 servletPath 和 pathInfo。在3.1的Servlet容器上,假设有相同的Servlet映射类型,可以通过MVC配置中的 路径(Path)匹配,提供一个 alwaysUseFullPath=true 的 UrlPathHelper 来实现。

幸运的是,默认的Servlet映射 "/" 是一个不错的选择。然而,仍然有一个问题,即 requestURI 需要被解码,以便能够与 controller 映射(mapping)进行比较。这也是不可取的,因为有可能对改变路径结构的保留字符进行解码。如果这些字符不被期望,那么你可以拒绝它们(就像Spring Security HTTP 防火墙),或者你可以将 UrlPathHelper 配置为 urlDecode=false,但 controller 映射需要与编码后的路径相匹配,这可能并不总是很好。此外,有时 DispatcherServlet 需要与另一个Servlet共享URL空间,可能需要按前缀进行映射。

在使用 PathPatternParser 和解析的pattern时,上述问题得到了解决,因为它可以替代 AntPathMatcher 的字符串路径匹配。PathPatternParser 从5.3版本开始就可以在Spring MVC中使用,并且从6.0版本开始默认启用。AntPathMatcher 需要对查找路径进行解码或对 controller 映射进行编码,与此不同的是,经过解析的 PathPattern 与称为 RequestPath 的路径的解析表示相匹配,一次一个路径段。这允许对路径段的值进行单独解码和消毒,而不存在改变路径结构的风险。Parsed PathPattern 还支持使用 servletPath 前缀映射,只要使用Servlet路径映射,并且前缀保持简单,即没有编码的字符。关于pattern语法的细节和比较,见 Pattern 比较

1.1.7. 拦截

所有 HandlerMapping 的实现都支持 handler 拦截器,当你想对某些请求应用特定的功能时,这些拦截器是非常有用的—例如,检查一个 principal。拦截器必须实现 org.springframework.web.servlet 包中的 HandlerInterceptor,它有三个方法,应该可以提供足够的灵活性来进行各种预处理和后处理:

  • preHandle(..): 在实际 handler 运行之前
  • postHandle(..): handler 运行后
  • afterCompletion(..): 在整个请求完成后

preHandle(..) 方法返回一个boolean值。你可以使用这个方法来中断或继续执行链的处理。当这个方法返回 true 时,handler 执行链继续进行。当它返回 false 时, DispatcherServlet 认为拦截器本身已经处理了请求(例如,渲染了一个适当的视图),并且不继续执行其他拦截器和执行链中的实际 handler。

关于如何配置拦截器的例子,请参见MVC配置部分的 拦截器。你也可以通过使用个别 HandlerMapping 实现的setters来直接注册它们。

postHandle 方法在 @ResponseBody 和 ResponseEntity 方法中用处不大,因为这些方法的响应是在 HandlerAdapter 中和 postHandle 之前写入和提交的。这意味着对响应进行任何修改都太晚了,比如添加一个额外的 header。对于这种情况,你可以实现 ResponseBodyAdvice,并把它声明为一个 Controller Advice Bean,或者直接在 RequestMappingHandlerAdapter 上配置它。

1.1.8. Exceptions

见 Reactive 技术栈中的等效内容

如果在请求映射过程中发生异常或从请求处理程序(如 @Controller)抛出异常, DispatcherServlet 会委托给处理程序异常解析器(HandlerExceptionResolver)Bean链来解析异常并提供替代处理,这通常是一个错误响应。

下表列出了可用的 HandlerExceptionResolver 实现:

HandlerExceptionResolver

说明

SimpleMappingExceptionResolver

异常类名称和错误视图名称之间的映射。对于在浏览器应用程序中渲染错误页面非常有用。

DefaultHandlerExceptionResolver

解析由Spring MVC引发的异常,并将其映射到HTTP状态码。也请参见替代的 ResponseEntityExceptionHandler 和 Error 响应(Response)

ResponseStatusExceptionResolver

解析带有 @ResponseStatus 注解的异常,并根据注解中的值将其映射到HTTP状态码。

ExceptionHandlerExceptionResolver

通过调用 @Controller 或 @ControllerAdvice 类中的 @ExceptionHandler 方法来解析异常。参见 @ExceptionHandler methods

解析器(Resolver)链

你可以通过在Spring配置中声明多个 HandlerExceptionResolver Bean并根据需要设置它们的 order 属性来形成一个异常解析器链。order 属性越高,异常解析器的定位就越靠后。

HandlerExceptionResolver 的约定,它可以返回:

  • 一个指向错误视图的 ModelAndView。
  • 如果异常在解析器中被处理,则是一个空(empty)的 ModelAndView。
  • 如果异常仍未被解决,则为 null,供后续的解析器尝试,如果异常仍在最后,则允许冒泡到Servlet容器中。

MVC 配置 自动为默认的Spring MVC异常、@ResponseStatus 注解的异常以及 @ExceptionHandler 方法的支持声明了内置解析器。你可以自定义该列表或替换它。

容器错误页面(Error Page)

如果一个异常仍然没有被任何 HandlerExceptionResolver 解析,因此,任其传播,或者如果响应状态被设置为错误状态(即4xx,5xx),Servlet容器可以在HTML中渲染一个默认的错误页面。为了定制容器的默认错误页面,你可以在 web.xml 中声明一个错误页面映射。下面的例子显示了如何做到这一点:

<error-page>
  <location>/error</location>
</error-page>

鉴于前面的例子,当一个异常冒出来或者响应有错误状态时,Servlet容器在容器内向配置的URL(例如,/error)进行 ERROR 调度。然后由 DispatcherServlet 进行处理,可能会将其映射到一个 @Controller,它可以被实现为返回一个带有model的错误视图名称或渲染一个JSON响应,如下例所示:

@RestController
public class ErrorController {

    @RequestMapping(path = "/error")
    public Map<String, Object> handle(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        map.put("status", request.getAttribute("jakarta.servlet.error.status_code"));
        map.put("reason", request.getAttribute("jakarta.servlet.error.message"));
        return map;
    }
}

Servlet API并没有提供在Java中创建错误页面映射的方法。然而,你可以同时使用 WebApplicationInitializer 和一个最小的 web.xml

1.1.9. 视图(View)解析

见 Reactive 技术栈中的等效内容

Spring MVC定义了 ViewResolver 和 View 接口,让你在浏览器中渲染模型,而不需要绑定到特定的视图技术。ViewResolver 提供了视图名称和实际视图之间的映射。View 解决了在移交给特定视图技术之前的数据准备问题。

下表提供了关于 ViewResolver 层次结构的更多细节:

ViewResolver

说明

AbstractCachingViewResolver

AbstractCachingViewResolver 的子类会缓存它们所解析的视图实例。缓存可以提高某些视图技术的性能。你可以通过将 cache 属性设置为 false 来关闭缓存。此外,如果你必须在运行时刷新某个视图(例如,当 FreeMarker 模板被修改时),你可以使用 removeFromCache(String viewName, Locale loc) 方法。

UrlBasedViewResolver

ViewResolver 接口的简单实现,无需明确的映射定义就能实现逻辑视图名称与URL的直接解析。如果你的逻辑名称与你的视图资源的名称直接匹配,而不需要任意的映射,这就很合适。

InternalResourceViewResolver

UrlBasedViewResolver 的方便子类,支持 InternalResourceView(实际上是Servlets和JSP)和子类,如 JstlView。你可以通过使用 setViewClass(..) 为这个解析器生成的所有视图指定视图类。请参阅 UrlBasedViewResolver

javadoc 了解详情。

FreeMarkerViewResolver

UrlBasedViewResolver 的方便子类,支持 FreeMarkerView 和它们的自定义子类。

ContentNegotiatingViewResolver

ViewResolver 接口的实现,根据请求文件名或 Accept 头来解析视图。参见 内容协商

BeanNameViewResolver

ViewResolver 接口的实现,它将视图名称解释为当前应用程序上下文中的bean名称。这是一个非常灵活的变体,可以根据不同的视图名称混合和匹配不同的视图类型。每个这样的 View 都可以被定义为Bean,例如在XML或配置类中。

处理(Handling)

见 Reactive 技术栈中的等效内容

你可以通过声明一个以上的解析器Bean来实现视图解析器链,如果有必要,还可以通过设置 order 属性来指定排序。记住,顺序属性越高,视图解析器在链中的位置就越靠后。

ViewResolver 的约定,它可以返回 null 以表示找不到视图。然而,在JSP和 InternalResourceViewResolver 的情况下,弄清JSP是否存在的唯一方法是通过 RequestDispatcher 进行调度。因此,你必须始终将 InternalResourceViewResolver 配置为视图解析器整体顺序中的最后一个。

配置视图解析就像在Spring配置中添加 ViewResolver Bean一样简单。MVC 配置视图(View)解析器 和添加无逻辑的 视图控制器(View Controller) 提供了专门的配置API,这对于没有控制器逻辑的HTML模板渲染非常有用。

重定向

见 Reactive 技术栈中的等效内容

视图名称中特殊的 redirect: 前缀可以让你执行重定向。 UrlBasedViewResolver(和它的子类)将其识别为一个需要重定向的指令。视图名称的其余部分是重定向的URL。

净效果与controller返回 RedirectView 的效果相同,但现在controller本身可以在逻辑视图名称方面操作。逻辑视图名称(如 redirect:/myapp/some/resource)是相对于当前 Servlet context 重定向的,而 redirect:https://myhost.com/some/arbitrary/path 则是重定向到一个绝对URL。

请注意,如果一个 controller 方法被 @ResponseStatus 注解,该注解值优先于 RedirectView 设置的响应状态。

转发

你也可以对最终由 UrlBasedViewResolver 和子类解析的视图名称使用一个特殊的 forward: 前缀。这将创建一个 InternalResourceView,它做一个 RequestDispatcher.forward()。因此,这个前缀对 InternalResourceViewResolver 和 InternalResourceView(用于JSP)没有用处,但如果你使用另一种视图技术,但仍然想强制转发一个资源,由Servlet/JSP引擎处理,那么它就会有帮助。请注意,你也可以用链式的多个视图解析器来代替。

内容协商

见 Reactive 技术栈中的等效内容

ContentNegotiatingViewResolver 本身并不解析视图,而是委托给其他视图解析器,并选择与客户端请求的表示相近的视图。表示法可以从 Accept 头或查询参数(例如,"/path?format=pdf")确定。

ContentNegotiatingViewResolver 通过比较请求的 Content-Type 和与其每个 ViewResolvers 相关的 View 所支持的媒体类型(也称为 Content-Type)来选择一个合适的 View 来处理请求。列表中第一个具有兼容的内容类型的视图将表示返回给客户端。如果 ViewResolver 链不能提供兼容的视图,就会查询通过 DefaultViews 属性指定的视图列表。后面这个选项适用于 singleton Views,它可以渲染当前资源的适当表示,而不考虑逻辑视图的名称。Accept 头可以包括通配符(例如 text/*),在这种情况下,Content-Type 为 text/xml 的 View 是一个兼容的匹配。

有关配置细节,请参见 MVC 配置 下的 视图(View)解析器

1.1.10. Locale

Spring架构的大多数部分都支持国际化,正如Spring Web MVC框架一样。 DispatcherServlet 让你通过使用客户端的 locale 自动解析消息(message)。这是由 LocaleResolver 对象完成的。

当一个请求进来时,DispatcherServlet 会寻找一个 locale 解析器,如果它找到一个,它就会尝试用它来设置locale。通过使用 RequestContext.getLocale() 方法,你总是可以检索到由locale解析器解析的locale。

除了自动解析 locale,你还可以在处理程序(handler)映射中附加一个拦截器(关于处理程序映射拦截器的更多信息,见 拦截),以便在特定情况下(例如,基于请求中的参数)改变语言。

Locale 解析器和拦截器定义在 org.springframework.web.servlet.i18n 包中,并以正常方式在 application context 中进行配置。以下是Spring 中 locale 解析器。

时区(Time Zone)

除了获得客户端的locale之外,知道它的时区也常常是有用的。 LocaleContextResolver 接口提供了对 LocaleResolver 的扩展,让解析器提供一个更丰富的 LocaleContext,其中可能包括时区信息。

当可用时,用户的 TimeZone 可以通过使用 RequestContext.getTimeZone() 方法获得。时区信息会被任何与Spring的 ConversionService 注册的 Date/Time Converter 和格 Formatter 对象自动使用。

Header 解析器

这个 locale resolver 检查由客户端(例如,一个Web浏览器)发送的请求中的 accept-language 头。通常,这个头字段包含了客户的操作系统的 locale。请注意,这个解析器不支持时区信息。

Cookie 解析器

这个locale解析器检查客户端上可能存在的 Cookie,看是否指定了 Locale 或 TimeZone。如果有,它就会使用指定的细节。通过使用这个locale 解析器的属性,你可以指定 cookie name 以及 maximum age。下面的例子定义了一个 CookieLocaleResolver:

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">

  <property name="cookieName" value="clientlanguage"/>

  <!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
  <property name="cookieMaxAge" value="100000"/>

</bean>

下表描述了 CookieLocaleResolver 的属性:

属性

默认

说明

cookieName

类名 + LOCALE

cookie 名

cookieMaxAge

Servlet 容器的默认值

一个cookie在客户端上持续存在的最长时间。如果指定为 -1,cookie将不会被持久化。它只在客户端关闭浏览器之前可用。

cookiePath

/

将cookie的可见性限制在你网站的某个部分。当 cookiePath 被指定时,cookie只对该路径和它下面的路径可见。

Session 解析器

SessionLocaleResolver 让你从可能与用户请求有关的会话中检索 Locale 和 TimeZone。与 CookieLocaleResolver 不同的是,这个策略将本地选择的 locale 设置存储在 Servlet 容器的 HttpSession 中。因此,这些设置在每个会话中都是临时的,因此在每个会话结束时都会丢失。

注意,与外部会话管理机制(如Spring Session 项目)没有直接关系。这个 SessionLocaleResolver 针对当前的 HttpServletRequest 评估并修改相应的 HttpSession 属性。

Locale 拦截器

你可以通过将 LocaleChangeInterceptor 添加到 HandlerMapping 定义中的一个来启用更改 locale。它检测请求中的一个参数,并相应地改变locale,在调度器的应用上下文中调用 LocaleResolver 的 setLocale 方法。下一个例子显示,对所有包含名为 siteLanguage 的参数的 *.view 资源的调用现在会改变 locale。因此,例如,对URL的请求, https://www.sf.net/home.view?siteLanguage=nl,将网站语言改为荷兰语。下面的例子显示了如何拦截 locale:

<bean id="localeChangeInterceptor"
        class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="siteLanguage"/>
</bean>

<bean id="localeResolver"
        class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

<bean id="urlMapping"
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="localeChangeInterceptor"/>
        </list>
    </property>
    <property name="mappings">
        <value>/**/*.view=someController</value>
    </property>
</bean>

当你使用 ResourceBundleThemeSource 时,一个主题被定义在一个简单的 properties 文件中。该 properties 文件列出了构成主题的资源,如下例所示:

styleSheet=/themes/cool/style.css background=/themes/cool/img/coolBg.jpg

属性的key是指视图代码中的主题元素的名称。对于JSP,你通常使用 spring:theme 自定义标签来完成,它与 spring:message 标签非常相似。下面的JSP片段使用前面例子中定义的主题来定制外观和感觉:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
  <html>
    <head>
      <link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css"/>
    </head>
    <body style="background=<spring:theme code='background'/>">
      ...
    </body>
  </html>

默认情况下,ResourceBundleThemeSource 使用一个空的 base name 前缀。结果是,属性文件从 classpath 的根部加载。因此,你要把 cool.properties 主题定义放在 classpath 根部的一个目录中(例如,放在 /WEB-INF/classes 中)。ResourceBundleThemeSource 使用标准的Java资源包加载机制,允许主题的完全国际化。例如,我们可以有一个 /WEB-INF/classes/cool_nl.properties,引用一个特殊的背景图片,上面有荷兰文。

下面的例子显示了如何拦截 locale:

<bean id="localeChangeInterceptor"
        class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="siteLanguage"/>
</bean>

<bean id="localeResolver"
        class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

<bean id="urlMapping"
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="localeChangeInterceptor"/>
        </list>
    </property>
    <property name="mappings">
        <value>/**/*.view=someController</value>
    </property>
</bean>
1.1.11. 主题(Theme)

你可以应用Spring Web MVC框架的主题来设置你的应用程序的整体外观和感觉,从而增强用户体验。主题是一个静态资源的集合,通常是样式表和图片,它影响应用程序的视觉风格。

从6.0版本开始,对主题的支持已经被废弃,转而使用CSS,并且在服务器端没有任何特殊支持。

定义一个主题

要在你的Web应用程序中使用主题,你必须设置一个 org.springframework.ui.context.ThemeSource 接口的实现。WebApplicationContext 接口扩展了 ThemeSource,但将其职责委托给一个专门的实现。默认情况下,该委托是一个 org.springframework.ui.context.support.ResourceBundleThemeSource 实现,它从 classpath 的根部加载properties文件。要使用自定义的 ThemeSource 实现或配置 ResourceBundleThemeSource 的 base name 前缀,你可以在 application context 中用保留名称 themeSource 注册一个bean。Web application context 会自动检测到具有该名称的Bean并使用它。

解析主题

上一节 所述,在你定义了主题之后,你决定使用哪个主题。DispatcherServlet 会寻找一个名为 themeResolver 的bean来找出要使用的 ThemeResolver 实现。主题解析器的工作方式与 LocaleResolver 基本相同。它检测要用于特定请求的主题,也可以改变请求的主题。下表描述了由Spring提供的主题解析器:

Class

说明

FixedThemeResolver

选择一个固定的主题,通过使用 defaultThemeName 属性设置。

SessionThemeResolver

该主题在用户的 HTTP 会话中被维护。它只需要为每个会话设置一次,但在不同的会话之间不会被持久化。

CookieThemeResolver

所选择的主题被储存在客户端的一个cookie中。

Spring还提供了一个 ThemeChangeInterceptor,可以通过一个简单的请求参数让主题在每个请求中发生变化。

1.1.12. Multipart 解析器

见 Reactive 技术栈中的等效内容

来自 org.springframework.web.multipart 包的 MultipartResolver 是一个解析包括文件上传在内的 multipart 请求的策略。有一个基于容器的 StandardServletMultipartResolver 实现,用于Servlet multipart 请求解析。请注意,基于Apache Commons FileUpload的过时的 CommonsMultipartResolver 已经不可用了,因为Spring Framework 6.0有新的Servlet 5.0+基线。

为了启用 multipart 处理,你需要在 DispatcherServlet 的Spring配置中声明一个 MultipartResolver Bean,名称为 multipartResolver。 DispatcherServlet 会检测到它并将其应用于传入的请求。当收到一个内容类型为 multipart/form-data 的POST时,解析器会将内容包裹在当前的 HttpServletRequest 中,作为一个 MultipartHttpServletRequest 来提供对解析文件的访问,此外还将 part 作为 request parameter 公开。

Servlet Multipart 解析

Servlet multipart 解析需要通过Servlet容器配置来启用。要做到这一点:

  • 在Java中,在Servlet注册上设置一个 MultipartConfigElement。
  • 在 web.xml 中,在servlet声明中添加一个 "" 部分。

下面的例子显示了如何在Servlet注册上设置一个 MultipartConfigElement:

J

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    // ...

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {

        // Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
        registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
    }

}

一旦Servlet的 multipart configuration 到位,你可以添加一个名为 multipartResolver 的 StandardServletMultipartResolver 类型的bean。

这个解析器变体按原样使用你的Servlet容器的 multipart 解析器,可能会使应用程序暴露在容器实现的差异中。默认情况下,它将尝试用任何 HTTP 方法解析任何 multipart/ content type,但这可能不受所有 Servlet 容器的支持。请参阅 StandardServletMultipartResolver

javadoc 了解详情和配置选项。

1.1.13. 日志

见 Reactive 技术栈中的等效内容

Spring MVC的DEBUG级日志被设计成紧凑、简约和人性化的。它专注于反复有用的高价值信息,而不是只有在调试特定问题时才有用的其他信息。

TRACE级别的日志通常遵循与DEBUG相同的原则(例如,也不应该是火烧眉毛),但可以用于调试任何问题。此外,一些日志信息在TRACE与DEBUG下可能会显示不同的细节水平。

好的日志来自于使用日志的经验。如果你发现任何不符合既定目标的地方,请让我们知道。

敏感数据

见 Reactive 技术栈中的等效内容

DEBUG 和 TRACE 日志可能会记录敏感信息。这就是为什么请求参数和header信息在默认情况下是被屏蔽的,它们的完整记录必须通过 DispatcherServlet 上的 enableLoggingRequestDetails 属性明确启用。

下面的例子显示了如何通过使用Java配置来做到这一点:

public class MyInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return ... ;
    }

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

    @Override
    protected String[] getServletMappings() {
        return ... ;
    }

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
        registration.setInitParameter("enableLoggingRequestDetails", "true");
    }

}

资料参考:Servlet 栈的 Web 应用

  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值