Spring Web MVC:DispatcherServlet

https://docs.spring.io/spring-framework/reference/web/webmvc.html

https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet.html

Spring Web MVC 是基于Servlet API构建的原始Web框架,从Spring框架最初版本起就已被包含在内。其正式名称“Spring Web MVC”来源于其源代码模块(spring-webmvc)的名称,但更常见的称呼是“Spring MVC”。

与Spring Web MVC并行,Spring Framework 5.0引入了一个响应式堆栈Web框架,其名称“Spring WebFlux”也基于其源代码模块(spring-webflux)。
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并重写特定方法。

对于程序化使用的场景,可以使用GenericWebApplicationContext作为AnnotationConfigWebApplicationContext的替代方案。

以下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容器中。

上下文层次结构(Context Hierarchy)

DispatcherServlet期望一个WebApplicationContext(一个普通ApplicationContext的扩展)用于其自身的配置。WebApplicationContext与它所关联的ServletContextServlet存在链接。它还绑定到ServletContext,这样应用程序可以使用RequestContextUtils上的静态方法来查找WebApplicationContext,如果需要访问它的话。

对于许多应用程序来说,拥有一个单一的WebApplicationContext既简单又足够。同时,也可以有一个上下文层次结构,其中一个根WebApplicationContext被多个DispatcherServlet(或其他Servlet)实例共享,每个实例都有自己的子WebApplicationContext配置。

WebApplicationContext通常包含基础设施bean,例如数据仓库和业务服务,这些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>

如果不需要应用程序上下文层次结构,应用程序可能只配置一个“根”上下文,并将contextConfigLocation Servlet参数留空。

特殊Bean类型

DispatcherServlet将请求处理和适当的响应渲染委托给特殊的bean。这里的“特殊bean”指的是由Spring管理的Object 实例,它们实现了框架的约定或规范。这些bean通常带有内置规范,但你可以自定义它们的属性,并扩展或替换它们。

以下表格列出了DispatcherServlet检测到的特殊bean:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Web MVC 配置

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

在大多数情况下,MVC配置是最好的起点。它使用Java或XML声明所需的bean,并提供了一个更高级别的配置回调API来进行自定义。

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

Servlet 配置

在Servlet环境中,可以选择以编程方式配置Servlet容器,作为替代方案或与web.xml文件结合使用。以下示例注册了一个DispatcherServlet

import org.springframework.web.WebApplicationInitializer;

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容器。一个名为AbstractDispatcherServletInitializerWebApplicationInitializer抽象基类实现使注册DispatcherServlet变得更容易,只需重写方法来指定servlet映射和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() };
	}
}

每个过滤器都会根据其具体类型添加一个默认名称,并自动映射到DispatcherServlet

AbstractDispatcherServletInitializer的受保护方法isAsyncSupported提供了一个单一的位置,用于在DispatcherServlet及其映射的所有过滤器上启用异步支持。默认情况下,此标志设置为true

如果需要进一步自定义DispatcherServlet本身,可以重写createDispatcherServlet方法。

处理

DispatcherServlet处理请求的过程如下:

  • WebApplicationContext会在请求中被搜索并绑定为一个属性,这样控制器和过程中的其他元素就可以使用它。默认情况下,它会被绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE键下。
  • 区域解析器(Locale Resolver)被绑定到请求中,以便在处理请求的过程中(渲染视图、准备数据等)让元素解析出要使用的区域设置。如果不需要区域解析,那么就不需要区域解析器。
  • 主题解析器(Theme Resolver)被绑定到请求中,以便诸如视图等元素确定要使用哪个主题。如果不使用主题,可以忽略它。
  • 如果指定了一个多部分文件(multipart file)解析器,那么会检查请求是否包含多部分数据。如果找到了多部分数据,那么请求将被包装在MultipartHttpServletRequest中,以便由过程中的其他元素进行进一步处理。
  • 会搜索一个合适的处理器(handler)。如果找到了处理器,那么将运行与处理器相关联的执行链(包括预处理器、后处理器和控制器),以准备一个用于渲染的模型。另外,对于带有注解的控制器,响应可以在HandlerAdapter内部直接渲染,而不是返回视图。
  • 如果返回了模型,那么视图将被渲染。如果没有返回模型(可能是由于预处理器或后处理器拦截了请求,可能是出于安全原因),则不会渲染视图,因为请求可能已经被满足了。

