docs: 更新文档

This commit is contained in:
dunwu 2023-02-14 19:37:30 +08:00
parent 6c87289439
commit 49bc9c9651
7 changed files with 711 additions and 6 deletions

View File

@ -26,27 +26,31 @@ module.exports = {
themeConfig: {
nav: [
{
text: 'Spring综合',
text: '综合',
link: '/01.Java/13.框架/01.Spring/00.Spring综合/'
},
{
text: 'Spring核心',
text: '核心',
link: '/01.Java/13.框架/01.Spring/01.Spring核心/'
},
{
text: 'Spring数据',
text: '数据',
link: '/01.Java/13.框架/01.Spring/02.Spring数据/'
},
{
text: 'SpringIO',
text: 'Web篇',
link: '/01.Java/13.框架/01.Spring/03.SpringWeb/'
},
{
text: 'IO篇',
link: '/01.Java/13.框架/01.Spring/04.SpringIO/'
},
{
text: 'Spring集成',
text: '集成',
link: '/01.Java/13.框架/01.Spring/05.Spring集成/'
},
{
text: 'Spring其他',
text: '其他',
link: '/01.Java/13.框架/01.Spring/99.Spring其他/'
}
],

View File

@ -0,0 +1,573 @@
---
title: Spring MVC 之 DispatcherServlet
categories:
- Java
- 框架
- Spring
- SpringWeb
tags:
- Java
- 框架
- Spring
- Web
- DispatcherServlet
date: 2023-02-13 09:57:52
permalink: /pages/20287b/
---
# Spring MVC 之 DispatcherServlet
与许多其他 Web 框架一样Spring MVC 是围绕前端控制器模式设计的,其中 `DispatcherServlet` 为请求处理提供了共享算法,而实际工作由可配置的委托组件执行。该模型非常灵活,支持多样化的工作流程。
`DispatcherServlet` 与其他 Servlet 一样,需要使用 Java 配置或在 `web.xml` 中根据 Servlet 规范进行声明和映射。也就是说,`DispatcherServlet` 使用 Spring 配置来发现请求映射、视图解析、异常处理等所需的委托组件。
【示例】Java 方式注册并初始化 `DispatcherServlet`,它由 Servlet 容器自动检测(请参阅 Servlet Config
```java
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/*");
}
}
```
【示例】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>
```
## 上下文层次结构
`DispatcherServlet` 需要一个 `WebApplicationContext``ApplicationContext` 的扩展类)用于它自己的配置。`WebApplicationContext` 有一个指向 `ServletContext` 和与之关联的 `Servlet` 的链接。它还绑定到 `ServletContext`,以便应用程序可以在 `RequestContextUtils` 上使用静态方法来查找 `WebApplicationContext`
对于多数应用程序来说,拥有一个 `WebApplicationContext` 单例就足够。也可以有一个上下文层次结构,其中有一个根 `WebApplicationContext` 在多个 `DispatcherServlet`(或其他 `Servlet`)实例之间共享,每个实例都有自己的子 `WebApplicationContext` 配置。
`WebApplicationContext` 通常包含基础结构 bean例如需要跨多个 Servlet 实例共享的数据存储和业务服务。这些 bean 是有效继承的,并且可以在特定 `Servlet` 的子 `WebApplicationContext` 中被覆盖(即重新声明),它通常包含指定 `Servlet` 的本地 bean。下图显示了这种关系
![](https://raw.githubusercontent.com/dunwu/images/dev/snap/20230213103223.png)
【示例】配置 `WebApplicationContext` 层次结构:
```java
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/*" };
}
}
```
【示例】`web.xml` 方式配置 `WebApplicationContext` 层次结构:
```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>
```
## 特殊 Bean 类型
`DispatcherServlet` 委托特殊 bean 来处理请求并渲染适当的响应。“特殊 bean”是指实现框架约束的被 Spring 管理的对象实例。
下表列出了 `DispatcherServlet` 检测到的特殊 beans
| Bean type | Explanation |
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `HandlerMapping` | 将请求映射到处理程序以及用于预处理和后处理的拦截器列表。映射基于一些标准,其细节因 `HandlerMapping` 实现而异。<br/>两个主要的 `HandlerMapping` 实现是 `RequestMappingHandlerMapping`(支持 @RequestMapping 注释方法)和 `SimpleUrlHandlerMapping`(维护 URI 路径模式到处理程序的显式注册)。 |
| `HandlerAdapter` | 帮助 `DispatcherServlet` 调用映射到请求的处理程序,而不管实际调用处理程序的方式如何。例如,调用带注解的控制器需要解析注解。`HandlerAdapter` 的主要目的是保护 `DispatcherServlet` 免受此类细节的影响。 |
| [`HandlerExceptionResolver`](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-exceptionhandlers) | 解决异常的策略可能将它们映射到处理程序、HTML 错误视图或其他目标。 |
| [`ViewResolver`](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-viewresolver) | 将从处理程序返回的基于字符串的逻辑视图名称解析为用于呈现响应的实际视图。 |
| [`LocaleResolver`](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-localeresolver), [LocaleContextResolver](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-timezone) | 解决用户正在使用的本地化设置,可能还有他们的时区,以便能够提供国际化的视图。请参见语言环境。 |
| [`ThemeResolver`](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-themeresolver) | 解决您的 Web 应用程序可以使用的主题——例如,提供个性化布局。 |
| [`MultipartResolver`](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-multipart) | 通过一些 multipart 解析库的帮助解析 multipart 请求(例如,通过浏览器上传文件)。 |
| [`FlashMapManager`](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-flash-attributes) | 存储和检索输入、输出 FlashMap可用于将属性从一个请求传递到另一个请求通常通过重定向方式。 |
## Web MVC 配置
应用程序可以声明处理请求所需的特殊 Bean 类型中列出的基础结构 bean。`DispatcherServlet` 检查每个特殊 bean 的 `WebApplicationContext`。如果没有匹配的 bean 类型,它将回退到 `DispatcherServlet.properties` 中列出的默认类型。
在大多数情况下MVC 配置是最好的起点。它以 Java 或 XML 声明所需的 bean并提供更高级别的配置回调 API 来对其进行自定义。
> 注意Spring Boot 依赖于 MVC Java 配置来配置 Spring MVC并提供了许多额外的方便选项。
## Servlet 配置
在 Servlet 环境中,您可以选择以编程方式配置 Servlet 容器作为替代方案或与 web.xml 文件结合使用。
```java
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 容器。名为 `AbstractDispatcherServletInitializer``WebApplicationInitializer` 的抽象基类实现通过覆盖方法来指定 servlet 映射和 `DispatcherServlet` 配置的位置,使得注册 `DispatcherServlet` 变得更加容易。
对于使用基于 Java 的 Spring 配置的应用程序,建议这样做,如以下示例所示:
```java
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 扩展,如以下示例所示:
```java
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`,如以下示例所示:
```java
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` 属性下。
- 语言环境解析器绑定到请求,让流程中的元素解析在处理请求(呈现视图、准备数据等)时要使用的语言环境。如果不需要语言环境解析,则不需要语言环境解析器。
- 主题解析器绑定到请求,让视图等元素决定使用哪个主题。如果不使用主题,可以忽略它。
- 如果指定 multipart 文件解析器,则会检查请求的 multipart。如果找到 multipart将请求将包装在 `MultipartHttpServletRequest` 中,以供流程中的其他元素进一步处理。
- 搜索合适的处理程序。如果找到处理程序,则运行与处理程序关联的执行链(预处理器、后处理器和控制器)以准备渲染模型。或者,对于带注释的控制器,可以呈现响应(在 `HandlerAdapter` 中)而不是返回视图。
- 如果返回模型,则渲染视图。如果没有返回模型(可能是由于预处理器或后处理器拦截了请求,也可能是出于安全原因),则不会渲染任何视图,因为请求可能已经完成。
`WebApplicationContext` 中声明的 `HandlerExceptionResolver` bean 用于解决请求处理期间抛出的异常。这些异常解析器允许自定义逻辑来解决异常。
对于 HTTP 缓存支持,处理程序可以使用 `WebRequest``checkNotModified` 方法,以及用于控制器的 HTTP 缓存中所述的带注释控制器的更多选项。
可以通过将 Servlet 初始化参数(`init-param` 元素)添加到 `web.xml` 文件中的 Servlet 声明来自定义各个 `DispatcherServlet` 实例。下表列出了支持的参数:
| 参数 | 说明 |
| :------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `contextClass` | 实现 `ConfigurableWebApplicationContext` 的类,将由此 Servlet 实例化和本地配置。默认情况下,使用 `XmlWebApplicationContext`。 |
| `contextConfigLocation` | 传递给上下文实例(由 `contextClass` 指定)以指示可以在何处找到上下文的字符串。该字符串可能包含多个字符串(使用逗号作为分隔符)以支持多个上下文。在具有两次定义的 bean 的多个上下文位置的情况下,最新的位置优先。 |
| `namespace` | `WebApplicationContext` 的命名空间。默认为 `[servlet-name]-servlet`。 |
| `throwExceptionIfNoHandlerFound` | 当找不到请求的处理程序时是否抛出 `NoHandlerFoundException`。然后可以使用 `HandlerExceptionResolver`(例如,通过使用 `@ExceptionHandler` 控制器方法)捕获异常并像其他任何方法一样处理。默认情况下,它设置为 `false`,在这种情况下,`DispatcherServlet` 设置响应状态为 404 (NOT_FOUND) 而不会引发异常。请注意,如果 [默认 servlet 处理](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc -default-servlet-handler) 也被配置,未解决的请求总是转发到默认的 servlet 并且永远不会引发 404。 |
## 路径匹配
Servlet API 将完整的请求路径公开为 `requestURI`,并将其进一步细分为 `contextPath`、`servletPath` 和 `pathInfo`,它们的值因 Servlet 的映射方式而异。从这些输入中Spring MVC 需要确定用于映射处理程序的查找路径,如果适用,它应该排除 `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 配置中通过路径匹配提供一个带有 `alwaysUseFullPath=true``UrlPathHelper` 来实现等效。
幸运的是,默认的 Servlet 映射 `"/"` 是一个不错的选择。但是,仍然存在一个问题,即需要对 `requestURI` 进行解码才能与控制器映射进行比较。这也是不可取的,因为可能会解码改变路径结构的保留字。如果不需要这样的字符,那么您可以拒绝它们(如 Spring Security HTTP 防火墙),或者您可以使用 `urlDecode=false` 配置 `UrlPathHelper`,但控制器映射需要与编码路径匹配,这可能并不总是有效。此外,有时 `DispatcherServlet` 需要与另一个 Servlet 共享 URL 空间,并且可能需要通过前缀进行映射。
在使用 `PathPatternParser` 和解析模式时解决了上述问题,作为使用 `AntPathMatcher` 进行字符串路径匹配的替代方法。`PathPatternParser` 从 5.3 版本开始就可以在 Spring MVC 中使用,并且从 6.0 版本开始默认启用。与需要解码查找路径或编码控制器映射的 `AntPathMatcher` 不同,解析的 `PathPattern` 与称为 `RequestPath` 的路径的解析表示匹配,一次一个路径段。这允许单独解码和清理路径段值,而没有改变路径结构的风险。解析的 `PathPattern` 也支持使用 `servletPath` 前缀映射,只要使用 Servlet 路径映射并且前缀保持简单,即它没有编码字符。
## 拦截器
所有 `HandlerMapping` 实现都支持处理拦截器,当想要将特定功能应用于某些请求时,这些拦截器很有用——例如,检查主体。拦截器必须使用 `org.springframework.web.servlet` 包中的三个方法实现 `HandlerInterceptor`,这三个方法应该提供足够的灵活性来进行各种预处理和后处理:
- `preHandle(..)`:在实际 handler 之前执行
- `postHandle(..)`handler 之后执行
- `afterCompletion(..)`:完成请求后执行
`preHandle(..)` 方法返回一个布尔值。可以使用此方法中断或继续执行链的处理。当此方法返回 true 时,处理程序执行链将继续。当它返回 false 时,`DispatcherServlet` 假定拦截器本身已经处理请求(并且,例如,呈现适当的视图)并且不会继续执行其他拦截器和执行链中的实际处理程序。
有关如何配置拦截器的示例,请参阅 MVC 配置部分中的[拦截器](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-config-interceptors)。还可以通过在各个 `HandlerMapping` 实现上使用 setter 来直接注册它们。
`postHandle` 方法对于 `@ResponseBody``ResponseEntity` 的方法不太有用,它们的响应是在 `HandlerAdapter` 中和 `postHandle` 之前编写和提交的。这意味着对响应进行任何更改都为时已晚,例如添加额外的标头。对于此类场景,您可以实现 `ResponseBodyAdvice` 并将其声明为 [Controller Advice](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-controller-advice) bean 或直接在 `RequestMappingHandlerAdapter` 上进行配置。
## 异常
如果在请求映射期间发生异常或从请求处理程序(例如 `@Controller`)抛出异常,则 `DispatcherServlet` 委托 `HandlerExceptionResolver` bean 链来解决异常并提供替代处理,这通常是错误响应。
下表列出了可用的 `HandlerExceptionResolver` 实现:
| `HandlerExceptionResolver` | 说明 |
| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------- |
| `SimpleMappingExceptionResolver` | 异常类名称和错误视图名称之间的映射。用于在浏览器应用程序中呈现错误页面。 |
| [`DefaultHandlerExceptionResolver`](https://docs.spring.io/spring-framework/docs/6.0.4/javadoc-api/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.html) | 解决由 Spring MVC 引发的异常并将它们映射到 HTTP 状态代码。 |
| `ResponseStatusExceptionResolver` | 使用 `@ResponseStatus` 注解解决异常,并根据注解中的值将它们映射到 HTTP 状态代码。 |
| `ExceptionHandlerExceptionResolver` | 通过在 `@Controller``@ControllerAdvice` 类中调用 `@ExceptionHandler` 方法来解决异常。 |
### 解析器链
您可以通过在 Spring 配置中声明多个 `HandlerExceptionResolver` bean 并根据需要设置它们的顺序属性来构成异常解析器链。order 属性越高,异常解析器的位置就越靠后。
`HandlerExceptionResolver` 的约定使它可以返回以下内容:
- 指向错误视图的 `ModelAndView`
- 如果异常是在解析器中处理的,则为空的 `ModelAndView`
- 如果异常仍未解决,则为 null供后续解析器尝试如果异常仍然存在则允许向上冒泡到 Servlet 容器。
MVC Config 自动为默认的 Spring MVC 异常、`@ResponseStatus` 注释的异常和对 `@ExceptionHandler` 方法的支持声明内置解析器。您可以自定义该列表或替换它。
### 错误页面
如果异常仍未被任何 `HandlerExceptionResolver` 处理并因此继续传播,或者如果响应状态设置为错误状态(即 4xx、5xxServlet 容器可以在 HTML 中呈现默认错误页面。要自定义容器的默认错误页面,您可以在 `web.xml` 中声明一个错误页面映射。以下示例显示了如何执行此操作:
```xml
<error-page>
<location>/error</location>
</error-page>
```
在前面的示例中当出现异常或响应具有错误状态时Servlet 容器会在容器内将 ERROR 分派到配置的 URL例如`/error`)。然后由 `DispatcherServlet` 处理,可能将其映射到 `@Controller`,后者可以返回带有模型的错误视图名称或呈现 JSON 响应,如以下示例所示:
```java
@RestController
public class ErrorController {
@RequestMapping(path = "/error")
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<String, Object>();
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`
## 视图解析
Spring MVC 定义了 `ViewResolver``View` 接口,让用户可以在浏览器中渲染模型,而无需限定于特定的视图技术。`ViewResolver` 提供视图名称和实际视图之间的映射。`View` 解决了在移交给特定视图技术之前准备数据的问题。
下表提供了有关 ViewResolver 一些子类:
| ViewResolver | Description |
| :------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AbstractCachingViewResolver` | `AbstractCachingViewResolver` 的子类缓存它们解析的视图实例。缓存提高了某些视图技术的性能。您可以通过将 `cache` 属性设置为 `false` 来关闭缓存。此外,如果您必须在运行时刷新某个视图(例如,修改 FreeMarker 模板时),您可以使用 removeFromCache(String viewName, Locale loc) 方法。 |
| `UrlBasedViewResolver` | `ViewResolver` 接口的简单实现,无需显式映射定义即可将逻辑视图名称直接解析为 URL。如果您的逻辑名称以直接的方式匹配您的视图资源的名称而不需要任意映射那么这是合适的。 |
| `InternalResourceViewResolver` | `UrlBasedViewResolver` 的子类,支持 `InternalResourceView`(实际上是 Servlet 和 JSP以及 `JstlView``TilesView` 等子类。可以使用 `setViewClass(..)` 为该解析器生成的所有视图指定视图类。 |
| `FreeMarkerViewResolver` | `UrlBasedViewResolver` 的子类,支持 `FreeMarkerView` 和它们的自定义子类。 |
| `ContentNegotiatingViewResolver` | `ViewResolver` 接口的实现,该接口根据请求文件名或 `Accept` 标头解析视图。 |
| `BeanNameViewResolver` | 将视图名称解释为当前应用程序上下文中的 bean 名称的 ViewResolver 接口的实现。这是一个非常灵活的变体,允许根据不同的视图名称混合和匹配不同的视图类型。每个这样的“视图”都可以定义为一个 bean例如 在 XML 或配置类中。 |
### 处理
可以通过声明多个解析器来构成视图解析器链,如果需要,还可以通过设置 order 属性来指定顺序。顺序属性越高,视图解析器在链中的位置就越靠后。
`ViewResolver` 的约定指定它可以返回 null 以指示找不到视图。但是,对于 JSP 和 `InternalResourceViewResolver`,确定 JSP 是否存在的唯一方法是通过 `RequestDispatcher` 执行分派。因此,您必须始终将 `InternalResourceViewResolver` 配置为在视图解析器的整体顺序中排在最后。
配置视图解析就像将 `ViewResolver` 添加到 Spring 配置中一样简单。MVC Config 为视图解析器和添加无逻辑视图控制器提供了专用的配置 API这对于没有控制器逻辑的 HTML 模板渲染很有用。
### 重定向
视图名称中的特殊前缀 `redirect:` 可以实现一个重定向。`UrlBasedViewResolver`(及其子类)将此识别为需要重定向的指令。视图名称的其余部分是重定向 URL。
最终效果与控制器返回 `RedirectView` 相同,但现在控制器本身可以根据逻辑视图名称进行操作。逻辑视图名称(例如 `redirect:/myapp/some/resource`)相对于当前 Servlet 上下文重定向,而名称(例如 `redirect:https://myhost.com/some/arbitrary/path`)重定向到绝对 URL。
请注意,如果使用 `@ResponseStatus` 注解标记控制器方法,则注解值优先于 `RedirectView` 设置的响应状态。
### 转发
视图名称中的特殊前缀 `forward:` 可以实现一个转发。这将创建一个 `InternalResourceView`,它执行 `RequestDispatcher.forward()`。因此,此前缀对 `InternalResourceViewResolver``InternalResourceView`(对于 JSP没有用但如果您使用另一种视图技术但仍想强制转发由 Servlet/JSP 引擎处理的资源,它可能会有所帮助。
### 内容协商
`ContentNegotiatingViewResolver` 本身不解析视图,而是委托给其他视图解析器并选择类似于客户端请求的表示的视图。可以从 `Accept` 头或查询参数(例如,`"/path?format=pdf"`)确定表示形式。
`ContentNegotiatingViewResolver` 通过将请求媒体类型与其每个 `ViewResolver` 关联的 `View` 支持的媒体类型(也称为 `Content-Type`)进行比较,来选择合适的 `View` 来处理请求。列表中第一个具有兼容 `Content-Type` 的视图将处理结果返回给客户端。如果 `ViewResolver` 链无法提供兼容的视图,则会查阅通过 `DefaultViews` 属性指定的视图列表。后一个选项适用于单例视图,它可以呈现当前资源的适当表示,而不管逻辑视图名称如何。`Accept` 标头可以包含通配符(例如 `text/*`),在这种情况下,`Content-Type` 为 `text/xml` 的 View 是兼容的匹配项。
## 国际化
大部分的 Spring 架构都支持国际化,就像 Spring web MVC 框架所做的那样。`DispatcherServlet` 允许您使用客户端的语言环境自动解析消息。这是通过 `LocaleResolver` 对象完成的。
当收到请求时,`DispatcherServlet` 会寻找语言环境解析器,如果找到,它会尝试使用它来设置 Locale 环境。通过使用 `RequestContext.getLocale()` 方法,您始终可以检索由 Locale 解析器解析的语言环境。
除了自动识别 Locale 环境之外,您还可以为 handle 映射附加拦截器,在特定情况下更改 Locale 环境设置(例如,基于请求中的参数)。
Locale 解析器和拦截器在 `org.springframework.web.servlet.i18n` 包中定义并以正常方式在您的应用程序上下文中配置。Spring 中有以下 Locale 解析器可供选择。
- [Time Zone](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-timezone)
- [Header Resolver](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-localeresolver-acceptheader)
- [Cookie Resolver](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-localeresolver-cookie)
- [Session Resolver](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-localeresolver-session)
- [Locale Interceptor](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-localeresolver-interceptor)
### 时区
除了获取客户端的区域设置外,了解其时区通常也很有用。`LocaleContextResolver` 接口提供了 `LocaleResolver` 的扩展,让解析器提供更丰富的 `LocaleContext`,其中可能包括时区信息。
如果可用,可以使用 `RequestContext.getTimeZone()` 方法获取用户的 `TimeZone`。在 Spring 的 `ConversionService` 中注册的任何日期/时间 `Converter``Formatter` 对象会自动使用时区信息。
### 标头解析器
此 Locale 解析器检查客户端(例如网络浏览器)发送的请求中的 `accept-language` 头。通常,此头字段包含客户端操作系统的区域信息。请注意,此解析器不支持时区信息。
### Cookie 解析器
This locale resolver inspects a `Cookie` that might exist on the client to see if a `Locale` or `TimeZone` is specified. If so, it uses the specified details. By using the properties of this locale resolver, you can specify the name of the cookie as well as the maximum age. The following example defines a `CookieLocaleResolver`:
此 Locale 解析器检查客户端上是否存在 `Cookie`,以查看是否指定了 `Locale``TimeZone`。如果是,它会使用指定的详细信息。通过使用此 Locale 解析器的属性,可以指定 cookie 的名称以及最长期限。以下示例定义了 `CookieLocaleResolver`
```xml
<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 的属性:
| 属性 | 默认值 | Description |
| :------------- | :------------------------ | :------------------------------------------------------------------------------------------------------ |
| `cookieName` | 类名 + LOCALE | cookie 名 |
| `cookieMaxAge` | Servlet container default | cookie 在客户端上保留的最长时间。如果指定了“-1”则不会保留 cookie。它仅在客户端关闭浏览器之前可用。 |
| `cookiePath` | / | 将 cookie 的可见性限制在您网站的特定部分。当指定 `cookiePath`cookie 仅对该路径及其下方的路径可见。 |
### Session Resolver
`SessionLocaleResolver` 允许您从可能与用户请求相关联的会话中检索 `Locale``TimeZone`。与 `CookieLocaleResolver` 相比,此策略将本地选择的 locale 设置存储在 Servlet 容器的 `HttpSession` 中。因此,这些设置对于每个会话都是临时的,因此会在每个会话结束时丢失。
注意,这与外部会话管理机制(例如 Spring Session 项目)没有直接关系。此 `SessionLocaleResolver` 根据当前 `HttpServletRequest` 评估和修改相应的 `HttpSession` 属性。
### Locale Interceptor
You can enable changing of locales by adding the `LocaleChangeInterceptor` to one of the `HandlerMapping` definitions. It detects a parameter in the request and changes the locale accordingly, calling the `setLocale` method on the `LocaleResolver` in the dispatchers application context. The next example shows that calls to all `*.view` resources that contain a parameter named `siteLanguage` now changes the locale. So, for example, a request for the URL, `https://www.sf.net/home.view?siteLanguage=nl`, changes the site language to Dutch. The following example shows how to intercept the locale:
可以通过将 `LocaleChangeInterceptor` 添加到一个 `HandlerMapping` 定义来启用区域设置更改。它检测请求中的参数并相应地更改 Locale 环境,在调度程序的应用程序上下文中调用 `LocaleResolver` 上的 `setLocale` 方法。下面的示例显示调用所有包含名为 `siteLanguage` 的参数的 `*.view` 资源,以更改语言环境。因此,例如,对 URL `https://www.sf.net/home.view?siteLanguage=nl` 的请求将站点语言更改为荷兰语。以下示例显示了如何拦截语言环境:
```xml
<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>
```
## 主题
您可以应用 Spring Web MVC 框架主题来设置应用程序的整体外观,从而增强用户体验。主题是静态资源的集合,通常是样式表和图像,它们会影响应用程序的视觉风格。
### 定义一个主题
要在 Web 应用程序中使用主题,必须设置 `org.springframework.ui.context.ThemeSource` 接口的实现。`WebApplicationContext` 接口扩展了 `ThemeSource` 但将其职责委托给了专门的实现。默认情况下,委托是 `org.springframework.ui.context.support.ResourceBundleThemeSource` ,它从类的根路径加载属性文件。要使用自定义的 `ThemeSource` 实现或配置 `ResourceBundleThemeSource` 的基本名称前缀,您可以在应用程序上下文中使用保留名称 `themeSource` 注册一个 bean。Web 应用程序上下文自动检测具有该名称的 bean 并使用它。
当使用 `ResourceBundleThemeSource` 时,主题是在一个简单的属性文件中定义的。属性文件列出了构成主题的资源,如以下示例所示:
```properties
styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg
```
The keys of the properties are the names that refer to the themed elements from view code. For a JSP, you typically do this using the `spring:theme` custom tag, which is very similar to the `spring:message` tag. The following JSP fragment uses the theme defined in the previous example to customize the look and feel:
属性的键是从视图代码中引用主题元素的名称。对于 JSP通常使用 `spring:theme` 自定义标签来执行此操作,它与 `spring:message` 标签非常相似。以下 JSP 片段使用前面示例中定义的主题来自定义外观:
```xml
<%@ 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 提供的主题解析器:
| Class | Description |
| :--------------------- | :------------------------------------------------------------------------------------ |
| `FixedThemeResolver` | 选择一个固定的主题,使用 `defaultThemeName` 属性设置。 |
| `SessionThemeResolver` | 主题在用户的 HTTP 会话中维护。 它只需要为每个会话设置一次,但不会在会话之间持续存在。 |
| `CookieThemeResolver` | 所选主题存储在客户端的 cookie 中。 |
Spring 还提供了一个 `ThemeChangeInterceptor`,它允许使用一个简单的请求参数在每个请求上更改主题。
## Multipart 解析器
`org.springframework.web.multipart` 包中的 `MultipartResolver` 是一种解析 multipart 请求(包括文件上传)的策略。 有一个基于容器的 `StandardServletMultipartResolver` 实现,用于 Servlet 多部分请求解析。 请注意,从具有新 Servlet 5.0+ 基线的 Spring Framework 6.0 开始,基于 Apache Commons FileUpload 的过时的 `CommonsMultipartResolver` 不再可用。
要启用 multipart 处理,需要在 `DispatcherServlet` Spring 配置中声明一个名为 `multipartResolver``MultipartResolver``DispatcherServlet` 检测到它并将其应用于传入请求。 当接收到内容类型为 `multipart/form-data` 的 POST 时,解析器解析将当前 `HttpServletRequest` 包装为 `MultipartHttpServletRequest` 的内容,以提供对已解析文件的访问以及将部分作为请求参数公开。
### Servlet 多部分解析
Servlet 多部分解析需要通过 Servlet 容器配置启用。 为此:
- 在 Java 中,在 Servlet 注册上设置一个 `MultipartConfigElement`
- 在 `web.xml` 中,将 `<multipart-config>` 部分添加到 servlet 声明。
以下示例显示如何在 Servlet 注册上设置 `MultipartConfigElement`
```java
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 配置好,就可以添加一个名为 `multipartResolver``StandardServletMultipartResolver` 类型的 bean。
## 参考资料
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
- [Spring Framework 官方文档之 Web](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html)

View File

@ -0,0 +1,62 @@
---
title: Spring MVC 之 Filter
categories:
- Java
- 框架
- Spring
- SpringWeb
tags:
- Java
- 框架
- Spring
- Web
- Filter
date: 2023-02-14 17:44:09
permalink: /pages/4a164d/
---
# Spring MVC 之 Filter
`spring-web` 模块提供了一些有用的 Filter
- [Form Data](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#filters-http-put)
- [Forwarded Headers](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#filters-forwarded-headers)
- [Shallow ETag](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#filters-shallow-etag)
- [CORS](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#filters-cors)
## 表单内容过滤器
浏览器只能通过 HTTP GET 或 HTTP POST 提交表单数据,但非浏览器客户端也可以使用 HTTP PUT、PATCH 和 DELETE。 Servlet API 需要 `ServletRequest.getParameter*()` 系列方法来支持仅对 HTTP POST 的表单字段访问。
`spring-web` 模块提供了 `FormContentFilter` 来拦截内容类型为 `applicationx-www-form-urlencoded` 的 HTTP PUT、PATCH、DELETE 请求,从请求体中读取表单数据,并包装 `ServletRequest` 通过 `ServletRequest.getParameter()` 系列方法使表单数据可用。
## 转发过滤器
当请求通过代理(如负载均衡器)时,主机、端口和方案可能会发生变化,这使得从客户端角度创建指向正确主机、端口和方案的链接成为一项挑战。
[RFC 7239](https://tools.ietf.org/html/rfc7239) 定义了 `Forwarded` HTTP 头,代理可以使用它来提供有关原始请求的信息。还有其他非标准头,包括 `X-Forwarded-Host`、`X-Forwarded-Port`、`X-Forwarded-Proto`、`X-Forwarded-Ssl` 和 `X-Forwarded-Prefix`
`ForwardedHeaderFilter` 是一个 Servlet 过滤器,它修改请求以便 a) 根据 `Forwarded` 头更改主机、端口和 schemeb) 删除这些头以消除进一步的影响。该过滤器依赖于包装请求,因此它必须排在其他过滤器之前,例如 `RequestContextFilter`,它应该与修改后的请求一起使用,而不是原始请求。
`Forwarded` 头有安全考量,因为应用程序无法知道头是由代理按预期添加的,还是由恶意客户端添加的。这就是为什么应将信任边界处的代理配置为删除来自外部的不受信任的 `Forwarded` 头。还可以使用 `removeOnly=true` 配置 `ForwardedHeaderFilter`,在这种情况下它会删除但不使用头。
为了支持[异步请求](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-async)和错误分派,此过滤器应使用 `DispatcherType.ASYNC``DispatcherType.ERROR` 进行映射。如果使用 Spring Framework 的 `AbstractAnnotationConfigDispatcherServletInitializer`(参见 [Servlet Config](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-container-config)),所有过滤器都会自动为所有调度类型注册。但是,如果通过 `web.xml` 或在 Spring Boot 中通过 `FilterRegistrationBean` 注册过滤器,请确保除了 `DispatcherType.REQUEST` 之外还包括 `DispatcherType.ASYNC``DispatcherType.ERROR`
## ETag 过滤器
`ShallowEtagHeaderFilter` 过滤器通过缓存写入响应的内容并从中计算 MD5 哈希来创建“浅”ETag。下次客户端发送时它会做同样的事情但它还会将计算值与 `If-None-Match` 请求标头进行比较,如果两者相等,则返回 304 (NOT_MODIFIED)。
此策略节省网络带宽但不节省 CPU因为必须为每个请求计算完整响应。前面描述的控制器级别的其他策略可以避免计算。
此过滤器有一个 `writeWeakETag` 参数,该参数将过滤器配置为写入类似于以下内容的弱 ETag`W"02a2d595e6ed9a0b24f027f2b63b134d6"`(如 [RFC 7232 Section 2.3](https://tools.ietf.org/html/rfc7232#section-2.3) 中所定义)。
为了支持[异步请求](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-async),这个过滤器必须用 `DispatcherType.ASYNC` 映射,这样过滤器才能延迟并成功生成一个 ETag 到最后最后一次异步调度。如果使用 Spring Framework 的 `AbstractAnnotationConfigDispatcherServletInitializer`,所有过滤器都会自动为所有调度类型注册。但是,如果通过 `web.xml` 或在 Spring Boot 中通过 `FilterRegistrationBean` 注册过滤器,请确保包含 `DispatcherType.ASYNC`
## 跨域过滤器
Spring MVC 通过控制器上的注解为 CORS 配置提供细粒度支持。但是,当与 Spring Security 一起使用时,建议依赖内置的 `CorsFilter`,它必须在 Spring Security 的过滤器链之前订阅。
## 参考资料
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
- [Spring Framework 官方文档之 Web](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html)

View File

@ -0,0 +1,53 @@
---
title: Spring MVC 之 Controller
categories:
- Java
- 框架
- Spring
- SpringWeb
tags:
- Java
- 框架
- Spring
- Web
- Controller
date: 2023-02-14 19:21:22
permalink: /pages/5d002f/
---
# Spring MVC 之 Controller
Spring MVC 提供了一种基于注解的编程模型,`@Controller` 和 `@RestController` 组件使用注解来表达请求映射、请求输入、异常处理等。注释控制器具有灵活的方法签名,并且不必扩展基类或实现特定接口。以下示例显示了一个由注释定义的控制器:
```java
@Controller
public class HelloController {
@GetMapping("/hello")
public String handle(Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
```
在前面的示例中,该方法接受一个 `Model` 并以 `String` 形式返回一个视图名称,但还存在许多其他选项。
## 声明
## RequestMapping
## 处理方法
## Model
## 数据绑定
## 异常
## @ControllerAdvice
## 参考资料
- [Spring Framework 官方文档](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html)
- [Spring Framework 官方文档之 Web](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html)

View File

@ -23,6 +23,9 @@ hidden: true
## 📖 内容
- [Spring WebMvc](01.SpringWebMvc.md)
- [DispatcherServlet](02.DispatcherServlet.md)
- [Filter](03.Filter.md)
- [Controller](04.Controller.md)
- [SpringBoot 之应用 EasyUI](21.SpringBoot之应用EasyUI.md)
## 📚 资料

Binary file not shown.

10
prettier.config.js Normal file
View File

@ -0,0 +1,10 @@
/**
* @see https://prettier.io/docs/en/options.html
* @see https://prettier.io/docs/en/configuration.html
*/
module.exports = {
tabWidth: 2,
semi: false,
singleQuote: true,
trailingComma: 'none'
}