摘要:这部分文档介绍了对构建在Servlet API上并部署到Servlet容器上的Servlet-stack web应用程序的支持。单独的章节包括Spring MVC、视图技术、CORS支持和WebSocket支持。有关反应堆栈web应用程序,请参见反应堆栈上的web。
一、Spring MVC简介
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)。
二、DispatcherServlet
与许多其他web框架一样,Spring MVC是围绕前端控制器模式设计的,其中中央Servlet DispatcherServlet提供了用于请求处理的共享算法,而实际工作是由可配置的委托组件执行的。该模型灵活,支持多种工作流。与任何Servlet一样,DispatcherServlet需要使用Java配置或web.xml根据Servlet规范声明和映射。反过来,DispatcherServlet使用Spring配置来发现它需要的用于请求映射、视图解析、异常处理等的委托组件。下面的Java配置示例注册并初始化DispatcherServlet, Servlet容器会自动检测到它(请参阅Servlet配置)
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
注:除了直接使用ServletContext API,您还可以扩展AbstractAnnotationConfigDispatcherServletInitializer并覆盖特定的方法(请参阅上下文层次结构下的示例)。
下面是通过XML配置注册并初始化DispatcherServlet的Web项目例子:
<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引导遵循不同的初始化顺序。Spring Boot没有连接到Servlet容器的生命周期中,而是使用Spring配置引导自身和嵌入式Servlet容器。过滤器和Servlet声明在Spring配置中检测到,并在Servlet容器中注册。有关更多细节,请参见Spring引导文档。
2.1. Context Hierarchy
DispatcherServlet需要一个WebApplicationContext(普通ApplicationContext的扩展)作为它自己的配置。WebApplicationContext具有到Servlet上下文及其关联的Servlet的链接。它还绑定到ServletContext,以便应用程序可以在requestcontext tutils上使用静态方法来查找WebApplicationContext(如果需要访问的话)。对于许多应用程序来说,拥有一个单一的WebApplicationContext既简单又足够。也可以有一个上下文层次结构,其中一个根WebApplicationContext跨多个DispatcherServlet(或其他Servlet)实例共享,每个实例都有自己的子WebApplicationContext配置。有关上下文层次结构特性的更多信息,请参见ApplicationContext的附加功能。根WebApplicationContext通常包含基础设施bean,例如需要跨多个Servlet实例共享的数据存储库和业务服务。这些bean是有效继承的,可以在特定于Servlet的子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参数为空。
2.2 特殊的Bean类型
DispatcherServlet将委托给特殊bean来处理请求并呈现适当的响应。“特殊bean”指的是实现WebFlux框架契约的spring托管对象实例。它们通常带有内置的契约,但是您可以自定义它们的属性并扩展或替换它们。
下表列出了DispatcherHandler检测到的特殊bean:
Bean类型 | 说明 |
HandlerMapping | 将请求映射到处理程序以及用于预处理和后处理的拦截器列表。映射基于一些标准,这些标准的细节因HandlerMapping实现的不同而不同。两个主要的HandlerMapping实现是 RequestMappingHandlerMapping(它支持@RequestMapping注释方法)和SimpleUrlHandlerMapping(它维护URI路径模式到处理程序的显式注册)。 |
HandlerAdapter | 帮助DispatcherServlet调用映射到请求的处理程序,不管该处理程序实际如何调用。例如,调用带注释的控制器需要解析注释。HandlerAdapter的主要目的是保护DispatcherServlet不受这些细节的影响。 |
HandlerExceptionResolver | 解决异常的策略,可以将异常映射到处理程序、HTML错误视图或其他目标。看到异常。 |
ViewResolver | 解析从处理程序返回到实际视图的基于逻辑字符串的视图名,使用该视图呈现响应。参见视图解析和视图技术。 |
LocaleResolver , LocaleContextResolver | 为了能够提供国际化的视图,解析客户机正在使用的区域设置(可能还有它们所在的时区)。看地区。 |
ThemeResolver | 解决web应用程序可以使用的主题——例如,提供个性化布局。看到主题。 |
MultipartResolver | 使用一些多部分解析库解析多部分请求(例如,浏览器表单文件上传)的抽象。看到几部分的解析器。 |
FlashMapManager | 存储和检索“输入”和“输出”FlashMap,可以使用它们将属性从一个请求传递到另一个请求,通常是通过重定向。看到Flash属性。 |
2.3. Web MVC Config
应用程序可以声明处理请求所需的特殊Bean类型中列出的基础设施Bean。DispatcherServlet检查每个特殊bean的WebApplicationContext。如果没有匹配的bean类型,则返回到DispatcherServlet.properties中列出的默认类型。
在大多数情况下,MVC配置是最好的起点。它以Java或XML声明所需的bean,并提供更高级别的配置回调API来定制它。
注:Spring Boot依赖于MVC Java配置来配置Spring MVC,并提供了许多额外的方便选项。
2.4. Servlet Config
在Servlet 3.0+环境中,您可以选择以编程方式配置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容器。一个名为AbstractDispatcherServletInitializer的WebApplicationInitializer的抽象基类实现,通过覆盖指定servlet映射和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还提供了一种方便的方法来添加过滤器实例,并将它们自动映射到DispatcherServlet,如下面的示例所示:
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
// ...
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
}
每个过滤器都添加了一个基于其具体类型的默认名称,并自动映射到DispatcherServlet。
AbstractDispatcherServletInitializer的isAsyncSupported受保护方法提供了一个单独的位置来启用对DispatcherServlet和映射到它的所有过滤器的异步支持。默认情况下,此标志设置为true。
最后,如果需要进一步定制DispatcherServlet本身,可以重写createDispatcherServlet方法。
2.5. Processing
- 在请求中搜索WebApplicationContext并将其绑定到控制器和流程中的其他元素可以使用的属性。它在缺省情况下绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE 中。
- 区域设置解析器绑定到请求,以便让流程中的元素解析处理请求(呈现视图、准备数据等)时使用的区域设置。如果不需要地区解析,则不需要地区解析器。
- 主题解析器绑定到请求,以让视图等元素决定使用哪个主题。如果不使用主题,可以忽略它。
- 如果指定多部分文件解析器,将检查请求是否有多部分。如果找到多个部分,则请求被包装在MultipartHttpServletRequest中,以便由流程中的其他元素进行进一步处理。有关多部分处理的更多信息,请参见多部分解析器。
- 搜索适当的处理程序。如果找到了处理程序,则执行与该处理程序(预处理程序、后处理程序和控制器)关联的执行链,以便准备模型或呈现。或者,对于带注释的控制器,可以呈现响应(在HandlerAdapter中),而不是返回视图。
- 如果返回模型,则呈现视图。如果没有返回模型(可能是由于预处理器或后处理器拦截了请求,可能是出于安全原因),则没有呈现视图,因为请求可能已经完成。
在WebApplicationContext中声明的HandlerExceptionResolver bean用于解决请求处理期间抛出的异常。这些异常解析器允许定制处理异常的逻辑。
Spring DispatcherServlet还支持返回Servlet API指定的最后修改日期。确定特定请求的最后修改日期的过程非常简单:DispatcherServlet查找适当的处理程序映射并测试找到的处理程序是否实现了LastModified接口。如果是,那么LastModified接口的long getLastModified(request)方法的值将返回给客户机。
您可以通过向web.xml文件中的Servlet声明中添加Servlet初始化参数(init-param元素)来自定义单个DispatcherServlet实例。下表列出了支持的参数:
参数 | 说明 |
contextClass | 实现ConfigurableWebApplicationContext的类,该类将由该Servlet实例化并在本地配置。默认情况下,使用XmlWebApplicationContext。 |
contextConfigLocation | 传递到上下文实例(由contextClass指定)的字符串,以指示在何处可以找到上下文。字符串可能由多个字符串(使用逗号作为分隔符)组成,以支持多个上下文。对于定义了两次的bean的多个上下文位置,优先考虑最新的位置。 |
namespace | WebApplicationContext的名称空间。默认为servlet-name servlet。 |
throwExceptionIfNoHandlerFound | 当没有为请求找到处理程序时,是否抛出NoHandlerFoundException。然后可以使用HandlerExceptionResolver(例如,通过使用@ExceptionHandler控制器方法)捕捉异常,并像处理其他任何异常一样处理异常。 默认情况下,该值设置为false,在这种情况下,DispatcherServlet将响应状态设置为404 (NOT_FOUND),而不会引发异常。 注意,如果还配置了缺省servlet处理,未解析的请求总是被转发到缺省servlet, 404永远不会被抛出。 |
2.6. 拦截器
所有HandlerMapping实现都支持处理程序拦截器,这些拦截器在您希望将特定功能应用于特定请求时非常有用——例如,检查主体。拦截器必须从org.springframe.web实现HandlerInterceptor。servlet包有三种方法,它们应该提供足够的灵活性来进行各种预处理和后处理:
- preHandle(..):在实际处理程序执行之前
- postHandle(..):处理程序执行后
- afterCompletion(..):完成请求后
The preHandle(..)
方法返回一个布尔值。您可以使用此方法中断或继续执行链的处理。当该方法返回true时,处理程序执行链将继续。当它返回false时,DispatcherServlet假定拦截器本身已经处理了请求(例如,呈现了一个适当的视图),并且没有继续执行执行链中的其他拦截器和实际处理程序。
有关如何配置拦截器的示例,请参阅MVC配置一节中的拦截器。您还可以通过在单个HandlerMapping实现上使用setter直接注册它们。
注意,postHandle对于@ResponseBody和ResponseEntity方法用处不大,响应是在HandlerAdapter中写入并提交的,并且是在postHandle之前提交的。这意味着对响应进行任何更改(例如添加额外的标题)都已经太晚了。对于这些场景,您可以实现ResponseBodyAdvice,并将其声明为控制器通知bean,或者直接在RequestMappingHandlerAdapter上配置它。
2.7. 异常
如果在请求映射期间发生异常,或者从请求处理程序(如@Controller)抛出异常,DispatcherServlet将委托给HandlerExceptionResolver bean链来解决异常并提供替代处理,这通常是错误响应。
下表列出了可用的HandlerExceptionResolver实现:
表2.HandlerExceptionResolver实现
HandlerExceptionResolver | 描述 |
SimpleMappingExceptionResolver | 异常类名和错误视图名之间的映射。用于在浏览器应用程序中呈现错误页面。 |
DefaultHandlerExceptionResolver | 解决Spring MVC引发的异常,并将其映射到HTTP状态码。请参阅备用ResponseEntityExceptionHandler和REST API异常。 |
ResponseStatusExceptionResolver | 使用@ResponseStatus注释解决异常,并根据注释中的值将异常映射到HTTP状态代码。 |
ExceptionHandlerExceptionResolver | 通过调用@Controller或@ControllerAdvice类中的@ExceptionHandler方法来解决异常。看到@ExceptionHandler方法。 |
Chain of Resolvers(解析器链)
通过在Spring配置中声明多个HandlerExceptionResolver bean并根据需要设置它们的顺序属性,可以形成异常解析器链。order属性越高,异常解析器的位置就越晚。
HandlerExceptionResolver合同规定它可以返回:
- 指向错误视图的ModelAndView。
- 如果异常是在解析器中处理的,则为空ModelAndView。
- 如果异常仍然未解析,则为null,以便后续的解析器尝试,如果异常在最后仍然存在,则允许将其冒泡到Servlet容器。
MVC配置自动为默认Spring MVC异常、@ResponseStatus注释异常和@ExceptionHandler方法的支持声明内置的解析器。您可以自定义该列表或替换它。
Container Error Page(容器错误页面)
如果任何HandlerExceptionResolver仍未解析异常,因此,该异常将继续传播,或者如果响应状态设置为错误状态(即4xx, 5xx), Servlet容器ca将以HTML呈现默认错误页面。要自定义容器的默认错误页,可以在web.xml中声明错误页映射。下面的例子展示了如何做到这一点:
<error-page>
<location>/error</location>
</error-page>
在前面的例子中,当异常冒泡或者响应具有错误状态时,Servlet容器在容器中向配置的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<String, Object>();
map.put("status", request.getAttribute("javax.servlet.error.status_code"));
map.put("reason", request.getAttribute("javax.servlet.error.message"));
return map;
}
}
注:Servlet API不提供在Java中创建错误页面映射的方法。但是,您可以同时使用WebApplicationInitializer和最低限度的web.xml。
2.8. View Resolution(视图解析器)
Spring MVC定义了视图解析器和视图接口,它们允许您在浏览器中呈现模型,而无需将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理在将数据移交给特定视图技术之前的准备工作。
下表提供了关于ViewResolver层次结构的更多细节:
表3.ViewResolver实现
ViewResolver | 描述 |
AbstractCachingViewResolver | 它们解析的AbstractCachingViewResolver的子类缓存视图实例。缓存提高了某些视图技术的性能。可以通过将缓存属性设置为false关闭缓存。此外,如果必须在运行时刷新某个视图(例如,修改FreeMarker模板时),可以使用removeFromCache(String viewName, Locale loc)方法。 |
XmlViewResolver | Spring MVC定义了视图解析器和视图接口,它们允许您在浏览器中呈现模型,而无需将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理在将数据移交给特定视图技术之前的准备工作。 默认配置文件是/WEB-INF/views.xml。 |
ResourceBundleViewResolver | Spring MVC定义了视图解析器和视图接口,它们允许您在浏览器中呈现模型,而无需将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理在将数据移交给特定视图技术之前的准备工作。 对于它应该解析的每个视图,它使用属性[viewname].(class)的值作为视图类和属性[viewname]的值。url作为视图url。您可以在关于视图技术的一章中找到示例。 |
UrlBasedViewResolver | Spring MVC定义了视图解析器和视图接口,它们允许您在浏览器中呈现模型,而无需将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理在将数据移交给特定视图技术之前的准备工作。 如果逻辑名称以一种简单的方式匹配视图资源的名称,而不需要任意映射,那么这种方法是合适的。 |
InternalResourceViewResolver | UrlBasedViewResolver的方便子类,它支持InternalResourceView(实际上是servlet和jsp)和子类,如JstlView和TilesView。您可以使用setViewClass(..)为这个解析器生成的所有视图指定视图类。有关详细信息,请参见UrlBasedViewResolver javadoc。 |
FreeMarkerViewResolver | UrlBasedViewResolver的方便的子类,它支持免费的emarkerview和它们的自定义子类。 |
ContentNegotiatingViewResolver | ViewResolver接口的实现,该接口基于请求文件名或Accept头解析视图。看到内容协商。 |
Handing(处理)
您可以通过声明多个解析器bean来链接视图解析器,如果需要,还可以通过设置order属性来指定order。记住,阶属性越高,视图解析器在链中的位置就越晚。
视图解析器的契约指定它可以返回null,以指示找不到视图。但是,对于JSP和InternalResourceViewResolver,判断JSP是否存在的惟一方法是通过RequestDispatcher执行分派。因此,您必须始终将InternalResourceViewResolver配置为在视图解析器的整体顺序中最后一个。
配置视图解析就像在Spring配置中添加ViewResolver bean一样简单。MVC配置为视图解析器和添加无逻辑的视图控制器提供了专用的配置API,这些视图控制器对于没有控制器逻辑的HTML模板呈现非常有用。
Redirecting(重定向)
Spring MVC定义了视图解析器和视图接口,它们允许您在浏览器中呈现模型,而无需将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理在将数据移交给特定视图技术之前的准备工作。
UrlBasedViewResolver(及其子类)认为这是一条需要重定向的指令。视图名称的其余部分是重定向URL。
净效果与控制器返回RedirectView相同,但是现在控制器本身可以根据逻辑视图名进行操作。逻辑视图名(如redirect:/myapp/some/resource)相对于当前Servlet上下文重定向,而名称(如redirect: http://myhost.com/some/任意rry/path)重定向到绝对URL。
注意,如果用@ResponseStatus注释控制器方法,则注释值优先于RedirectView设置的响应状态。
Forwarding(转发)
Spring MVC定义了视图解析器和视图接口,它们允许您在浏览器中呈现模型,而无需将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理在将数据移交给特定视图技术之前的准备工作。
这将创建一个InternalResourceView,它执行RequestDispatcher.forward()。因此,这个前缀在InternalResourceViewResolver和InternalResourceView(对于JSP)中是没有用的,但是如果您使用另一种视图技术,但仍然想要强制Servlet/JSP引擎处理资源的转发,那么这个前缀是有用的。请注意,您也可以链接多个视图解析器。
Content Negotiation(内容转让)
contentatingviewresolver并不解析视图本身,而是将视图委托给其他视图解析器,并选择与客户端请求的表示类似的视图。表示可以从Accept头或查询参数(例如,“/path?format=pdf”)确定。
Spring MVC定义了视图解析器和视图接口,它们允许您在浏览器中呈现模型,而无需将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理在将数据移交给特定视图技术之前的准备工作。
列表中具有兼容内容类型的第一个视图将表示返回给客户机。如果视图解析器链不能提供兼容的视图,则会参考通过DefaultViews属性指定的视图列表。后一个选项适用于单例视图,它可以呈现当前资源的适当表示,而不管逻辑视图名称如何。Accept头可以包含通配符(例如text/*),在这种情况下,内容类型为text/xml的视图是兼容的匹配。
有关配置细节,请参见MVC Config下的视图解析器。
2.9. Locale
Spring的大部分架构都支持国际化,正如Spring web MVC框架所做的那样。DispatcherServlet允许您通过使用客户机的地区自动解析消息。这是用LocaleResolver对象完成的。
Spring MVC定义了视图解析器和视图接口,它们允许您在浏览器中呈现模型,而无需将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理在将数据移交给特定视图技术之前的准备工作。
通过使用RequestContext.getLocale()方法,您总是可以检索由地区解析器解析的地区。
Spring MVC定义了视图解析器和视图接口,它们允许您在浏览器中呈现模型,而无需将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理在将数据移交给特定视图技术之前的准备工作。
区域设置解析器和拦截器在org.springframe .web.servlet中定义。i18n包,并在您的应用程序上下文中以正常方式配置。Spring中包含了以下区域设置解析器的选择。
Time Zone
除了获取客户机的语言环境之外,了解其时区通常也很有用。LocaleContextResolver接口提供了LocaleResolver的扩展,允许解析器提供更丰富的LocaleContext,其中可能包括时区信息。
当可用时,可以使用RequestContext.getTimeZone()方法获取用户的时区。在Spring的ConversionService中注册的任何日期/时间转换器和格式化程序对象都会自动使用时区信息。
Header Resolver
这个区域设置解析器检查客户机(例如web浏览器)发送的请求中的accept-language标头。通常,这个头字段包含客户机操作系统的地区。请注意,此解析器不支持时区信息。
Cookie Resolver
此区域设置解析器检查客户端上可能存在的Cookie,以查看是否指定了区域设置或时区。如果是,则使用指定的细节。通过使用此区域设置解析器的属性,可以指定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的属性:
表4.CookieLocaleResolver属性
属性 | 默认值 | 描述 |
cookieName | classname + LOCALE | cookie名称 |
cookieMaxAge | Servlet container default | cookie在客户端上存在的最长时间。如果指定-1,cookie将不会被持久化。它只有在客户端关闭浏览器时才可用。 |
cookiePath | / | 将cookie的可见性限制在站点的某个部分。当指定cookiePath时,cookie只对该路径及其下面的路径可见。 |
Session Resolver
SessionLocaleResolver允许您从会话中检索可能与用户请求关联的地区和时区。与CookieLocaleResolver不同,该策略将本地选择的区域设置存储在Servlet容器的HttpSession中。因此,这些设置对于每个会话都是临时的,因此在每个会话结束时都会丢失。
请注意,与外部会话管理机制(如Spring会话项目)没有直接关系。此SessionLocaleResolver针对当前HttpServletRequest计算和修改相应的HttpSession属性。
Locale Interceptor
Spring MVC定义了视图解析器和视图接口,它们允许您在浏览器中呈现模型,而无需将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理在将数据移交给特定视图技术之前的准备工作。
它在请求中检测一个参数并相应地更改语言环境,在dispatcher的应用程序上下文中调用LocaleResolver上的setLocale方法。下一个示例显示对all *的调用。包含名为siteLanguage的参数的视图资源现在更改了地区设置。例如,请求URL http://www.sf.net/home.view?站点语言=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>
2.10. Themes
您可以应用Spring Web MVC框架主题来设置应用程序的整体外观,从而增强用户体验。主题是静态资源的集合,通常是样式表和图像,它们影响应用程序的视觉样式。
Defining a theme(定义一个主题)
Spring MVC定义了视图解析器和视图接口,它们允许您在浏览器中呈现模型,而无需将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理在将数据移交给特定视图技术之前的准备工作。
ThemeSource接口。WebApplicationContext接口扩展了ThemeSource,但将其职责委托给专用实现。默认情况下,委托是一个org.springframe .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>
Spring MVC定义了视图解析器和视图接口,它们允许您在浏览器中呈现模型,而无需将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理在将数据移交给特定视图技术之前的准备工作。
结果,属性文件从类路径的根加载。这样,你就可以放凉了。在类路径根目录中的属性主题定义(例如,在/WEB-INF/classes中)。ResourceBundleThemeSource使用标准的Java资源包加载机制,允许主题完全国际化。例如,我们可以有一个/WEB-INF/classes/cool_nl。属性,该属性引用具有荷兰文本的特殊背景图像。
Resolving Themes
定义主题(如前面部分所述)后,您将决定使用哪个主题。DispatcherServlet寻找一个名为themeResolver的bean,以确定要使用哪个themeResolver实现。主题解析器的工作方式与LocaleResolver非常相似。它检测用于特定请求的主题,还可以更改请求的主题。下表描述了Spring提供的主题解析器:
表5所示.ThemeResolver实现
Class | 描述 |
FixedThemeResolver | 选择一个固定主题,通过使用defaultThemeName属性设置。 |
SessionThemeResolver | 主题在用户的HTTP会话中维护。它只需要为每个会话设置一次,而不是在会话之间持久化。 |
CookieThemeResolver | 所选主题存储在客户机上的cookie中。 |
Spring还提供了一个ThemeChangeInterceptor,它允许使用一个简单的请求参数在每个请求上更改主题。
2.11. Multipart Resolver
来自org.springframe .web的多部分解决程序。多部分包是一种解析包括文件上传在内的多部分请求的策略。一种实现基于Commons FileUpload,另一种实现基于Servlet 3.0多部分请求解析。
要启用多部分处理,需要在DispatcherServlet Spring配置中声明一个名为多部分解决程序的多部分解决程序bean。DispatcherServlet检测到它并将其应用于传入请求。当接收到内容类型为multipart/form-data的POST时,解析器将解析内容并将当前HttpServletRequest包装为MultipartHttpServletRequest,以提供对已解析部件的访问,并将其作为请求参数公开。
Apache Commons FileUpload
要使用Apache Commons FileUpload,可以配置一个名为multipartResolver的CommonsMultipartResolver类型的bean。您还需要将commons-fileupload作为类路径上的依赖项。
Servlet 3.0
Servlet 3.0多部分解析需要通过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 3.0配置就绪,您就可以添加一个名为multipartResolver的StandardServletMultipartResolver类型的bean。
2.12. Logging
Spring MVC中的调试级日志记录被设计为紧凑、最小化和人性化。它关注的是一遍又一遍地有用的高价值信息,而不是只有在调试特定问题时才有用的信息。
跟踪级日志记录通常遵循与调试相同的原则(例如,也不应该是消防软管),但是可以用于调试任何问题。此外,一些日志消息可能在跟踪和调试时显示不同级别的详细信息。
良好的日志记录来自使用日志的经验。如果您发现有任何不符合规定目标的地方,请告诉我们。
Sensitive Data(敏感数据)
调试和跟踪日志记录可能会记录敏感信息。这就是为什么请求参数和头在默认情况下是被屏蔽的,它们的完整日志记录必须通过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(Dynamic registration) {
registration.setInitParameter("enableLoggingRequestDetails", "true");
}
}
三、Filters
spring-web模块提供了一些有用的过滤器:
3.1. Form Data
浏览器只能通过HTTP GET或HTTP POST提交表单数据,但非浏览器客户机也可以使用HTTP PUT、PATCH和DELETE。Servlet API需要Servlet trequest . getparameter *()方法来支持仅用于HTTP POST的表单字段访问。
spring-web模块提供了FormContentFilter来拦截带有内容类型的application/x-www-form-urlencode的HTTP PUT、PATCH和DELETE请求,从请求体读取表单数据,并包装ServletRequest以使表单数据通过ServletRequest. getparameter *()系列方法可用。
3.2. Forwarded Headers
当请求通过代理(如负载均衡器)时,主机、端口和方案可能会更改,这使得从客户端角度创建指向正确主机、端口和方案的链接成为一个挑战。
RFC 7239定义了转发的HTTP头,代理可以使用它来提供关于原始请求的信息。还有其他非标准头文件,包括x转发主机、x转发端口、x转发原型、x转发ssl和x转发前缀。
forward headerfilter是一个Servlet过滤器,它基于转发的标头修改请求的主机、端口和方案,然后删除这些标头。
由于应用程序无法知道消息头是由代理添加的,还是由恶意客户端添加的,因此转发消息头需要考虑安全问题。这就是为什么应该将信任边界处的代理配置为删除来自外部的不受信任转发头的原因。您还可以使用removeOnly=true配置转发的headerfilter,在这种情况下,它将删除但不使用标题。
3.3. Shallow ETag
Spring MVC定义了视图解析器和视图接口,它们允许您在浏览器中呈现模型,而无需将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理在将数据移交给特定视图技术之前的准备工作。
下一次客户机发送时,它也会执行相同的操作,但是它还会将计算出的值与if - none - match请求标头进行比较,如果两者相等,则返回304 (NOT_MODIFIED)。
这种策略节省了网络带宽,但不节省CPU,因为必须为每个请求计算完整的响应。前面描述的控制器级别的其他策略可以避免计算。看到HTTP缓存。
该过滤器具有writeWeakETag参数,该参数将过滤器配置为编写类似于以下内容的弱ETags: W/“02a2d595e6ed9a0b24f027f2b63b134d6”(如RFC 7232节2.3中定义的那样)。
3.4. CORS
Spring MVC通过控制器上的注解为CORS配置提供了细粒度的支持。但是,当与Spring Security一起使用时,我们建议依赖于必须在Spring Security的过滤器链之前订购的内置CorsFilter。
有关CORS和CORS过滤器的详细信息,请参阅有关部分。
四、Annotated Controllers
Spring MVC提供了一个基于注解的编程模型,其中@Controller和@RestController组件使用注释来表示请求映射、请求输入、异常处理等。带注释的控制器具有灵活的方法签名,不需要扩展基类或实现特定的接口。下面的例子显示了一个由注解定义的控制器:
@Controller
public class HelloController {
@GetMapping("/hello")
public String handle(Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
在前面的例子中,该方法接受一个模型,并将视图名作为字符串返回,但是还存在许多其他选项,本章稍后将对此进行解释。
关于spring.io的指南和教程。使用本节中描述的基于注释的编程模型。
4.1. Declaration
您可以使用Servlet的WebApplicationContext中的标准Spring bean定义来定义控制器bean。@Controller构造型允许自动检测,与Spring一般支持在类路径中检测@Component类并自动注册bean定义相一致。它还充当带注释类的原型,指示其作为web组件的角色。
要自动检测此类@Controller bean,可以将组件扫描添加到Java配置中,如下面的示例所示:
@Configuration
@ComponentScan("org.example.web")
public class WebConfig {
// ...
}
下面的例子展示了与前一个例子等价的XML配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example.web"/>
<!-- ... -->
</beans>
@RestController是一个复合注释,它本身是由@Controller和@ResponseBody进行元注释的,以指示其每个方法都继承类型级别的@ResponseBody注释的控制器,因此,相对于使用HTML模板解析和呈现视图,它直接向响应体写入内容。
AOP代理
在某些情况下,您可能需要在运行时用AOP代理装饰控制器。一个例子是,如果您选择在控制器上直接使用@Transactional注释。在这种情况下,特别是对于控制器,我们建议使用基于类的代理。这通常是控制器的默认选择。但是,如果控制器必须实现一个不是Spring上下文回调的接口(例如InitializingBean、*Aware等),则可能需要显式地配置基于类的代理。例如,使用,您可以更改为<tx:annotation-driven proxy-target-class="true"/>。
4.2.
Request Mapping
您可以使用@RequestMapping注解将请求映射到控制器方法。它具有各种属性,可以通过URL、HTTP方法、请求参数、头和媒体类型进行匹配。您可以在类级别使用它来表示共享映射,也可以在方法级别使用它来缩小到特定端点映射。
也有HTTP方法的@RequestMapping的快捷方式的变体:
快捷方式是提供的自定义注释,这是因为,与使用@RequestMapping相比,大多数控制器方法应该映射到特定的HTTP方法,而@RequestMapping在默认情况下与所有HTTP方法匹配。同时,在类级别上仍然需要@RequestMapping来表示共享映射。
下面的例子有类型和方法级别映射:
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
- @PatchMapping
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
URI patterns
您可以使用以下glob模式和通配符映射请求:
- ?匹配一个字符
- * 在路径段中匹配零个或多个字符
- ** 匹配零个或多个路径段
您还可以声明URI变量并使用@PathVariable访问它们的值,如下面的示例所示:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
您可以在类和方法级别声明URI变量,如下面的示例所示:
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
@GetMapping("/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
URI变量会自动转换为适当的类型,或者引发TypeMismatchException异常。默认情况下支持简单类型(int、long、Date等),您可以注册对任何其他数据类型的支持。参见类型转换和DataBinder。
您可以显式地为URI变量命名(例如,@PathVariable(“customId”)),但是如果名称相同,并且您的代码是用调试信息编译的,或者是用Java 8上的-parameters编译器标志编译的,则可以忽略该细节。
语法{varName:regex}使用正则表达式声明URI变量,正则表达式的语法为{varName:regex}。例如,给定URL“/spring-web-3.0.5 .jar”,下面的方法提取名称、版本和文件扩展名:
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
// ...
}
URI路径模式还可以嵌入${…}占位符,这些占位符在启动时通过对本地、系统、环境和其他属性源使用PropertyPlaceHolderConfigurer解析。例如,您可以使用它基于某些外部配置参数化基本URL。
Spring MVC使用来自Spring -core的路径匹配契约和AntPathMatcher实现进行URI路径匹配。
Pattern Comparison(模式对比)
当多个模式匹配一个URL时,必须对它们进行比较以找到最佳匹配。这是通过使用AntPathMatcher完成的。getPatternComparator(字符串路径),它查找更具体的模式。
如果模式的URI变量计数较低,且单个通配符计数为1,而双通配符计数为2,则模式就不那么特定。如果分数相等,则选择较长的模式。给定相同的分数和长度,将选择URI变量多于通配符的模式。
默认映射模式(/**)被排除在评分之外,并且总是最后排序。此外,前缀模式(例如/public/**)被认为没有其他没有双通配符的模式那么具体。
有关详细信息,请参阅AntPathMatcher中的AntPatternComparator,并请记住您可以自定义PathMatcher实现。请参阅配置部分中的路径匹配。
Suffix Match(后缀匹配)
默认情况下,Spring MVC执行.*后缀模式匹配,这样映射到/person的控制器也隐式映射到/person.*。然后,文件扩展名用于解释请求的内容类型,以用于响应(即,而不是Accept标头)——例如/person.pdf, /person.xml等。
当浏览器用于发送难以一致解释的Accept标头时,以这种方式使用文件扩展名是必要的。目前,这不再是必须的,应该首选使用Accept标头。
随着时间的推移,文件扩展名的使用在很多方面都被证明是有问题的。当与URI变量、路径参数和URI编码的使用重叠时,可能会造成歧义。关于基于url的授权和安全性的推理(更多细节请参见下一节)也变得更加困难。