WebApplicationContext中声明的HandlerExceptionResolver bean用于解决请求处理过程中抛出的异常。这些异常解析器允许自定义处理异常的逻辑。

对于HTTP缓存支持,处理器可以使用WebRequestcheckNotModified方法,以及为带有注解的控制器提供的更多选项。

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

路径匹配(Path Matching)

Servlet API 公开了完整的请求路径作为 requestURI,并将其进一步细分为 contextPathservletPathpathInfo,这些值根据 Servlet 的映射方式而有所不同。基于这些输入,Spring MVC 需要确定用于映射处理器的查找路径,这个路径应该排除 contextPath 和任何 servletMapping 前缀(如果适用的话)。

servletPathpathInfo 是被解码过的,这使得它们无法直接与完整的 requestURI 进行直接比较以推导出 lookupPath,因此需要对 requestURI 进行解码。然而,这样做又引入了新的问题,因为路径中可能包含编码过的保留字符,如 “/” 或 “;”,这些字符在解码后可能会改变路径的结构,从而也可能导致安全问题。此外,Servlet 容器可能会以不同程度地对 servletPath 进行规范化,这使得对 requestURI 进行 startsWith 比较变得更加不可能。

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

默认的 Servlet 映射 “/” 是一个好选择。然而,仍然有一个问题,那就是需要将 requestURI 进行解码,以便能够与控制器的映射进行比较。这同样是不可取的,因为解码可能改变路径结构的保留字符。如果不期望出现这样的字符,那么你可以拒绝它们(像 Spring Security HTTP 防火墙那样),或者你可以将 UrlPathHelper 配置为 urlDecode=false,但控制器映射将需要匹配到编码后的路径,这可能并不总是有效。此外,有时 DispatcherServlet 需要与其他 Servlet 共享 URL 空间,并可能需要通过前缀进行映射。

使用 PathPatternParser 和解析后的模式,可以解决上述与 AntPathMatcher 进行字符串路径匹配相关的问题。PathPatternParser 自 Spring MVC 5.3 版本起可供使用,从 6.0 版本起默认启用。与 AntPathMatcher 不同,后者需要解码查找路径或编码控制器映射,而解析后的 PathPattern 则与路径的解析表示(称为 RequestPath)逐个路径段进行匹配。这允许单独解码和清理路径段值,而无需冒着改变路径结构的风险。解析后的 PathPattern 还支持使用 servletPath 前缀映射,只要使用了 Servlet 路径映射,并且前缀保持简单(即不包含编码字符)。

拦截

所有的 HandlerMapping 实现都支持处理器拦截器,这在你想对特定请求应用特定功能时非常有用——例如,检查用户认证信息。拦截器必须实现来自 org.springframework.web.servlet 包中的 HandlerInterceptor 接口,该接口包含三个方法,它们应该提供了足够的灵活性来进行各种预处理和后处理操作:

  • preHandle(..):在实际处理器运行之前执行。
  • postHandle(..):在处理器运行之后执行。
  • afterCompletion(..):整个请求完成之后执行。

preHandle(..) 方法返回一个布尔值。你可以使用这个方法来决定是否中断或继续执行链的处理。当该方法返回 true 时,处理器执行链会继续执行。当返回 false 时,DispatcherServlet 会认为拦截器本身已经处理了请求(例如,渲染了合适的视图),因此不会继续执行链中的其他拦截器和实际的处理器。

对于使用 @ResponseBodyResponseEntity 方法的情况,postHandle 方法不那么有用,因为这些方法的响应是在 HandlerAdapter 中编写并提交的,且在 postHandle 方法之前。这意味着在 postHandle 方法中,对响应进行任何更改(例如添加额外的头部信息)都太迟了。对于这样的场景,你可以实现 ResponseBodyAdvice 接口,并将其声明为一个 Controller Advice bean,或者直接在 RequestMappingHandlerAdapter 上进行配置。

异常

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

以下表格列出了可用的 HandlerExceptionResolver 实现:
在这里插入图片描述
在这里插入图片描述

解析器链(Chain of Resolvers)

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

HandlerExceptionResolver 的约定规定它可以返回:

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

MVC 配置会自动声明针对默认 Spring MVC 异常、带有 @ResponseStatus 注解的异常以及支持 @ExceptionHandler 方法的内置解析器。你可以自定义这个列表或替换它。

容器错误页面(Container Error Page)

如果任何 HandlerExceptionResolver 都无法解决异常,因此允许异常继续传播,或者如果响应状态被设置为错误状态(即 4xx5xx),Servlet 容器可以渲染一个默认的 HTML 错误页面。要自定义容器的默认错误页面,你可以在 web.xml 中声明一个错误页面映射。以下示例展示了如何做到这一点:

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

根据前面的示例,当异常冒泡或响应具有错误状态时,Servlet 容器会在容器内部进行 ERROR 分派,到配置的 URL(例如 /error)。然后,这个请求将由 DispatcherServlet 处理,可能将其映射到一个 @Controller,该 @Controller 可以实现返回一个带有模型的错误视图名称或渲染一个 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 来实现。

视图解析(View Resolution)

Spring MVC 定义了 ViewResolverView 接口,使你能够在浏览器中呈现模型,而无需将你绑定到特定的视图技术。ViewResolver 提供了视图名称和实际视图之间的映射。View 负责在将数据传递给特定的视图技术之前准备数据。

下面的表格提供了关于 ViewResolver 层次结构的更多详细信息:

  • AbstractCachingViewResolverAbstractCachingViewResolver 的子类会缓存它们解析的视图实例。缓存可以提高某些视图技术的性能。你可以通过将 cache 属性设置为 false 来关闭缓存。此外,如果你必须在运行时刷新某个视图(例如,当 FreeMarker 模板被修改时),你可以使用 removeFromCache(String viewName, Locale loc) 方法。
  • UrlBasedViewResolverViewResolver 接口的简单实现,它直接影响逻辑视图名称到 URL 的直接解析,而无需明确的映射定义。如果你的逻辑名称以直接的方式与你的视图资源的名称相匹配,而不需要任意的映射,那么这是合适的。
  • InternalResourceViewResolverUrlBasedViewResolver 的一个方便的子类,支持InternalResourceView(实际上,Servlets 和 JSPs)以及诸如 JstlView 这样的子类。你可以通过使用setViewClass(..) 来为这个解析器生成的所有视图指定视图类。
  • FreeMarkerViewResolverUrlBasedViewResolver 的一个方便的子类,支持 FreeMarkerView 以及其自定义子类。
  • ContentNegotiatingViewResolverViewResolver 接口的实现,它根据请求的文件名或 Accept头信息来解析视图。
  • BeanNameViewResolverViewResolver 接口的实现,它将视图名称解释为当前应用程序上下文中的 bean名称。这是一个非常灵活的变体,它允许根据不同的视图名称混合和匹配不同的视图类型。每个这样的 View 可以被定义为一个 bean,例如在XML 或配置类中。

处理(Handling)

你可以通过声明多个解析器 bean 并根据需要设置 order 属性来指定顺序,从而将视图解析器链接起来。记住,order 属性的值越高,视图解析器在链中的位置就越靠后。

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

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

重定向(Redirecting)

在视图名称中使用特殊的 redirect: 前缀,可以执行重定向操作。UrlBasedViewResolver(及其子类)会将其识别为需要执行重定向的指令。视图名称的其余部分就是重定向的 URL。

其最终效果与控制器返回 RedirectView 相同,但现在控制器本身可以基于逻辑视图名称进行操作。逻辑视图名称(例如 redirect:/myapp/some/resource)相对于当前的 Servlet 上下文进行重定向,而像 redirect:https://myhost.com/some/arbitrary/path 这样的名称则重定向到绝对 URL。

转发(Forwarding)

对于最终由 UrlBasedViewResolver 及其子类解析的视图名称,你还可以使用特殊的 forward: 前缀。这会创建一个 InternalResourceView,该视图执行 RequestDispatcher.forward()。因此,这个前缀对于 InternalResourceViewResolverInternalResourceView(用于 JSP)来说并没有用处,但如果你使用另一种视图技术但仍希望强制将资源转发给 Servlet/JSP 引擎处理,那么它可能会有所帮助。请注意,你也可以选择链式连接多个视图解析器。

内容协商(Content Negotiation)

ContentNegotiatingViewResolver 本身并不解析视图,而是委托给其他视图解析器,并选择类似于客户端请求的表示的视图。这个表示可以从 Accept 头信息或者查询参数(例如,/path/to/resource?format=pdf)中确定。

ContentNegotiatingViewResolver 通过将请求媒体类型与每个 ViewResolver 关联的 View 支持的媒体类型(也称为 Content-Type)进行比较,来选择适当的 View 来处理请求。列表中第一个具有兼容 Content-TypeView 会将表示返回给客户端。如果 ViewResolver 链无法提供兼容的视图,则会查阅通过 DefaultViews 属性指定的视图列表。后一种选项适用于那些无论逻辑视图名称如何,都能呈现当前资源的适当表示的单一 ViewAccept 头信息可以包含通配符(例如 text/*),在这种情况下,Content-Typetext/xmlView 就是一个兼容的匹配项。

区域设置(Locale)

Spring 架构的大多数部分都支持国际化,就像 Spring web MVC 框架所做的那样。DispatcherServlet 允许你根据客户端的区域设置自动解析消息。这是通过 LocaleResolver 对象来完成的。

当一个请求进来时,DispatcherServlet 会查找一个区域设置解析器,如果找到了,它就会尝试使用它来设置区域设置。通过使用 RequestContext.getLocale() 方法,你总是可以获取到由区域设置解析器解析出来的区域设置。

除了自动区域设置解析之外,你还可以将拦截器附加到处理器映射上,以便在特定情况下(例如,基于请求中的参数)更改区域设置。

区域设置解析器和拦截器定义在 org.springframework.web.servlet.i18n 包中,并且按照正常方式在你的应用程序上下文中进行配置。Spring 包含了以下一些区域设置解析器的选择。

  • Time Zone
  • Header Resolver
  • Cookie Resolver
  • Session Resolver
  • Locale Interceptor

时区(Time Zone)

除了获取客户端的区域设置外,了解其时区通常也很有用。LocaleContextResolver 接口提供了对 LocaleResolver 的扩展,让解析器能够提供一个更丰富的 LocaleContext,这可能包括时区信息。

当可用时,可以通过使用 RequestContext.getTimeZone() 方法来获取用户的时区。任何已注册到 Spring 的 ConversionServiceDate/Time ConverterFormatter对象都会自动使用时区信息。

头部解析器(Header Resolver)

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

Cookie 解析器(Cookie Resolver)

这个区域设置解析器会检查客户端上可能存在的Cookie,以查看是否指定了LocaleTimeZone 。如果指定了,它就会使用这些详细的设置。通过使用这个区域设置解析器的属性,你可以指定Cookie的名称以及最大生存时间。以下示例定义了一个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的属性:
在这里插入图片描述

Session 解析器(Session Resolver)

SessionLocaleResolver 允许你从与用户请求可能关联的会话中检索区域设置(Locale)和时区(TimeZone)。与 CookieLocaleResolver 不同,这种策略将本地选择的区域设置存储在 Servlet 容器的 HttpSession 中。因此,这些设置对于每个会话都是临时的,并在每个会话结束时丢失。

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

区域设置拦截器(Locale Interceptor)

你可以通过向其中一个HandlerMapping定义添加LocaleChangeInterceptor来启用区域设置的更改。它会检测请求中的参数,并据此更改区域设置,调用分派器应用程序上下文中的LocaleResolversetLocale方法。以下示例显示了包含名为siteLanguage参数的所有*.view资源的调用现在会更改区域设置。因此,例如,对URLwww.sf.net/home.view?siteLanguage=nl的请求会将网站语言更改为荷兰语。以下示例展示了如何拦截区域设置:

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

主题(Themes)

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

自6.0版本起,对主题的支持已被弃用,转而支持使用CSS,并且服务器端不再提供任何特殊支持。

定义主题(Defining a theme)

要在你的Web应用程序中使用主题,你必须设置一个实现org.springframework.ui.context.ThemeSource接口的类。WebApplicationContext接口扩展了ThemeSource,但将其职责委托给了一个专门的实现。默认情况下,这个委托是org.springframework.ui.context.support.ResourceBundleThemeSource的实现,它从类路径的根目录加载属性文件。要使用自定义的ThemeSource实现或配置ResourceBundleThemeSource的基本名称前缀,你可以在应用程序上下文中注册一个名为themeSource的bean。Web应用程序上下文会自动检测具有该名称的bean并使用它。

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

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

属性文件的键是从视图代码中引用主题元素的名称。对于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使用空的基本名称前缀。因此,属性文件从类路径的根目录加载。因此,你会将cool.properties主题定义放在一个位于类路径根目录的文件夹中(例如,/WEB-INF/classes)。ResourceBundleThemeSource使用标准的Java资源包加载机制,允许对主题进行完整的国际化。例如,我们可以有一个/WEB-INF/classes/cool_nl.properties,它引用一个带有荷兰文字的特殊背景图像。

解析主题(Resolving Themes)

在按照前面的部分定义了主题之后,你需要决定使用哪个主题。DispatcherServlet会查找一个名为themeResolver的bean来确定使用哪个ThemeResolver实现。主题解析器的工作方式与区域设置解析器(LocaleResolver)非常相似。它检测特定请求应使用的主题,并且还可以更改请求的主题。下面的表格描述了Spring提供的主题解析器:
在这里插入图片描述

Spring还提供了一个ThemeChangeInterceptor,它允许通过简单的请求参数在每次请求时更改主题。

多部分解析器(Multipart Resolver)

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

为了启用多部分处理,你需要在你的DispatcherServlet Spring配置中声明一个名为multipartResolverMultipartResolver bean。DispatcherServlet会检测到它,并将其应用于传入的请求。当接收到一个内容为multipart/form-data类型的POST请求时,解析器会解析内容,将当前的HttpServletRequest包装为MultipartHttpServletRequest,以便除了将各部分作为请求参数暴露出来之外,还能访问解析后的文件。

Servlet多部分解析(Servlet多部分解析)

Servlet多部分解析需要通过Servlet容器配置来启用。具体做法如下:

  • 在Java中,需要在Servlet注册上设置一个MultipartConfigElement
  • web.xml中,向servlet声明添加一个"<multipart-config>"部分。

以下示例展示了如何在Servlet注册上设置一个MultipartConfigElement

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	// ...

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

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

}

一旦Servlet多部分配置设置完毕,你就可以添加一个名为multipartResolverStandardServletMultipartResolver类型的bean。

这个解析器变体使用你的Servlet容器的多部分解析器,这可能会使应用程序暴露于容器实现之间的差异。默认情况下,它会尝试解析任何使用任何HTTP方法的多部分内容类型,但这可能并非所有Servlet容器都支持。

日志记录

Spring MVC中的DEBUG级别日志设计得紧凑、简洁且易于人类阅读。它专注于那些反复使用的高价值信息,而不是仅在调试特定问题时才有用的其他信息。

TRACE级别的日志通常遵循与DEBUG相同的原则,但可以用于调试任何问题。此外,一些日志消息在TRACE级别和DEBUG级别上可能显示出不同程度的细节。

良好的日志记录来源于使用日志的经验。

敏感数据

DEBUG和TRACE日志可能会记录敏感信息。这就是为什么请求参数和头部默认会被遮蔽,要完整记录它们必须通过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");
	}

}
  • 24
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值