Web上的Servlet堆栈
Web上的Servlet堆栈
这部分文档涵盖了对Servlet堆栈的支持,构建在Servlet API上并部署到Servlet容器的Web应用程序。单独的章节包括Spring MVC, View技术,CORS支持和WebSocket支持。对于反应堆栈,Web应用程序,请转至Web上的反应堆栈。
1. Spring Web MVC
1.1。介绍
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 Web MVC。在下一节 介绍春季WebFlux。
有关基准信息以及与Servlet容器和Java EE版本范围的兼容性,请访问Spring Framework Wiki。
1.2。DispatcherServlet的
弹簧MVC,像许多其它Web框架,被设计围绕前端控制器图案,其中中央Servlet的DispatcherServlet,而实际的工作是由可配置的,委托组件执行提供了一种用于请求处理的共享算法。该模型非常灵活,支持多种工作流程。
的DispatcherServlet,因为任何Servlet,需要根据使用Java配置或在Servlet说明书中声明和映射web.xml。反过来,DispatcherServletSpring使用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之外,还可以扩展 |
以下是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使用Spring配置来引导自身和嵌入的Servlet容器,而不是挂钩到Servlet容器的生命周期。 |
1.2.1。上下文层次结构
DispatcherServlet对于自己的配置,希望有WebApplicationContext一个平原的扩展 ApplicationContext。WebApplicationContext有一个链接, ServletContext并Servlet与之相关联。它也受ServletContext 这样的影响,应用程序可以使用静态方法RequestContextUtils来查找 WebApplicationContext是否需要访问它。
对于许多具有单个应用程序的应用程序WebApplicationContext来说简单而充分 也可以有一个上下文层次结构,其中一个根WebApplicationContext 跨多个DispatcherServlet(或其他Servlet)实例共享,每个实例都有其自己的子WebApplicationContext配置。有关 上下文层次结构功能的更多信息,请参阅ApplicationContext的其他功能。
根WebApplicationContext通常包含需要跨多个Servlet实例共享的基础架构bean,例如数据存储库和业务服务。这些bean被有效地继承,并且可以在特定于Servlet的子项WebApplicationContext中被覆盖(即重新声明),该子项通常包含给定的本地bean Servlet:

以下是具有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/*" };
}
}
|
如果不需要应用程序上下文层次结构,则应用程序可以通过 |
和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>
|
如果不需要应用程序上下文层次结构,应用程序可能只配置一个“根”上下文,并将 |
1.2.2。特殊豆类型
在DispatcherServlet特殊的豆代表来处理请求和渲染相应的反应。“特殊bean”是指实现WebFlux框架协议的Spring管理的对象实例。那些通常带有内置合同,但您可以自定义其属性,扩展或替换它们。
下表列出了由以下内容检测到的特殊bean DispatcherHandler:
| 豆类 | 说明 |
|---|---|
|
将请求映射到处理程序以及用于预处理和后处理的拦截器列表 。该映射基于一些标准,其细节因 这两个主要的 |
|
|
的HandlerAdapter |
帮助 |
|
解决异常的策略可能将它们映射到处理程序或HTML错误视图或其他。请参阅例外。 |
|
|
解决 |
|
|
解析您的Web应用程序可以使用的主题,例如提供个性化布局。请参阅主题。 |
|
|
在多部分解析库的帮助下解析多部分请求(例如浏览器表单文件上载)的抽象。请参阅多部分解析器。 |
|
|
存储和检索 |
1.2.3。Web MVC配置
应用程序可以声明 处理请求所需的特殊Bean类型中列出的基础架构Bean。该DispatcherServlet检查 WebApplicationContext对每个特殊的豆。如果没有匹配的bean类型,它将回退到DispatcherServlet.properties中列出的默认类型 。
在大多数情况下,MVC配置是最好的起点。它用Java或XML声明所需的bean,并提供更高级别的配置回调API来定制它。
|
Spring Boot依靠MVC Java配置来配置Spring MVC,并且还提供了许多额外的方便选项。 |
1.2.4。Servlet配置
在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容器。的抽象基类实现WebApplicationInitializer名为 AbstractDispatcherServletInitializer使得它更容易注册 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。
该isAsyncSupported保护的方法AbstractDispatcherServletInitializer 提供了一个单一的地方,以使在异步支持DispatcherServlet并映射到它的所有过滤器。默认情况下,该标志设置为true。
最后,如果您需要进一步自定义DispatcherServlet自己,则可以覆盖该createDispatcherServlet方法。
1.2.5。处理
该DispatcherServlet工艺要求如下:
-
在
WebApplicationContext被搜索并在请求作为在过程控制器和其它元件可以利用一个属性的约束。它在密钥下被默认绑定DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE。 -
语言环境解析程序绑定到请求以启用进程中的元素来解析处理请求(呈现视图,准备数据等)时要使用的语言环境。如果你不需要区域解析,你不需要它。
-
主题解析器必须使请求等元素决定使用哪个主题。如果你不使用主题,你可以忽略它。
-
如果您指定了多部分文件解析器,则将检查请求中的多部分; 如果找到多部分,则将请求包装在a中以
MultipartHttpServletRequest供进程中的其他元素进一步处理。有关多部分处理的更多信息,请参阅多部分解析器。 -
搜索适当的处理程序。如果找到处理程序,则会执行与处理程序(预处理程序,后处理程序和控制器)关联的执行链以准备模型或渲染。或者对于注释的控制器,响应可以被渲染(在
HandlerAdapter)内而不是返回视图。 -
如果返回模型,则呈现视图。如果没有返回模型(可能是由于预处理程序或后处理程序拦截了请求,可能出于安全原因),则不会呈现视图,因为请求可能已经被满足。
在HandlerExceptionResolver声明中声明的bean WebApplicationContext用于解决请求处理期间抛出的异常。这些异常解析器允许定制逻辑来解决异常。有关更多详情,请参阅例外。
Spring DispatcherServlet也支持Servlet API指定的最后修改日期的返回 。确定特定请求的最后修改日期的过程很简单:DispatcherServlet查找适当的处理程序映射并测试找到的处理程序是否实现LastModified接口。如果是这样,接口long getLastModified(request)方法的值将 LastModified返回给客户端。
您可以DispatcherServlet通过将Servlet初始化参数(init-param元素)添加到web.xml文件中的Servlet声明来自 定义各个实例。请参阅下表以获取支持的参数列表。
| 参数 | 说明 |
|---|---|
|
|
实现的类 |
|
|
传递给上下文实例(由其指定 |
|
|
命名空间的 |
|
|
是否抛出一个 默认情况下,它被设置为“false”,在这种情况下, 请注意,如果还配置了默认的servlet处理,则未解析的请求将始终转发到默认的servlet,并且永远不会引发404。 |
1.2.6。截击
所有HandlerMapping实现都支持处理程序拦截器,当您想要将特定功能应用于某些请求时(例如,检查委托人),该拦截器非常有用。拦截器必须HandlerInterceptor从 org.springframework.web.servlet包中实现三种方法,这些方法应该提供足够的灵活性来进行各种预处理和后处理:
-
preHandle(..)- 在执行实际处理程序之前 -
postHandle(..)- 后在处理器执行 -
afterCompletion(..)- 完成请求完成后
该preHandle(..)方法返回一个布尔值。您可以使用此方法来中断或继续处理执行链。当此方法返回时true,处理程序执行链将继续; 当它返回false时,DispatcherServlet 假设拦截器本身负责处理请求(并且例如呈现适当的视图),并且不继续执行执行链中的其他拦截器和实际处理器。
见拦截在MVC配置一节的如何配置拦截器的实例。你也可以通过setter在各个HandlerMapping实现上直接注册它们 。
请注意,postHandle对于在之前和之前 写入和提交响应的方法@ResponseBody和ResponseEntity方法来说,它们的用处不大。这意味着对响应进行任何更改都为时已晚,例如添加额外的标题。对于这样的场景,您可以实现并将其声明为Controller Advice bean或直接对其进行配置 。HandlerAdapterpostHandleResponseBodyAdviceRequestMappingHandlerAdapter
1.2.7。例外
如果在请求映射期间发生异常,或者从请求处理程序(如an)中抛出异常@Controller,则DispatcherServlet委派一系列HandlerExceptionResolver bean来解析异常并提供替代处理,这通常是错误响应。
下表列出了可用的HandlerExceptionResolver实现:
| HandlerExceptionResolver | 描述 |
|---|---|
|
|
异常类名称和错误视图名称之间的映射。用于在浏览器应用程序中呈现错误页面。 |
|
解决Spring MVC引发的异常并将它们映射到HTTP状态代码。另请参阅备用 |
|
|
|
根据 |
|
|
通过调用 |
解析器链
您可以通过HandlerExceptionResolver 在Spring配置中声明多个bean并order根据需要设置它们的属性来形成异常解析器链。order属性越高,异常解析器定位的越晚。
HandlerExceptionResolver指定它可以返回的合同:
-
ModelAndView指向错误视图。 -
ModelAndView如果在解析器中处理异常,则为空。 -
null如果异常仍未解决,供后续解析器尝试使用; 如果异常仍然在最后,它可以冒泡到Servlet容器。
在MVC配置自动宣告违约Spring MVC的例外内置解析器,用于@ResponseStatus注释例外,并支持的 @ExceptionHandler方法。您可以自定义该列表或将其替换。
容器错误页面
如果任何异常仍未解决,HandlerExceptionResolver因此尚未传播,或者响应状态设置为错误状态(即4xx,5xx),则Servlet容器可能会在HTML中呈现默认错误页面。要自定义容器的默认错误页面,可以在以下位置声明错误页面映射web.xml:
<error-page>
<location>/error</location>
</error-page>
鉴于上述情况,当异常冒泡时,或者响应具有错误状态时,Servlet容器会在容器内将ERROR分派到配置的URL(例如“/ error”)。然后这个被处理DispatcherServlet,可能映射到一个@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中创建错误页面映射的方法。但是,您可以同时使用 |
1.2.8。查看分辨率
Spring MVC的定义ViewResolver和View,使您能够在浏览器显示模型而不必被束缚在特定的视图技术接口。ViewResolver 提供视图名称和实际视图之间的映射。View在交付给特定视图技术之前处理数据的准备。
下表提供了有关ViewResolver层次结构的更多详细信息:
| 视图解析器 | 描述 |
|---|---|
|
|
|
|
|
它的实现 |
|
|
它的实现 |
|
|
|
|
|
它的方便子类 |
|
|
该方便的子类 |
|
|
|
处理
您可以通过声明多个解析器bean来链接视图解析器,并在必要时通过设置order属性来指定排序。请记住,订单属性越高,视图解析器在链中的位置越晚。
a的合约ViewResolver指定它可以返回null来指示无法找到视图。但是,在JSP的情况下,InternalResourceViewResolver找出JSP是否存在的唯一方法是执行分派 RequestDispatcher。因此,InternalResourceViewResolver必须始终将其配置为按视图解析器的整体顺序排列。
重定向
redirect:视图名称中的特殊前缀允许您执行重定向。的 UrlBasedViewResolver(和子类别)识别为是需要重定向的指令。视图名称的其余部分是重定向URL。
净效果与控制器返回a相同RedirectView,但现在控制器本身可以简单地按逻辑视图名称操作。逻辑视图名称(例如redirect:/myapp/some/resource将相对于当前Servlet上下文重定向),而诸如名称之类的名称redirect:http://myhost.com/some/arbitrary/path 将重定向到绝对URL。
请注意,如果控制器方法使用注释@ResponseStatus,则注释值优先于由设置的响应状态RedirectView。
转发
forward:对于最终由UrlBasedViewResolver子类和子类最终解析的视图名称,也可以使用特殊的前缀。这创造了一个InternalResourceView做一个RequestDispatcher.forward()。因此,这个前缀是不是有用的InternalResourceViewResolver和InternalResourceView(对JSP),但它可以如使用另一种观点认为技术有帮助的,但还是要强制资源的着由Servlet / JSP引擎处理。请注意,您也可以链接多个视图解析器。
内容协商
ContentNegotiatingViewResolver 不会自行解析视图,而是委托给其他视图解析器,并选择类似于客户端请求的视图。该表示可以从Accept头部或查询参数确定,例如"/path?format=pdf"。
在ContentNegotiatingViewResolver选择一个合适的View通过比较与所述媒体类型(也被称为媒体请求类型(一个或多个),以处理该请求 Content-Type由支持)的View与每个其相关联ViewResolvers。View具有兼容性的列表中的第一个Content-Type将表示返回给客户端。如果ViewResolver链不能提供兼容视图,那么DefaultViews将查阅通过属性指定的视图列表。后者选项适用于Views可以呈现当前资源的适当表示的单例,而不管逻辑视图的名称如何。的Accept 报头可以包括通配符,例如text/*,在这种情况下View,其内容类型是text/xml为相容的匹配。
1.2.9。语言环境
Spring的架构大部分支持国际化,就像Spring web MVC框架一样。DispatcherServlet使您能够使用客户端的区域设置自动解析消息。这是用LocaleResolver对象完成的。
当请求进入时,DispatcherServlet查找区域设置解析器,并且如果它找到一个它尝试使用它来设置区域设置。使用该RequestContext.getLocale() 方法,您始终可以检索由区域设置解析程序解析的区域设置。
区域设置解析器和拦截器在org.springframework.web.servlet.i18n包中定义, 并以正常方式在应用程序上下文中配置。这里是Spring中包含的一个语言环境解析器的选择。
时区
除了获取客户端的语言环境之外,了解他们的时区通常也很有用。该LocaleContextResolver接口提供了一个扩展LocaleResolver,使解析器可以提供更丰富的LocaleContext,其中可能包括时区信息。
可用时,TimeZone可以使用该RequestContext.getTimeZone()方法获取 用户。时区信息将自动由日期/时间Converter和FormatterSpring's注册的对象使用 ConversionService。
头解析器
此语言环境解析程序检查accept-language客户端发送的请求中的标头(例如Web浏览器)。通常这个头域包含客户端操作系统的区域设置。请注意,此解析器不支持时区信息。
Cookie解析器
此语言环境解析程序检查Cookie客户端上可能存在的查看是否指定了 Localeor TimeZone。如果是这样,它使用指定的细节。使用本地区解析器的属性,您可以指定cookie的名称以及最大年龄。在下面找到一个定义a的例子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>
| 属性 | 默认 | 描述 |
|---|---|---|
|
cookieName |
classname + LOCALE |
Cookie的名称 |
|
名cookieMaxAge |
Servlet容器默认 |
Cookie保留在客户端上的最长时间。如果指定了-1,则cookie不会被保留; 它只有在客户端关闭浏览器之后才可用。 |
|
cookiePath |
/ |
将Cookie的可见性限制到您网站的某个部分。当指定cookiePath时,cookie将只对该路径及其下面的路径可见。 |
会话解析器
在SessionLocaleResolver可以检索Locale并TimeZone从可能与用户的请求相关的会话。与此相反 CookieLocaleResolver,该策略将本地选择的区域设置存储在Servlet容器中HttpSession。因此,这些设置对于每个会话都只是临时的,因此在每个会话终止时都会丢失。
请注意,与Spring Session项目等外部会话管理机制没有直接关系。这SessionLocaleResolver将简单地HttpSession根据当前评估和修改相应的属性HttpServletRequest。
Locale拦截器
您可以通过添加LocaleChangeInterceptor处理器映射之一来启用区域设置的更改(请参阅[mvc-handlermapping])。它会检测请求中的参数并更改语言环境。它呼吁setLocale()在LocaleResolver上下文中也存在这一点。以下示例显示调用*.view包含名为参数的所有资源siteLanguage现在将更改区域设置。因此,例如,对以下URL的请求http://www.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>
1.2.10。主题
您可以应用Spring Web MVC框架主题来设置应用程序的整体外观,从而增强用户体验。主题是静态资源的集合,通常是样式表和图像,它们会影响应用程序的视觉风格。
定义一个主题
要在您的Web应用程序中使用主题,您必须设置org.springframework.ui.context.ThemeSource接口的实现 。该WebApplicationContext 接口扩展ThemeSource,但其代表职责的专用实现。默认情况下,委托将是一个org.springframework.ui.context.support.ResourceBundleThemeSource从类路径的根目录加载属性文件的 实现。要使用自定义ThemeSource 实现或配置基本名称前缀ResourceBundleThemeSource,可以使用保留名称在应用程序上下文中注册一个bean themeSource。Web应用程序上下文自动检测具有该名称的bean并使用它。
在使用时ResourceBundleThemeSource,主题是在简单的属性文件中定义的。属性文件列出组成主题的资源。这里是一个例子:
=的styleSheet /主题/冷却/ style.css中 背景= /主题/冷却/ 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主题定义放在类路径根目录中,例如in /WEB-INF/classes。在ResourceBundleThemeSource使用标准的Java资源包加载机制,允许主题的国际化。例如,我们可以使用一个/WEB-INF/classes/cool_nl.properties带有荷兰文字的特殊背景图片。
解决主题
在定义主题之后,如上一节所述,您可以决定使用哪个主题。该 DispatcherServlet会查找一个名为的bean themeResolver来找出ThemeResolver要使用的实现。一个主题解析器的工作方式与a相同 LocaleResolver。它检测用于特定请求的主题,还可以更改请求的主题。Spring提供以下主题解析器:
| 类 | 描述 |
|---|---|
|
|
选择使用 |
|
|
主题在用户的HTTP会话中维护。它只需要为每个会话设置一次,但不会在会话之间持续。 |
|
|
所选主题存储在客户端的Cookie中。 |
Spring还提供了一个ThemeChangeInterceptor允许使用简单的请求参数对每个请求进行主题更改的工具。
1.2.11。多部分解析器
MultipartResolver从org.springframework.web.multipart包中解析包括文件上传在内的多部分请求的策略。有一个基于Commons FileUpload的实现,另一个基于Servlet 3.0多部分请求解析。
要启用多部分处理,您需要MultipartResolver在DispatcherServletSpring配置中声明一个名为“multipartResolver” 的bean 。在DispatcherServlet检测到它,并将其应用于传入请求。当接收到与内容类型“多部分/格式数据”的POST,解析器解析的内容和包装了当前HttpServletRequest作为MultipartHttpServletRequest以揭露其作为请求参数提供到解析部件除了访问。
Apache FileUpload
要使用Apache Commons FileUpload,只需CommonsMultipartResolver使用名称配置一个类型为bean的bean即可multipartResolver。当然你也需要把commons-fileupload你的类路径作为依赖。
Servlet 3.0
Servlet 3.0多部分解析需要通过Servlet容器配置启用:
-
在Java中,设置一个
MultipartConfigElementServlet注册。 -
在servlet声明中
web.xml添加一个"<multipart-config>"小节。
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配置就绪后,只需添加一个StandardServletMultipartResolver名称类型的bean即可 multipartResolver。
1.3。过滤器
该spring-web模块提供了一些有用的过滤器。
1.3.1。HTTP PUT表单
浏览器只能通过HTTP GET或HTTP POST提交表单数据,但非浏览器客户端也可以使用HTTP PUT和PATCH。Servlet API需要ServletRequest.getParameter*() 支持仅用于HTTP POST的表单字段访问的方法。
该spring-web模块提供HttpPutFormContentFilter了拦截HTTP PUT和PATCH请求的内容类型application/x-www-form-urlencoded,从请求主体读取表单数据,并封装ServletRequest这些表单数据以便通过该ServletRequest.getParameter*()方法系列获得。
1.3.2。转发头
当请求经过负载平衡器等代理时,主机,端口和方案可能会改变,这对于需要创建资源链接的应用程序提出了挑战,因为链接应反映原始请求的主机,端口和方案客户视角。
RFC 7239定义了代理的“转发”HTTP头,用于提供有关原始请求的信息。还有其他非标准标题正在使用,例如“X-Forwarded-Host”,“X-Forwarded-Port”和“X-Forwarded-Proto”。
ForwardedHeaderFilter检测,提取并使用来自“已转发”报头或来自“X-Forwarded-Host”,“X-Forwarded-Port”和“X-Forwarded-Proto”的信息。它包装请求以覆盖它的主机,端口和方案,并“隐藏”转发的标题以供后续处理。
请注意,使用转发标头时存在安全考虑因素,如RFC 7239第8节中所述。在应用程序级别,很难确定转发标头是否可信。这就是为什么应该正确配置网络上游以从外部过滤掉不受信任的转发标头的原因。
没有代理并且不需要使用转发标头的应用程序可以配置ForwardedHeaderFilter为删除并忽略这些标头。
1.3.3。浅ETag
有一个ShallowEtagHeaderFilter。它被称为浅层,因为它没有任何内容的知识。相反,它依靠缓冲写入响应的实际内容并计算最终的ETag值。
有关更多详情,请参阅ETag过滤器。
1.3.4。CORS
Spring MVC通过控制器上的注释为CORS配置提供细粒度的支持。然而,当与Spring Security一起使用时,建议依靠 CorsFilter必须在Spring Security的过滤器链之前订购的内置组件。
1.4。带注释的控制器
Spring MVC中提供了其中的基于注解的编程模型@Controller和 @RestController组件使用注释来表达要求的映射,请求输入,异常处理等。带注释的控制器具有灵活的方法签名,不必扩展基类,也不需要实现特定的接口。
@Controller
public class HelloController {
@GetMapping("/hello")
public String handle(Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
在这个特定的例子中,该方法接受a Model并返回一个视图名称,String 但存在许多其他选项,并在本章的下面进一步解释。
|
spring.io上的指南和教程使用本节中描述的基于注释的编程模型。 |
1.4.1。宣言
您可以使用Servlet中的标准Spring bean定义来定义控制器bean WebApplicationContext。该@Controller原型允许自动检测,使用Spring普遍支持检测对准@Component在类路径中类和自动注册bean定义他们。它也充当注释类的刻板印象,表明它作为Web组件的角色。
要启用这些@Controllerbean的自动检测,您可以将组件扫描添加到您的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/>,更改为<tx:annotation-driven proxy-target-class="true"/>。
1.4.2。请求映射
该@RequestMapping注释用于将请求映射到控制器方法。它具有通过URL,HTTP方法,请求参数,标题和媒体类型进行匹配的各种属性。它可以在类级使用来表示共享映射,或者在方法级使用,以缩小到特定的端点映射。
还有以下HTTP方法特定的快捷方式变体@RequestMapping:
-
@GetMapping -
@PostMapping -
@PutMapping -
@DeleteMapping -
@PatchMapping
以上是开箱即用提供的自定义注释,因为大多数控制器方法应该映射到特定的HTTP方法,而使用@RequestMapping默认情况下与所有HTTP方法相匹配。同样 @RequestMapping,在课堂上还需要表达共享映射。
以下是类型和方法级别映射的示例:
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
URI模式
您可以使用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"),但是如果名称相同,并且您的代码是使用调试信息或-parametersJava 8上的编译器标志编译的,则可以将该详细信息留出。
语法{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使用 |
模式比较
当多个模式匹配一个URL时,必须比较它们以找到最佳匹配。这通过AntPathMatcher.getPatternComparator(String path)查找更具体的模式来完成。
如果一个模式的URI变量计数较低,并且单个通配符计为1,双通配符计为2,则该模式不太具体。给定相同的分数,则选择较长的模式。给定相同的分数和长度,选择比通配符更多的URI变量的模式。
默认的映射模式/**从评分中排除,并且总是最后排序。同样,前缀模式/public/**被认为比没有双通配符的其他模式更具体。
有关完整详情,请参阅AntPatternComparator中AntPathMatcher,并保持头脑的是,PathMatcher用于实现可定制。请参阅 配置部分中的路径匹配。
后缀匹配
默认情况下,Spring MVC执行".*"后缀模式匹配,以便映射到的控制器/person也隐式映射到/person.*。然后,将文件的扩展名是用于解释使用该响应的请求的内容类型(即,而不是“接受”报头),例如/person.pdf, /person.xml等
当浏览器用于发送难以一致解释的Accept头时,使用像这样的文件扩展名是必要的。目前这不再是必要的,使用“Accept”标题应该是首选。
随着时间的推移,使用文件扩展名已被证明存在各种问题。当使用URI变量,路径参数,URI编码进行覆盖时,它可能会引起歧义,并且还会导致很难推断基于URL的授权和安全性(有关更多详细信息,请参阅下一节)。
要完全禁用文件扩展名,您必须同时设置以下两项:
-
useSuffixPatternMatching(false),请参阅PathMatchConfigurer -
favorPathExtension(false),请参阅ContentNeogiationConfigurer
基于URL的内容协商仍然很有用,例如,在浏览器中输入URL时。为了使我们能够推荐基于查询参数的策略,以避免文件扩展带来的大部分问题。或者,如果您必须使用文件扩展名,请考虑通过ContentNeogiationConfigurer的mediaTypes属性将它们限制为显式注册的扩展名列表 。
后缀匹配和RFD
反射式文件下载(RFD)攻击与XSS类似,因为它依赖于请求输入,例如查询参数,URI变量,反映在响应中。然而,与其将JavaScript插入HTML中,RFD攻击依赖于浏览器切换来执行下载,并在稍后双击时将响应视为可执行脚本。
在Spring MVC中@ResponseBody,ResponseEntity方法面临风险,因为它们可以呈现不同的内容类型,客户端可以通过URL路径扩展来请求这些内容类型 禁用后缀模式匹配和使用路径扩展进行内容协商可降低风险,但不足以防止RFD攻击。
为了防止RFD攻击,在呈现响应体之前,Spring MVC添加一个 Content-Disposition:inline;filename=f.txt标题来建议一个固定且安全的下载文件。只有当URL路径包含一个文件扩展名,既没有被列入白名单,也没有为了内容协商目的而明确注册时,才会这样做。但是,当URL直接输入浏览器时,它可能会有副作用。
默认情况下,许多常用路径扩展都列入白名单。自定义HttpMessageConverter实现的应用程序 可以显式注册用于内容协商的文件扩展名,以避免Content-Disposition为这些扩展名添加头。请参阅内容类型。
有关RFD的其他建议,请参阅CVE-2015-5211。
易耗媒体类型
您可以根据请求缩小请求映射的Content-Type范围:
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
// ...
}
consumes属性还支持否定表达式 - 例如!text/plain表示除“text / plain”以外的任何内容类型。
您可以在类级别声明共享消耗属性。与大多数其他请求映射属性不同,当在类级别使用时,方法级别的使用属性将覆盖而不是扩展类级别声明。
|
|
可生成的媒体类型
您可以根据Accept请求标头和控制器方法生成的内容类型列表缩小请求映射的范围:
@GetMapping(path = "/pets/{petId}", produces = "application/json;charset=UTF-8")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
媒体类型可以指定一个字符集。支持否定表达式 - 例如 !text/plain表示除“text / plain”之外的任何内容类型。
|
对于JSON内容类型,即使RFC7159 明确指出“没有为此注册定义字符集参数”,也应该指定UTF-8字符集 ,因为有些浏览器要求它正确解释UTF-8特殊字符。 |
您可以在课程级别声明共享产品属性。与大多数其他请求映射属性不同,当在类级别使用时,方法级别的产品属性将覆盖而不是扩展类级别声明。
|
|
参数,标题
您可以根据请求参数条件缩小请求映射。您可以测试是否存在请求参数("myParam"),缺少("!myParam")或特定值("myParam=myValue"):
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
您也可以对请求标题条件使用相同的内容:
@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
HTTP头,选项
@GetMapping - 并且@RequestMapping(method=HttpMethod.GET),为了请求映射的目的,透明地支持HTTP HEAD。控制器方法不需要改变。应用了一个响应包装,javax.servlet.http.HttpServlet确保将"Content-Length" 头部设置为写入的字节数,而不实际写入响应。
@GetMapping - 也是@RequestMapping(method=HttpMethod.GET)隐式映射到并支持HTTP HEAD。一个HTTP HEAD请求被处理,就像它是HTTP GET一样,但不是写入主体,而是计算字节数和“Content-Length”标头集。
默认情况下,HTTP OPTIONS是通过将“允许”响应头设置为@RequestMapping具有匹配URL模式的所有方法中列出的HTTP方法列表来处理的。
对于@RequestMapping没有HTTP方法声明的情况,“Allow”头设置为 "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"。-控制器的方法应该总是使用HTTP方法的具体变体声明例如所支持的HTTP方法 @GetMapping,@PostMapping等
@RequestMapping 方法可以显式映射到HTTP HEAD和HTTP OPTIONS,但在常见情况下这不是必需的。
自定义注释
Spring MVC支持使用组合的注释 进行请求映射。这些注释本身是使用元注释来 @RequestMapping组成的,以重新声明@RequestMapping具有更狭窄,更具体目的的属性的子集(或全部)。
@GetMapping,@PostMapping,@PutMapping,@DeleteMapping,和@PatchMapping由注解的例子。它们是开箱即用的,因为大多数控制器方法应该映射到特定的HTTP方法,而使用@RequestMapping 默认情况下与所有HTTP方法相匹配。如果您需要组合注释的示例,请查看如何声明这些注释。
Spring MVC还支持自定义请求映射属性和自定义请求匹配逻辑。这是一个更高级的选项,需要进行子分类RequestMappingHandlerMapping并覆盖getCustomMethodCondition可以检查自定义属性并返回自己的方法RequestCondition。
1.4.3。处理程序方法
@RequestMapping 处理程序方法具有灵活的签名,可以从一系列支持的控制器方法参数和返回值中进行选择。
方法参数
下表显示支持的控制器方法参数。任何参数都不支持反应类型。
JDK 8的java.util.Optional被支撑作为组合的方法的参数与具有注解required的属性-例如@RequestParam,@RequestHeader等,和相当于required=false。
| 控制器方法参数 | 描述 |
|---|---|
|
|
通用访问请求参数,请求和会话属性,不需要直接使用Servlet API。 |
|
|
选择任何特定的请求或响应类型-例如 |
|
|
强制进行会话。因此,这样的论点从来不是 |
|
|
用于编程式HTTP / 2资源推送的Servlet 4.0 push builder API。请注意,根据Servlet规范, |
|
|
目前已通过身份验证 |
|
|
请求的HTTP方法。 |
|
|
当前的请求区域设置由最具体的 |
|
|
与当前请求关联的时区,由a确定 |
|
|
用于访问由Servlet API公开的原始请求主体。 |
|
|
用于访问由Servlet API公开的原始响应主体。 |
|
|
用于访问URI模板变量。请参阅URI模式。 |
|
|
用于访问URI路径段中的名称/值对。请参阅矩阵变量。 |
|
|
用于访问Servlet请求参数。参数值被转换为声明的方法参数类型。请参阅@RequestParam。 请注意,使用 |
|
|
用于访问请求标题。标题值被转换为声明的方法参数类型。请参阅@RequestHeader。 |
|
|
用于访问cookie。Cookies值被转换为声明的方法参数类型。请参阅@CookieValue。 |
|
|
用于访问HTTP请求主体。正文内容使用 |
|
|
用于访问请求标头和正文。身体转换为 |
|
|
用于访问“multipart / form-data”请求中的部分。见多部分。 |
|
|
用于访问HTML控制器中使用的模型,并将其作为视图渲染的一部分展示给模板。 |
|
|
指定要重定向时使用的属性 - 即要追加到查询字符串,和/或临时存储的flash属性,直到重定向后的请求。请参阅重定向属性和Flash属性。 |
|
|
用于访问模型中的现有属性(如果不存在,则实例化),并应用数据绑定和验证。请参阅@ModelAttribute以及Model和DataBinder。 请注意,使用 |
|
|
用于访问命令对象(即 |
|
|
用于标记表单处理完成,触发清理通过类级 |
|
|
为了准备一个相对于当前请求的主机,端口,方案,上下文路径以及servlet映射的文字部分的URL,还要考虑到头部 |
|
|
用于访问任何会话属性; 与作为类级 |
|
|
用于访问请求属性。有关更多详细信息,请参阅@RequestAttribute。 |
|
任何其他的论点 |
如果一个方法参数与上述任何一个不匹配,默认情况下,它被解析为 |
返回值
下表显示支持的控制器方法返回值。所有返回值都支持反应类型,请参阅下面的更多详细信息。
| 控制器方法返回值 | 描述 |
|---|---|
|
|
返回值通过 |
|
|
返回值指定完整的响应,包括HTTP标头和正文通过 |
|
|
为了返回一个响应头和没有正文。 |
|
|
视图名称,用 |
|
|
甲 |
|
|
要通过a隐式确定的视图名称添加到隐式模型的属性 |
|
|
要通过a隐式确定的视图名称添加到模型的属性 请注意,这 |
|
|
要使用的视图和模型属性,以及可选的响应状态。 |
|
|
具有 如果以上都不是,则 |
|
|
从任何线程异步生成上述任何返回值 - 例如可能由于某些事件或回调。请参阅异步请求和 |
|
|
|
|
|
|
|
|
用异步方式异步发出一个对象流写入响应 |
|
|
|
|
反应类型 - Reactor,RxJava或其他通过 |
替代 对于流情况下-例如 |
|
任何其他返回值 |
如果返回值与以上任何一个不匹配,默认情况下它被视为视图名称,如果是 |
类型转换
一些注释控制器方法参数表示基于字符串的请求输入-例如 @RequestParam,@RequestHeader,@PathVariable,@MatrixVariable,和@CookieValue,可能需要类型转换如果参数被声明为比其它的东西String。
对于这种情况,基于配置的转换器自动应用类型转换。默认情况下,简单的类型,如int,long,Date,等的支持。类型转换可以通过一个被定制WebDataBinder,参见DataBinder的,或者通过注册 Formatters与FormattingConversionService,参见 春字段格式。
矩阵变量
矩阵变量可以出现在任何路径段中,每个变量用分号分隔,多个值用逗号分隔,例如"/cars;color=red,green;year=2012"。也可以通过重复的变量名称指定多个值,例如 "color=red;color=green;color=blue"。
如果URL需要包含矩阵变量,则控制器方法的请求映射必须使用URI变量来屏蔽该变量内容,并确保可以成功地匹配请求,而与矩阵变量顺序和呈现无关。下面是一个例子:
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
// q == 11
}
考虑到所有路径段可能包含矩阵变量,有时候您可能需要明确矩阵变量应该包含哪个路径变量。例如:
// GET /owners/42;q=11/pets/21;q=22
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable(name="q", pathVar="ownerId") int q1,
@MatrixVariable(name="q", pathVar="petId") int q2) {
// q1 == 11
// q2 == 22
}
矩阵变量可以被定义为可选的并且指定一个默认值:
// GET /pets/42
@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
// q == 1
}
要获得所有矩阵变量,请使用MultiValueMap:
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable MultiValueMap<String, String> matrixVars,
@MatrixVariable(pathVar="petId"") MultiValueMap<String, String> petMatrixVars) {
// matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
// petMatrixVars: ["q" : 22, "s" : 23]
}
请注意,您需要启用矩阵变量。在MVC的Java的配置,你需要设置一个UrlPathHelper与removeSemicolonContent=false通过 路径匹配。在MVC XML命名空间中,使用 <mvc:annotation-driven enable-matrix-variables="true"/>。
@RequestParam
使用@RequestParam注释将Servlet请求参数(即查询参数或表单数据)绑定到控制器中的方法参数。
以下代码片段显示了用法:
@Controller
@RequestMapping("/pets")
public class EditPetForm {
// ...
@GetMapping
public String setupForm(@RequestParam("petId") int petId, Model model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ...
}
使用这种标注方法参数默认情况下必需的,但你可以指定一个方法参数是通过设置可选@RequestParam的required标志false 或通过声明的参数java.util.Optional包装。
如果目标方法参数类型不是,则自动应用类型转换 String。请参阅类型转换。
当@RequestParam注解被声明为Map<String, String>或 MultiValueMap<String, String>参数时,映射将填充所有请求参数。
请注意,使用@RequestParam是可选的,例如设置其属性。默认情况下,任何由BeanUtils#isSimpleProperty确定的简单值类型的参数都不会被任何其他参数解析器解析,它被视为使用注释@RequestParam。
@RequestHeader
使用@RequestHeader注释将请求标头绑定到控制器中的方法参数。
给定标题请求:
主机localhost:8080 接受text / html,application / xhtml + xml,application / xml; q = 0.9 Accept-Language fr,en-gb; q = 0.7,en; q = 0.3 接受编码gzip,放气 Accept-Charset ISO-8859-1,utf-8; q = 0.7,*; q = 0.7 保持活力300
以下是获取Accept-Encoding和Keep-Alive标题的值:
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
如果目标方法参数类型不是,则自动应用类型转换 String。请参阅类型转换。
当@RequestHeader注解上的使用Map<String, String>, MultiValueMap<String, String>或HttpHeaders参数,则地图被填充有所有标头值。
|
内置支持可用于将逗号分隔的字符串转换为字符串的数组/集合或类型转换系统已知的其他类型。例如带注释的方法参数 |
@CookieValue
使用@CookieValue注释将HTTP cookie的值绑定到控制器中的方法参数。
鉴于以下cookie的请求:
JSESSIONID = 415A4AC178C59DACE0B2C9CA727CDD84
以下代码示例演示了如何获取cookie值:
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
//...
}
如果目标方法参数类型不是,则自动应用类型转换 String。请参阅类型转换。
@ModelAttribute
使用@ModelAttribute方法参数上的注释来访问模型中的属性,或者如果不存在,则将其实例化。模型属性还覆盖了来自HTTP Servlet请求参数的名称与字段名称匹配的值。这被称为数据绑定,它不必处理解析和转换单个查询参数和表单字段。例如:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }
Pet上面的实例解决如下:
-
从HTTP会话通过@SessionAttributes。
-
来自通过一个
Converter(下面的例子)传递的URI路径变量。 -
来自默认构造函数的调用。
-
从调用具有与Servlet请求参数匹配的参数的“主构造函数”; 参数名称通过JavaBeans
@ConstructorProperties或字节码中的运行时保留参数名称确定。
虽然通常使用模型来填充具有属性的模型,但另一种替代方法是依靠Converter<String, T>URI结合URI路径变量约定。在下面的示例中,模型属性名称“account”与URI路径变量“account”匹配,并且Account通过将String帐号传递给已注册的Converter<String, Account>:
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
// ...
}
在获得模型属性实例之后,应用数据绑定。的 WebDataBinder类匹配servlet请求参数名(查询参数和表单字段),以对目标对象的字段名称。必要时应用类型转换后填充匹配字段。有关数据绑定(和验证)的更多信息,请参阅 验证。有关自定义数据绑定的更多信息,请参阅DataBinder。
数据绑定可能会导致错误。默认情况下BindException会引发一个,但要在控制器方法中检查这些错误,请BindingResult立即在旁边添加一个参数@ModelAttribute,如下所示:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
在某些情况下,您可能需要访问不带数据绑定的模型属性。对于这种情况,您可以将其Model注入控制器并直接访问它,或者@ModelAttribute(binding=false)按照如下所示进行设置:
@ModelAttribute
public AccountForm setUpForm() {
return new AccountForm();
}
@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
return accountRepository.findOne(accountId);
}
@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
@ModelAttribute(binding=false) Account account) {
// ...
}
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
请注意,使用@ModelAttribute是可选的,例如设置其属性。默认情况下,BeanUtils#isSimpleProperty确定的任何非简单值类型的 参数都不会被任何其他参数解析器解析,它被视为使用注释@ModelAttribute。
@SessionAttributes
@SessionAttributes用于在请求之间的HTTP Servlet会话中存储模型属性。它是一个声明特定控制器使用的会话属性的类型级注释。这通常会列出模型属性的名称或模型属性的类型,这些属性应该透明地存储在会话中供随后的访问请求使用。
例如:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
在第一个请求中,当名称为“pet”的模型属性添加到模型中时,它会自动提升并保存在HTTP Servlet会话中。直到另一个控制器方法使用SessionStatus方法参数清除存储时,它仍然存在:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors) {
// ...
}
status.setComplete();
// ...
}
}
}
@SessionAttribute
如果您需要访问全局管理的现有会话属性(例如,通过过滤器),并且可能存在也可能不存在,请@SessionAttribute在方法参数上使用注释:
@RequestMapping("/")
public String handle(@SessionAttribute User user) {
// ...
}
对于需要添加或删除会话属性的用例,请考虑注入 org.springframework.web.context.request.WebRequest或javax.servlet.http.HttpSession注入控制器方法。
对于作为控制器工作流程一部分的会话中的模型属性的临时存储,请考虑SessionAttributes按照@SessionAttributes中所述 使用。
@RequestAttribute
到类似@SessionAttribute的@RequestAttribute注释可以被用于访问先前创建预先存在的请求的属性,例如,通过一个Servlet Filter或HandlerInterceptor:
@GetMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}
重定向属性
默认情况下,所有模型属性都被视为在重定向URL中作为URI模板变量公开。剩下的属性是原始类型或集合/基本类型数组,它们被自动附加为查询参数。
如果为重定向专门准备了模型实例,则将基元类型属性附加为查询参数可能是所需的结果。但是,在注释控制器中,模型可能包含为渲染目的而添加的其他属性(例如下拉字段值)。为了避免在URL中出现这样的属性,@RequestMapping方法可以声明一个类型参数RedirectAttributes并使用它来指定可用的确切属性RedirectView。如果方法确实重定向,RedirectAttributes则使用内容。否则使用模型的内容。
在RequestMappingHandlerAdapter提供了一个名为标志 "ignoreDefaultModelOnRedirect",可以用来表示默认的内容 Model,如果一个控制器方法重定向不应该被使用。相反,控制器方法应该声明一个类型的属性,RedirectAttributes或者如果它不这样做,则不应该传递属性RedirectView。MVC命名空间和MVC Java配置都将此标志设置false为保持向后兼容性。但是,对于我们建议的新应用程序true
请注意,当前请求中的URI模板变量在扩展重定向URL时会自动变为可用,并且不需要明确地通过Model也不通过RedirectAttributes。例如:
@PostMapping("/files/{path}")
public String upload(...) {
// ...
return "redirect:files/{path}";
}
将数据传递到重定向目标的另一种方式是通过Flash属性。与其他重定向属性不同,Flash属性保存在HTTP会话中(因此不会出现在URL中)。有关更多信息,请参阅Flash属性。
Flash属性
Flash属性为一个请求存储用于另一个请求的属性提供了一种方法。这是重定向时最常需要的 - 例如 Post / Redirect / Get模式。在重定向(通常在会话中)之前,Flash属性会临时保存,以便在重定向后立即将其删除。
Spring MVC有两个主要的抽象来支持Flash属性。FlashMap用于保存闪存属性,同时FlashMapManager用于存储,检索和管理FlashMap实例。
Flash属性支持始终处于“打开”状态,并且不需要明确启用即使未使用它也不会导致创建HTTP会话。在每个请求中,都有一个“输入”,FlashMap其中包含从前一个请求(如果有)传递的属性和FlashMap具有属性的“输出” ,以保存后续请求。这两个FlashMap实例都可以在Spring MVC中的任何地方通过静态方法访问 RequestContextUtils。
带注释的控制器通常不需要FlashMap直接使用。相反,一个 @RequestMapping方法可以接受一个类型参数,RedirectAttributes并用它为重定向场景添加flash属性。通过添加的Flash属性 RedirectAttributes会自动传播到“输出”FlashMap。同样,在重定向之后,来自“输入”的属性FlashMap会自动添加到 Model提供目标URL的控制器的属性中。
Multipart
After a MultipartResolver has been enabled, the content of POST requests with "multipart/form-data" is parsed and accessible as regular request parameters. In the example below we access one regular form field and one uploaded file:
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
}
return "redirect:uploadFailure";
}
}
|
When using Servlet 3.0 multipart parsing you can also use |
Multipart content can also be used as part of data binding to a command object. For example the above form field and file could have been fields on a form object:
class MyForm {
private String name;
private MultipartFile file;
// ...
}
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(MyForm form, BindingResult errors) {
if (!form.getFile().isEmpty()) {
byte[] bytes = form.getFile().getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
}
return "redirect:uploadFailure";
}
}
多部分请求也可以在非RESTful服务场景中从非浏览器客户端提交。例如,一个文件与JSON一起:
POST / someUrl 内容类型:多部分/混合 --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp 内容处理:表单数据; NAME =“元数据” Content-Type:application / json; 字符集= UTF-8 内容传输编码:8位 { “name”:“value” } --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp 内容处理:表单数据; NAME =“文件的数据”; 文件名= “file.properties” Content-Type:text / xml 内容传输编码:8位 ...文件数据...
你可以通过@RequestParamas 访问“元数据”部分,String但是你可能想要从JSON反序列化(类似于@RequestBody)。使用HttpMessageConverter@RequestPart转换后使用 注释来访问多部分 :
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
@RequestPart("file-data") MultipartFile file) {
// ...
}
@RequestPart可以结合使用javax.validation.Valid,或Spring的 @Validated注释,这会导致应用标准Bean验证。默认情况下,验证错误会导致一个MethodArgumentNotValidException变成400(BAD_REQUEST)响应。或者,验证错误可以通过Errors或BindingResult参数在控制器内本地处理:
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
BindingResult result) {
// ...
}
@RequestBody
使用@RequestBody注释通过HttpMessageConverter将请求体读取并反序列化成Object 。下面是一个@RequestBody参数的例子:
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
@RequestBody可以结合使用javax.validation.Valid,或Spring的 @Validated注释,这会导致应用标准Bean验证。默认情况下,验证错误会导致一个MethodArgumentNotValidException变成400(BAD_REQUEST)响应。或者,验证错误可以通过Errors或BindingResult参数在控制器内本地处理:
@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
// ...
}
HttpEntity
HttpEntity或多或少与使用@RequestBody相同,但基于公开请求标头和正文的容器对象。下面是一个例子:
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
// ...
}
@ResponseBody
@ResponseBody通过HttpMessageConverter使用方法上的注释将返回序列化为响应主体 。例如:
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}
@ResponseBody在类级别上也受支持,在这种情况下,它由所有控制器方法继承。这@RestController仅仅是用@Controllerand 标记的元注释的效果@ResponseBody。
You can use the Message Converters option of the MVC Config to configure or customize message conversion.
@ResponseBody methods can be combined with JSON serialization views. See Jackson JSON for details.
ResponseEntity
ResponseEntity is more or less identical to using @ResponseBody but based on a container object that specifies request headers and body. Below is an example:
@PostMapping("/something")
public ResponseEntity<String> handle() {
// ...
URI location = ... ;
return ResponseEntity.created(location).build();
}
Jackson JSON
Jackson serialization views
Spring MVC provides built-in support for Jackson’s Serialization Views which allows rendering only a subset of all fields in an Object. To use it with @ResponseBody or ResponseEntity controller methods, use Jackson’s @JsonView annotation to activate a serialization view class:
@RestController
public class UserController {
@GetMapping("/user")
@JsonView(User.WithoutPasswordView.class)
public User getUser() {
return new User("eric", "7!jd#h23");
}
}
public class User {
public interface WithoutPasswordView {};
public interface WithPasswordView extends WithoutPasswordView {};
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
@JsonView(WithoutPasswordView.class)
public String getUsername() {
return this.username;
}
@JsonView(WithPasswordView.class)
public String getPassword() {
return this.password;
}
}
|
|
对于依赖视图分辨率的控制器,只需将序列化视图类添加到模型中即可:
@Controller
public class UserController extends AbstractController {
@GetMapping("/user")
public String getUser(Model model) {
model.addAttribute("user", new User("eric", "7!jd#h23"));
model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
return "userView";
}
}
杰克逊JSONP
为了启用JSONP支持@ResponseBody 和ResponseEntity方法,声明一个如下所示@ControllerAdvice扩展的bean,AbstractJsonpResponseBodyAdvice其中constructor参数指示JSONP查询参数名称:
@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpAdvice() {
super("callback");
}
}
对于依赖视图解析的控制器,当请求具有名为jsonpor 的查询参数时,JSONP将自动启用callback。这些名称可以通过jsonpParameterNames属性进行定制。
1.4.4。模型
该@ModelAttribute注释可用于:
-
在用于创建或访问模型中的对象的方法参数中
@RequestMapping,并通过一个方法将其绑定到请求WebDataBinder。 -
作为帮助在任何方法调用之前初始化模型的方法级注释
@Controller或@ControllerAdvice类@RequestMapping。 -
在
@RequestMapping标记其返回值的方法上是一个模型属性。
本节讨论@ModelAttribute方法,或者上面的列表中的第二个。控制器可以有多种@ModelAttribute方法。所有这些方法都是@RequestMapping在同一控制器中的方法之前调用的。一个@ModelAttribute 方法也可以通过控制器共享@ControllerAdvice。有关更多详细信息,请参阅控制器建议部分 。
@ModelAttribute方法具有灵活的方法签名。它们支持许多与@RequestMapping方法相同的参数,除了@ModelAttribute它本身或与请求体相关的任何内容。
一个示例@ModelAttribute方法:
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountRepository.findAccount(number));
// add more ...
}
仅添加一个属性:
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountRepository.findAccount(number);
}
|
如果没有明确指定名称,则根据对象类型选择默认名称,如约定 Javadoc中所述 。您始终可以通过使用重载 |
@ModelAttribute can also be used as a method-level annotation on @RequestMapping methods in which case the return value of the @RequestMapping method is interpreted as a model attribute. This is typically not required, as it is the default behavior in HTML controllers, unless the return value is a String which would otherwise be interpreted as a view name (also see [mvc-coc-r2vnt]). @ModelAttribute can also help to customize the model attribute name:
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
// ...
return account;
}
1.4.5. DataBinder
@Controller or @ControllerAdvice classes can have @InitBinder methods in order to initialize instances of WebDataBinder, and those in turn are used to:
-
Bind request parameters (i.e. form data or query) to a model object.
-
Convert String-based request values such as request parameters, path variables, headers, cookies, and others, to the target type of controller method arguments.
-
在呈现HTML表单时,将模型对象值设置为字符串值。
@InitBinder方法可以注册控制器特定的java.bean.PropertyEditor,或Spring Converter和Formatter组件。另外, MVC配置可用于注册Converter和Formatter 键入全局共享FormattingConversionService。
@InitBinder方法支持许多与一个@RequestMapping方法相同的参数,除了@ModelAttribute(命令对象)参数。通常他们是用WebDataBinder参数声明的,用于注册和void返回值。下面是一个例子:
@Controller
public class FormController {
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
或者,当Formatter通过共享使用基于设置的设置时 FormattingConversionService,您可以重新使用相同的方法并注册控制器特定Formatter的设置:
@Controller
public class FormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
1.4.6。例外
@Controller和@ControllerAdvice类可以有 @ExceptionHandler方法来处理来自控制器方法的异常。例如:
@Controller
public class SimpleController {
// ...
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
异常可能与传播的顶级异常(即直接IOException抛出)匹配,或者与顶层包装异常(例如,IOException内部包装)中的直接原因相匹配 IllegalStateException。
对于匹配异常类型,最好将目标异常声明为方法参数,如上所示。当多个异常方法匹配时,根异常匹配通常优先于引发异常匹配。更具体地说,ExceptionDepthComparator它用于根据抛出的异常类型的深度对异常进行排序。
或者,注释声明可以缩小匹配的异常类型:
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
// ...
}
Or even a list of specific exception types with a very generic argument signature:
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
// ...
}
|
The distinction between root and cause exception matching can be surprising: In the 行为在 |
我们通常建议在参数签名中尽可能具体,以减少根和导致异常类型之间不匹配的可能性。考虑将多重匹配方法分解为单个@ExceptionHandler 方法,每个方法通过其签名匹配单个特定的异常类型。
在多重@ControllerAdvice安排中,请@ControllerAdvice根据相应订单的优先顺序声明您的主根异常映射。虽然根异常匹配优先于原因,但它是在给定的控制器或@ControllerAdvice类的方法中定义的。这意味着优先级较高的@ControllerAdvicebean 上的原因匹配优先 于较低优先级的@ControllerAdvicebean 上的任何匹配(例如root) 。
最后但并非最不重要的一点,@ExceptionHandler方法实现可能会选择退出处理给定的异常实例,方法是将其重新推回原始形式。在仅对根级别匹配感兴趣的场景或者在特定上下文中不能静态确定的匹配情况下,这非常有用。重新抛出的异常将通过剩余的解析链传播,就像如果给定的@ExceptionHandler方法不会首先匹配一样。
对@ExceptionHandlerSpring MVC中的方法的支持建立在HandlerExceptionResolver机制的DispatcherServlet 级别上。
方法参数
@ExceptionHandler 方法支持以下参数:
| 方法论证 | 描述 |
|---|---|
|
异常类型 |
用于访问引发的异常。 |
|
|
用于访问引发异常的控制器方法。 |
|
|
通用访问请求参数,请求和会话属性,不需要直接使用Servlet API。 |
|
|
选择任何特定的请求或响应类型-例如 |
|
|
强制进行会话。因此,这样的论点从来不是 |
|
|
目前已通过身份验证 |
|
|
请求的HTTP方法。 |
|
|
当前的请求区域设置由最具体的 |
|
|
与当前请求关联的时区,由a确定 |
|
|
用于访问由Servlet API公开的原始响应主体。 |
|
|
对于访问模型的错误响应,请始终为空。 |
|
|
指定要重定向时使用的属性 - 即要追加到查询字符串,和/或临时存储的flash属性,直到重定向后的请求。请参阅重定向属性和Flash属性。 |
|
|
用于访问任何会话属性; 与作为类级 |
|
|
用于访问请求属性。有关更多详细信息,请参阅@RequestAttribute。 |
返回值
@ExceptionHandler 方法支持以下返回值:
| 返回值 | 描述 |
|---|---|
|
|
返回值通过 |
|
|
返回值指定完整的响应,包括HTTP标头和正文通过 |
|
|
视图名称,用 |
|
|
甲 |
|
|
要通过a隐式确定的视图名称添加到隐式模型的属性 |
|
|
要通过a隐式确定的视图名称添加到模型的属性 请注意,这 |
|
|
要使用的视图和模型属性,以及可选的响应状态。 |
|
|
具有 如果以上都不是,则 |
|
任何其他返回值 |
如果返回值与上述任何一个不匹配,默认情况下它将被视为要添加到模型的模型属性,除非它是一个简单类型,如BeanUtils#isSimpleProperty所确定的, 在这种情况下,它仍然是未解决的。 |
REST API异常
A common requirement for REST services is to include error details in the body of the response. The Spring Framework does not automatically do this because the representation of error details in the response body is application specific. However a@RestController may use @ExceptionHandler methods with a ResponseEntity return value to set the status and the body of the response. Such methods may also be declared in @ControllerAdvice classes to apply them globally.
Applications that implement global exception handling with error details in the response body should consider extendingResponseEntityExceptionHandler which provides handling for exceptions that Spring MVC raises along with hooks to customize the response body. To make use of this, create a subclass of ResponseEntityExceptionHandler, annotate with @ControllerAdvice, override the necessary methods, and declare it as a Spring bean.
1.4.7. Controller Advice
Typically @ExceptionHandler, @InitBinder, and @ModelAttribute methods apply within the @Controller class (or class hierarchy) they are declared in. If you want such methods to apply more globally, across controllers, you can declare them in a class marked with @ControllerAdvice or @RestControllerAdvice.
@ControllerAdvice is marked with @Component which means such classes can be registered as Spring beans via component scanning. @RestControllerAdvice is also a meta-annotation marked with both @ControllerAdvice and @ResponseBody which essentially means @ExceptionHandler methods are rendered to the response body via message conversion (vs view resolution/template rendering).
On startup, the infrastructure classes for @RequestMapping and @ExceptionHandler methods detect Spring beans of type @ControllerAdvice, and then apply their methods at runtime. Global @ExceptionHandler methods (from an @ControllerAdvice) are applied after local ones (from the @Controller). By contrast global @ModelAttribute and @InitBinder methods are applied before local ones.
默认@ControllerAdvice方法适用于每个请求,即所有控制器,但您可以通过注释上的属性将其缩小到控制器的子集:
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
请记住,上述选择器在运行时进行评估,如果广泛使用,可能会对性能产生负面影响。有关 更多详细信息,请参阅 @ControllerAdviceJavadoc。
1.5。URI链接
本节介绍Spring框架中可用于准备URI的各种选项。
1.5.1。UriComponents
Spring MVC和Spring WebFlux
UriComponents可比java.net.URI。但它带有一个专用的 UriComponentsBuilder并支持URI模板变量:
String uriTemplate = "http://example.com/hotels/{hotel}";
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate)
.queryParam("q", "{q}")
.build();
URI uri = uriComponents.expand("Westin", "123").encode().toUri();
| 带有URI模板的静态工厂方法。 | |
| 添加或替换URI组件。 | |
建立UriComponents。 |
|
展开URI变量,编码并获取URI。 |
以上可以作为单链和捷径完成:
String uriTemplate = "http://example.com/hotels/{hotel}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate)
.queryParam("q", "{q}")
.buildAndExpand("Westin", "123")
.encode()
.toUri();
1.5.2。UriBuilder
Spring MVC和Spring WebFlux
UriComponentsBuilder是一个实现UriBuilder。在一起 UriBuilderFactory,并UriBuilder提供了从URI模板建立一个URI一个可插拔的机制,以及分享共同的属性,如基本URI,编码策略,和其他人的方式。
无论是RestTemplate和WebClient可以用配置UriBuilderFactory ,以自定义的URI如何从URI模板创建的。默认实现依赖于UriComponentsBuilder内部,并提供了配置通用基本URI的选项,替代编码模式策略等。
配置的示例RestTemplate:
String baseUrl = "http://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
配置的示例WebClient:
String baseUrl = "http://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
// Configure the UriBuilderFactory..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
// Or use shortcut on builder..
WebClient client = WebClient.builder().baseUrl(baseUrl).build();
// Or use create shortcut...
WebClient client = WebClient.create(baseUrl);
您也可以DefaultUriBuilderFactory直接使用,就像您一样UriComponentsBuilder。主要区别在于它DefaultUriBuilderFactory是有状态的,可以重新用于准备许多URL,共享通用配置,例如基本URL, UriComponentsBuilder而无状态和每个URI。
使用以下示例DefaultUriBuilderFactory:
String baseUrl = "http://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123"); // encoding strategy applied..
1.5.3。URI编码
Spring MVC和Spring WebFlux
编码URI的默认方式UriComponents如下所示:
-
URI变量被扩展。
-
每个URI组件(路径,查询等)都是单独编码的。
编码规则如下:在URI组件中,按照RFC 3986中的定义,对所有非法字符(包括非US-ASCII字符)以及URI组件中非法的所有其他字符应用百分比编码。
|
编码in |
上述默认编码策略不会对所有具有保留含义的字符进行编码,而只会对给定URI组件中的非法字符进行编码。如果这不符合您的期望,您可以使用下面介绍的替代策略。
当使用DefaultUriBuilderFactory -插入WebClient, RestTemplate或者直接使用,则可以切换到一个替代编码策略,如下所示:
String baseUrl = "http://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.VALUES_ONLY);
// ...
这种替代编码策略UriUtils.encode(String, Charset)在扩展之前应用于每个URI变量值,有效地编码所有非US-ASCII字符以及URI中具有保留含义的所有字符,这确保扩展的URI变量不会对结构产生任何影响或URI的含义。
1.5.4。Servlet请求相对
您可以使用ServletUriComponentsBuilder创建相对于当前请求的URI:
HttpServletRequest request = ...
// Re-uses host, scheme, port, path and query string...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}").build()
.expand("123")
.encode();
您可以创建相对于上下文路径的URI:
// Re-uses host, port and context path...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
.path("/accounts").build()
您可以创建相对于Servlet的URI(例如/main/*):
// Re-uses host, port, context path, and Servlet prefix...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts").build()
|
|
1.5.5。链接到控制器
Spring MVC提供了一种机制来准备控制器方法的链接。例如,以下MVC控制器很容易创建链接:
@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {
@GetMapping("/bookings/{booking}")
public ModelAndView getBooking(@PathVariable Long booking) {
// ...
}
}
您可以通过名称引用方法来准备链接:
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
在上面的例子中,我们提供了实际的方法参数值,在这种情况下是长整型值21,用作路径变量并插入到URL中。此外,我们提供了值42以填充任何剩余的URI变量,例如从类型级别请求映射继承的“酒店”变量。如果该方法有更多参数,则可以为URL不需要的参数提供空值。通常只有@PathVariable和@RequestParam参数与构造URL相关。
还有其他方法可供使用MvcUriComponentsBuilder。例如,您可以使用类似于通过代理进行模拟测试的技术,以避免通过名称引用控制器方法(该示例假定为静态导入MvcUriComponentsBuilder.on):
UriComponents uriComponents = MvcUriComponentsBuilder
.fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
|
当控制器方法签名被认为可用于创建链接时,其设计受到限制 |
上面的例子使用了静态方法MvcUriComponentsBuilder。在内部,他们依靠ServletUriComponentsBuilder从当前请求的方案,主机,端口,上下文路径和servlet路径准备基本URL。这在大多数情况下效果很好,但有时可能不够。例如,您可能在请求的上下文之外(例如,准备链接的批处理过程),或者您需要插入路径前缀(例如,从请求路径中删除并需要重新插入链接的区域设置前缀)。
对于这种情况,您可以使用静态“fromXxx”重载方法来接受 UriComponentsBuilder使用基本URL。或者您可以MvcUriComponentsBuilder 使用基本URL 创建实例,然后使用基于实例的“withXxx”方法。例如:
UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
|
|
1.5.6. Links in views
You can also build links to annotated controllers from views such as JSP, Thymeleaf, FreeMarker. This can be done using the fromMappingName method in MvcUriComponentsBuilder which refers to mappings by name.
Every @RequestMapping is assigned a default name based on the capital letters of the class and the full method name. For example, the method getFoo in class FooController is assigned the name "FC#getFoo". This strategy can be replaced or customized by creating an instance of HandlerMethodMappingNamingStrategy and plugging it into yourRequestMappingHandlerMapping. The default strategy implementation also looks at the name attribute on @RequestMapping and uses that if present. That means if the default mapping name assigned conflicts with another (e.g. overloaded methods) you can assign a name explicitly on the @RequestMapping.
|
The assigned request mapping names are logged at TRACE level on startup. |
The Spring JSP tag library provides a function called mvcUrl that can be used to prepare links to controller methods based on this mechanism.
For example given:
@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {
@RequestMapping("/{country}")
public HttpEntity getAddress(@PathVariable String country) { ... }
}
You can prepare a link from a JSP as follows:
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
<a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>
The above example relies on the mvcUrl JSP function declared in the Spring tag library (i.e. META-INF/spring.tld). For more advanced cases (e.g. a custom base URL as explained in the previous section), it is easy to define your own function, or use a custom tag file, in order to use a specific instance of MvcUriComponentsBuilder with a custom base URL.
1.6. Async Requests
Spring MVC has an extensive integration with Servlet 3.0 asynchronous request processing:
-
DeferredResultandCallablereturn values in controller method provide basic support for a single asynchronous return value. -
Controllers can stream multiple values including SSE and raw data.
-
Controllers can use reactive clients and return reactive types for response handling.
1.6.1. DeferredResult
Once the asynchronous request processing feature is enabled in the Servlet container, controller methods can wrap any supported controller method return value with DeferredResult:
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
// Save the deferredResult somewhere..
return deferredResult;
}
// From some other thread...
deferredResult.setResult(data);
The controller can produce the return value asynchronously, from a different thread, for example in response to an external event (JMS message), a scheduled task, or other.
1.6.2. Callable
A controller may also wrap any supported return value with java.util.concurrent.Callable:
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
};
}
The return value will then be obtained by executing the the given task through the configured TaskExecutor.
1.6.3. Processing
Here is a very concise overview of Servlet asynchronous request processing:
-
A
ServletRequestcan be put in asynchronous mode by callingrequest.startAsync(). The main effect of doing so is that the Servlet, as well as any Filters, can exit but the response will remain open to allow processing to complete later. -
The call to
request.startAsync()returnsAsyncContextwhich can be used for further control over async processing. For example it provides the methoddispatch, that is similar to a forward from the Servlet API except it allows an application to resume request processing on a Servlet container thread. -
The
ServletRequestprovides access to the currentDispatcherTypethat can be used to distinguish between processing the initial request, an async dispatch, a forward, and other dispatcher types.
DeferredResult processing:
-
Controller returns a
DeferredResultand saves it in some in-memory queue or list where it can be accessed. -
Spring MVC calls
request.startAsync(). -
Meanwhile the
DispatcherServletand all configured Filter’s exit the request processing thread but the response remains open. -
The application sets the
DeferredResultfrom some thread and Spring MVC dispatches the request back to the Servlet container. -
The
DispatcherServletis invoked again and processing resumes with the asynchronously produced return value.
Callable processing:
-
Controller returns a
Callable. -
Spring MVC calls
request.startAsync()and submits theCallableto aTaskExecutorfor processing in a separate thread. -
Meanwhile the
DispatcherServletand all Filter’s exit the Servlet container thread but the response remains open. -
Eventually the
Callableproduces a result and Spring MVC dispatches the request back to the Servlet container to complete processing. -
The
DispatcherServletis invoked again and processing resumes with the asynchronously produced return value from theCallable.
For further background and context you can also read the blog posts that introduced asynchronous request processing support in Spring MVC 3.2.
Exception handling
When using a DeferredResult you can choose whether to call setResult or setErrorResult with an exception. In both cases Spring MVC dispatches the request back to the Servlet container to complete processing. It is then treated either as if the controller method returned the given value, or as if it produced the given exception. The exception then goes through the regular exception handling mechanism, e.g. invoking @ExceptionHandler methods.
When using Callable, similar processing logic follows. The main difference being that the result is returned from the Callableor an exception is raised by it.
Interception
HandlerInterceptor's can also be AsyncHandlerInterceptor in order to receive the afterConcurrentHandlingStarted callback on the initial request that starts asynchronous processing instead of postHandle and afterCompletion.
HandlerInterceptor's can also register a CallableProcessingInterceptor or a DeferredResultProcessingInterceptor in order to integrate more deeply with the lifecycle of an asynchronous request for example to handle a timeout event. SeeAsyncHandlerInterceptor for more details.
DeferredResult provides onTimeout(Runnable) and onCompletion(Runnable) callbacks. See the Javadoc of DeferredResult for more details. Callable can be substituted for WebAsyncTask that exposes additional methods for timeout and completion callbacks.
Compared to WebFlux
The Servlet API was originally built for making a single pass through the Filter-Servlet chain. Asynchronous request processing, added in Servlet 3.0, allows applications to exit the Filter-Servlet chain but leave the response open for further processing. The Spring MVC async support is built around that mechanism. When a controller returns a DeferredResult, the Filter-Servlet chain is exited and the Servlet container thread is released. Later when the DeferredResult is set, an ASYNC dispatch (to the same URL) is made during which the controller is mapped again but rather than invoking it, the DeferredResult value is used (as if the controller returned it) to resume processing.
By contrast Spring WebFlux is neither built on the Servlet API, nor does it need such an asynchronous request processing feature because it is asynchronous by design. Asynchronous handling is built into all framework contracts and is intrinsically supported through :: stages of request processing.
From a programming model perspective, both Spring MVC and Spring WebFlux support asynchronous and Reactive types as return values in controller methods. Spring MVC even supports streaming, including reactive back pressure. However individual writes to the response remain blocking (and performed on a separate thread) unlike WebFlux that relies on non-blocking I/O and does not need an extra thread for each write.
另一个根本区别在于Spring MVC中不支持在控制器方法的参数,如异步或反应类型@RequestBody,@RequestPart和其他人,也不是模型属性它有异步和无任何类型的明确支持。Spring WebFlux确实支持这一切。
1.6.4。HTTP流媒体
DeferredResult和Callable可用于单个异步返回值。如果您想生成多个异步值并将其写入响应,该怎么办?
对象
所述ResponseBodyEmitter返回值可以被用于产生对象,其中每个发送的对象序列与流 HttpMessageConverter和写入响应。例如:
@GetMapping("/events")
public ResponseBodyEmitter handle() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
// Save the emitter somewhere..
return emitter;
}
// In some other thread
emitter.send("Hello once");
// and again later on
emitter.send("Hello again");
// and done at some point
emitter.complete();
ResponseBodyEmitter也可以用作身体,ResponseEntity允许您自定义响应的状态和标题。
当emitter抛出一个IOException(例如,如果远程客户端离开)应用程序不负责清理连接,并且不应该调用emitter.complete 或emitter.completeWithError。相反,servlet容器会自动启动一个 AsyncListener错误通知,其中Spring MVC进行completeWithError调用,然后执行一次最终的ASYNC调度,以便Spring MVC调用配置的异常解析器并完成请求。
SSE
SseEmitter是ResponseBodyEmitter为服务器发送的事件提供支持 的子类,其中根据W3C SSE规范对从服务器发送的事件进行格式化。为了从控制器产生SSE流,只需返回SseEmitter:
@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
SseEmitter emitter = new SseEmitter();
// Save the emitter somewhere..
return emitter;
}
// In some other thread
emitter.send("Hello once");
// and again later on
emitter.send("Hello again");
// and done at some point
emitter.complete();
虽然SSE是流入浏览器的主要选项,但请注意,Internet Explorer不支持服务器发送的事件。考虑使用Spring的 WebSocket消息传递和SockJS后备传输(包括SSE),这些传输的目标是广泛的浏览器。
有关异常处理的注意事项,请参阅上一节。
原始数据
有时候绕过消息转换和直接流式传输给响应OutputStream例如文件下载是有用的 。使用StreamingResponseBody 返回值类型来做到这一点:
@GetMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// write...
}
};
}
StreamingResponseBody可以用作身体,ResponseEntity允许您自定义响应的状态和标题。
1.6.5。活性类型
Spring MVC支持在控制器中使用被动客户端库。这包括 WebClient从spring-webflux和其他如春天数据反应性数据存储库。在这种情况下,能够从控制器方法返回反应类型是方便的。
响应返回值的处理方式如下:
-
单一价值的承诺适应于和类似于使用
DeferredResult。例子包括Mono(Reactor)或Single(RxJava)。 -
具有诸如
"application/stream+json"或的流媒体类型的多值流"text/event-stream"适用于并类似于使用ResponseBodyEmitter或SseEmitter。例子包括Flux(Reactor)或Observable(RxJava)。应用程序也可以返回Flux<ServerSentEvent>或Observable<ServerSentEvent>。 -
具有任何其他媒体类型(例如“应用程序/ json”)的多值流适用于并类似于使用
DeferredResult<List<?>>。
|
Spring MVC通过ReactiveAdapterRegistry支持Reactor和RxJava , |
1.6.6。断开
当远程客户端消失时,Servlet API不会提供任何通知。因此,无论是通过SseEmitter还是<< mvc-ann-async-reactive-types,reactive type>,在传输响应时,定期发送数据非常重要,因为如果客户端断开连接,写入操作将失败。发送可以采取空的(仅评论)SSE事件的形式,或者另一方必须将其解释为心跳并忽略的任何其他数据。
或者考虑使用web消息传递解决方案,例如 WebSocket上的STOMP或带有 内置心跳机制的SockJS的 WebSocket 。
1.6.7。组态
异步请求处理功能必须在Servlet容器级别启用。MVC配置也为异步请求提供了几个选项。
Servlet容器
Filter和Servlet声明asyncSupported需要将其设置为true,以启用异步请求处理。另外,应该声明过滤器映射来处理ASYNC javax.servlet.DispatchType。
在Java配置中,当你AbstractAnnotationConfigDispatcherServletInitializer 用来初始化Servlet容器时,这是自动完成的。
在web.xml配置中,添加<async-supported>true</async-supported>到 DispatcherServlet和Filter声明,并添加<dispatcher>ASYNC</dispatcher>到过滤器映射。
Spring MVC
MVC配置公开与异步请求处理相关的选项:
-
Java配置 - 使用
configureAsyncSupport回调WebMvcConfigurer。 -
XML命名空间 - 使用下面的
<async-support>元素<mvc:annotation-driven>。
您可以配置以下内容:
-
Default timeout value for async requests, which if not set, depends on the underlying Servlet container (e.g. 10 seconds on Tomcat).
-
AsyncTaskExecutorto use for blocking writes when streaming with Reactive types, and also for executingCallable's returned from controller methods. It is highly recommended to configure this property if you’re streaming with reactive types or have controller methods that returnCallablesince by default it is aSimpleAsyncTaskExecutor. -
DeferredResultProcessingInterceptor's andCallableProcessingInterceptor's.
Note that the default timeout value can also be set on a DeferredResult, ResponseBodyEmitter and SseEmitter. For a Callable, use WebAsyncTask to provide a timeout value.
1.7. CORS
1.7.1. Introduction
For security reasons browsers prohibit AJAX calls to resources outside the current origin. For example you could have your bank account in one tab and evil.com in another. Scripts from evil.com should not be able to make AJAX requests to your bank API with your credentials, e.g. withdrawing money from your account!
Cross-Origin Resource Sharing (CORS) is a W3C specification implemented by most browsers that allows you to specify what kind of cross domain requests are authorized rather than using less secure and less powerful workarounds based on IFRAME or JSONP.
1.7.2. Processing
The CORS specification distinguishes between preflight, simple, and actual requests. To learn how CORS works, you can readthis article, among many others, or refer to the specification for more details.
Spring MVC HandlerMapping's provide built-in support for CORS. After successfully mapping a request to a handler, HandlerMapping's check the CORS configuration for the given request and handler and take further actions. Preflight requests are handled directly while simple and actual CORS requests are intercepted, validated, and have required CORS response headers set.
In order to enable cross-origin requests (i.e. the Origin header is present and differs from the host of the request) you need to have some explicitly declared CORS configuration. If no matching CORS configuration is found, preflight requests are rejected. No CORS headers are added to the responses of simple and actual CORS requests and consequently browsers reject them.
Each HandlerMapping can be configured individually with URL pattern based CorsConfiguration mappings. In most cases applications will use the MVC Java config or the XML namespace to declare such mappings, which results in a single, global map passed to all HadlerMappping's.
Global CORS configuration at the HandlerMapping level can be combined with more fine-grained, handler-level CORS configuration. For example annotated controllers can use class or method-level @CrossOrigin annotations (other handlers can implement CorsConfigurationSource).
全球和本地配置相结合的规则通常是可以相加的 - 例如,所有全球和所有本地配置。对于那些只能接受单个值的属性,比如allowCredentials和maxAge,局部覆盖全局值。查看 CorsConfiguration#combine(CorsConfiguration) 更多细节。
|
要了解更多信息或进行高级自定义设置,请检查:
|
1.7.3。@CrossOrigin
该@CrossOrigin 标注能够对注解的控制器方法跨域请求:
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
默认情况下@CrossOrigin允许:
-
所有的起源。
-
所有标题。
-
控制器方法映射到的所有HTTP方法。
-
allowedCredentials默认情况下不启用,因为它建立了一个信任级别,暴露敏感的用户特定信息,如cookie和CSRF令牌,并且只能在适当的时候使用。 -
maxAge设置为30分钟。
@CrossOrigin 也在类级别上得到支持,并通过所有方法继承:
@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
CrossOrigin 可以在类和方法级别使用:
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("http://domain2.com")
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
1.7.4。全局配置
除了细粒度的控制器方法级配置之外,您还可能需要定义一些全局CORS配置。你可以CorsConfiguration 在任何地方单独设置基于URL的映射HandlerMapping。然而,大多数应用程序将使用MVC Java配置或MVC XNM命名空间来完成此操作。
默认情况下全局配置启用以下功能:
-
所有的起源。
-
所有标题。
-
GET,HEAD和POST方法。 -
allowedCredentials默认情况下不启用,因为它建立了一个信任级别,暴露敏感的用户特定信息,如cookie和CSRF令牌,并且只能在适当的时候使用。 -
maxAge设置为30分钟。
Java配置
要在MVC Java配置中启用CORS,请使用CorsRegistry回调:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600);
// Add more mappings...
}
}
XML配置
To enable CORS in the XML namespace, use the <mvc:cors> element:
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="http://domain1.com, http://domain2.com"
allowed-methods="GET, PUT"
allowed-headers="header1, header2, header3"
exposed-headers="header1, header2" allow-credentials="true"
max-age="123" />
<mvc:mapping path="/resources/**"
allowed-origins="http://domain1.com" />
</mvc:cors>
1.7.5. CORS Filter
You can apply CORS support through the built-in CorsFilter.
|
If you’re trying to use the |
To configure the filter pass a CorsConfigurationSource to its constructor:
CorsConfiguration config = new CorsConfiguration();
// Possibly...
// config.applyPermitDefaultValues()
config.setAllowCredentials(true);
config.addAllowedOrigin("http://domain1.com");
config.addAllowedHeader("");
config.addAllowedMethod("");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
CorsFilter filter = new CorsFilter(source);
1.8. Web Security
The Spring Security project provides support for protecting web applications from malicious exploits. Check out the Spring Security reference documentation including:
HDIV is another web security framework that integrates with Spring MVC.
1.9. HTTP Caching
A good HTTP caching strategy can significantly improve the performance of a web application and the experience of its clients. The 'Cache-Control' HTTP response header is mostly responsible for this, along with conditional headers such as 'Last-Modified' and 'ETag'.
The 'Cache-Control' HTTP response header advises private caches (e.g. browsers) and public caches (e.g. proxies) on how they can cache HTTP responses for further reuse.
An ETag (entity tag) is an HTTP response header returned by an HTTP/1.1 compliant web server used to determine change in content at a given URL. It can be considered to be the more sophisticated successor to the Last-Modified header. When a server returns a representation with an ETag header, the client can use this header in subsequent GETs, in an If-None-Matchheader. If the content has not changed, the server returns 304: Not Modified.
This section describes the different choices available to configure HTTP caching in a Spring Web MVC application.
1.9.1. Cache-Control
Spring Web MVC支持许多用例以及为应用程序配置“Cache-Control”头的方法。虽然RFC 7234第5.2.2节 完全描述了该头文件及其可能的指令,但有几种方法可以解决最常见的情况。
Spring Web MVC在其几个API中使用了一个配置约定 setCachePeriod(int seconds):
-
甲
-1值将不生成'Cache-Control'响应头。 -
一个
0值,将使用防止缓存'Cache-Control: no-store'指令。 -
一个
n > 0值将n使用'Cache-Control: max-age=n'指令缓存给定的响应秒数 。
该CacheControl生成器类简单地描述了可用的“缓存控制”指令,并使其更容易建立自己的HTTP缓存策略。一旦构建完成,一个CacheControl实例可以被接受为几个Spring Web MVC API中的一个参数。
// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);
// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();
// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS)
.noTransform().cachePublic();
1.9.2。静态资源
应该为静态资源提供适当的'Cache-Control'条件标题以获得最佳性能。 配置一个ResourceHttpRequestHandler for serving静态资源不仅'Last-Modified'通过读取文件的元数据本地写入标题,而且'Cache-Control'如果配置正确,还可以标题。
您可以cachePeriod在a上设置属性ResourceHttpRequestHandler或使用CacheControl支持更多特定指令的实例:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public-resources/")
.setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic());
}
}
在XML中:
<mvc:resources mapping="/resources/**" location="/public-resources/">
<mvc:cache-control max-age="3600" cache-public="true"/>
</mvc:resources>
1.9.3。@Controller缓存
控制器可以支持'Cache-Control','ETag'和/或'If-Modified-Since'HTTP请求; 如果'Cache-Control'要在响应中设置标题,这确实是推荐的。这包括long为给定请求计算lastModified 和/或Etag值,将其与'If-Modified-Since'请求头值进行比较,并可能返回状态码为304(未修改)的响应。
如HttpEntity所述,控制器可以使用HttpEntity类型与请求/响应进行交互 。返回的控制器ResponseEntity可以包含HTTP缓存信息,如下所示:
@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {
Book book = findBook(id);
String version = book.getVersion();
return ResponseEntity
.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
.eTag(version) // lastModified is also available
.body(book);
}
这样做不仅会在响应中包含'ETag'和'Cache-Control'标题,而且如果客户端发送的条件标头与Controller设置的缓存信息相匹配,它也会将响应转换为HTTP 304 Not Modified空的响应。
一种@RequestMapping方法也可能希望支持相同的行为。这可以实现如下:
@RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {
long lastModified = // 1. application-specific calculation
if (request.checkNotModified(lastModified)) {
// 2. shortcut exit - no further processing necessary
return null;
}
// 3. or otherwise further request processing, actually preparing content
model.addAttribute(...);
return "myViewName";
}
这里有两个关键要素:打电话request.checkNotModified(lastModified)和回电null。前者在返回之前设置适当的响应状态和标题true。后者与前者结合使得Spring MVC不再处理请求。
请注意,这有3种变体:
-
request.checkNotModified(lastModified)与比较上次更改时间'If-Modified-Since'或'If-Unmodified-Since'请求头 -
request.checkNotModified(eTag)compares eTag with the'If-None-Match'request header -
request.checkNotModified(eTag, lastModified)does both, meaning that both conditions should be valid
When receiving conditional 'GET'/'HEAD' requests, checkNotModified will check that the resource has not been modified and if so, it will result in a HTTP 304 Not Modified response. In case of conditional 'POST'/'PUT'/'DELETE' requests, checkNotModified will check that the resource has not been modified and if it has been, it will result in a HTTP 409 Precondition Failed response to prevent concurrent modifications.
1.9.4. ETag Filter
Support for ETags is provided by the Servlet filter ShallowEtagHeaderFilter. It is a plain Servlet Filter, and thus can be used in combination with any web framework. The ShallowEtagHeaderFilter filter creates so-called shallow ETags by caching the content written to the response and generating an MD5 hash over that to send as an ETag header. The next time a client sends a request for the same resource, it uses that hash as the If-None-Match value. The filter detects this, lets the request be processed as usual, and at the end compares the two hashes. If they are equal, a 304 is returned.
Note that this strategy saves network bandwidth but not CPU, as the full response must be computed for each request. Other strategies at the controller level, described above, can avoid computation.
This filter has a writeWeakETag parameter that configures the filter to write Weak ETags, like this: W/"02a2d595e6ed9a0b24f027f2b63b134d6", as defined in RFC 7232 Section 2.3.
1.10. View Technologies
The use of view technologies in Spring MVC is pluggable, whether you decide to use Thymeleaf, Groovy Markup Templates, JSPs, or other, is primarily a matter of a configuration change. This chapter covers view technologies integrated with Spring MVC. We assume you are already familiar with View Resolution.
1.10.1. Thymeleaf
Thymeleaf is modern server-side Java template engine that emphasizes natural HTML templates that can be previewed in a browser by double-clicking, which is very helpful for independent work on UI templates, e.g. by designer, without the need for a running server. If you’re looking to replace JSPs, Thymeleaf offers one of the most extensive set of features that will make such a transition easier. Thymeleaf is actively developed and maintained. For a more complete introduction see the Thymeleaf project home page.
The Thymeleaf integration with Spring MVC is managed by the Thymeleaf project. The configuration involves a few bean declarations such as ServletContextTemplateResolver, SpringTemplateEngine, and ThymeleafViewResolver. See Thymeleaf+Spring for more details.
1.10.2. FreeMarker
Apache FreeMarker是一个模板引擎,用于生成从HTML到电子邮件等任何类型的文本输出。Spring框架内置了集成Spring MVC和FreeMarker模板的集成。
查看配置
将FreeMarker配置为视图技术:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.freemarker();
}
// Configure FreeMarker...
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/freemarker");
return configurer;
}
}
在XML中配置相同:
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:freemarker/>
</mvc:view-resolvers>
<!-- Configure FreeMarker... -->
<mvc:freemarker-configurer>
<mvc:template-loader-path location="/WEB-INF/freemarker"/>
</mvc:freemarker-configurer>
或者你也可以声明这个FreeMarkerConfigurerbean来完全控制所有的属性:
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
</bean>
您的模板需要存储在FreeMarkerConfigurer 上面显示的目录中。如果您的控制器返回视图名称“welcome”,给定上述配置,那么解析器将查找/WEB-INF/freemarker/welcome.ftl模板。
FreeMarker配置
FreeMarker的'Settings'和'SharedVariables'可以直接传递给ConfigurationSpring管理的FreeMarker 对象,方法是在bean上设置合适的bean属性FreeMarkerConfigurer。该freemarkerSettings属性需要一个 java.util.Properties对象,该freemarkerVariables属性需要一个 java.util.Map。
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
<property name="freemarkerVariables">
<map>
<entry key="xml_escape" value-ref="fmXmlEscape"/>
</map>
</property>
</bean>
<bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/>
有关设置和变量适用于Configuration对象的详细信息,请参阅FreeMarker文档。
表格处理
Spring提供了一个用于JSP的标签库,其中包含一个 <spring:bind/>标签。此标记主要使表单能够显示来自表单支持对象的值,并显示ValidatorWeb或业务层中失败验证的结果。Spring还支持FreeMarker中的相同功能,还有其他方便的宏来自行生成表单输入元素。
绑定宏
spring-webmvc.jar这两种语言的文件内都保留了一组标准的宏,因此它们始终可用于适当配置的应用程序。
Spring库中定义的一些宏被认为是内部的(私有的),但宏定义中不存在这样的范围,所有的宏都可以调用代码和用户模板。以下部分仅关注需要从模板中直接调用的宏。如果您希望直接查看宏代码,则会spring.ftl在程序包中 调用该文件org.springframework.web.servlet.view.freemarker。
简单的绑定
在作为Spring MVC控制器的表单视图的HTML表单(vm / ftl模板)中,可以使用类似于以下内容的代码绑定到字段值,并以与JSP等效的方式类似的方式显示每个输入字段的错误消息。以下为personForm先前配置的视图显示了示例代码:
<!-- freemarker macros have to be imported into a namespace. We strongly
recommend sticking to 'spring' -->
<#import "/spring.ftl" as spring/>
<html>
...
<form action="" method="POST">
Name:
<@spring.bind "myModelObject.name"/>
<input type="text"
name="${spring.status.expression}"
value="${spring.status.value?html}"/><br>
<#list spring.status.errorMessages as error> <b>${error}</b> <br> </#list>
<br>
...
<input type="submit" value="submit"/>
</form>
...
</html>
<@spring.bind> requires a 'path' argument which consists of the name of your command object (it will be 'command' unless you changed it in your FormController properties) followed by a period and the name of the field on the command object you wish to bind to. Nested fields can be used too such as "command.address.street". The bind macro assumes the default HTML escaping behavior specified by the ServletContext parameter defaultHtmlEscape in web.xml.
The optional form of the macro called <@spring.bindEscaped> takes a second argument and explicitly specifies whether HTML escaping should be used in the status error messages or values. Set to true or false as required. Additional form handling macros simplify the use of HTML escaping and these macros should be used wherever possible. They are explained in the next section.
Input macros
Additional convenience macros for both languages simplify both binding and form generation (including validation error display). It is never necessary to use these macros to generate form input fields, and they can be mixed and matched with simple HTML or calls direct to the spring bind macros highlighted previously.
The following table of available macros show the FTL definitions and the parameter list that each takes.
| macro | FTL definition |
|---|---|
|
message (output a string from a resource bundle based on the code parameter) |
<@spring.message code/> |
|
messageText (output a string from a resource bundle based on the code parameter, falling back to the value of the default parameter) |
<@spring.messageText code, text/> |
|
url (prefix a relative URL with the application’s context root) |
<@spring.url relativeUrl/> |
|
formInput (standard input field for gathering user input) |
<@spring.formInput path, attributes, fieldType/> |
|
formHiddenInput * (hidden input field for submitting non-user input) |
<@spring.formHiddenInput path, attributes/> |
|
formPasswordInput * (standard input field for gathering passwords. Note that no value will ever be populated in fields of this type) |
<@spring.formPasswordInput path, attributes/> |
|
formTextarea (large text field for gathering long, freeform text input) |
<@spring.formTextarea path, attributes/> |
|
formSingleSelect (drop down box of options allowing a single required value to be selected) |
<@spring.formSingleSelect path, options, attributes/> |
|
formMultiSelect (a list box of options allowing the user to select 0 or more values) |
<@spring.formMultiSelect path, options, attributes/> |
|
formRadioButtons (a set of radio buttons allowing a single selection to be made from the available choices) |
<@spring.formRadioButtons path, options separator, attributes/> |
|
formCheckboxes (a set of checkboxes allowing 0 or more values to be selected) |
<@spring.formCheckboxes path, options, separator, attributes/> |
|
formCheckbox (a single checkbox) |
<@spring.formCheckbox path, attributes/> |
|
showErrors (simplify display of validation errors for the bound field) |
<@spring.showErrors separator, classOrStyle/> |
-
In FTL (FreeMarker),
formHiddenInputandformPasswordInputare not actually required as you can use the normalformInputmacro, specifyinghiddenorpasswordas the value for thefieldTypeparameter.
The parameters to any of the above macros have consistent meanings:
-
path: the name of the field to bind to (ie "command.name")
-
options: a Map of all the available values that can be selected from in the input field. The keys to the map represent the values that will be POSTed back from the form and bound to the command object. Map objects stored against the keys are the labels displayed on the form to the user and may be different from the corresponding values posted back by the form. Usually such a map is supplied as reference data by the controller. Any Map implementation can be used depending on required behavior. For strictly sorted maps, a
SortedMapsuch as aTreeMapwith a suitable Comparator may be used and for arbitrary Maps that should return values in insertion order, use aLinkedHashMapor aLinkedMapfrom commons-collections. -
separator: where multiple options are available as discreet elements (radio buttons or checkboxes), the sequence of characters used to separate each one in the list (ie "<br>").
-
attributes: an additional string of arbitrary tags or text to be included within the HTML tag itself. This string is echoed literally by the macro. For example, in a textarea field you may supply attributes as 'rows="5" cols="60"' or you could pass style information such as 'style="border:1px solid silver"'.
-
classOrStyle: for the showErrors macro, the name of the CSS class that the span tag wrapping each error will use. If no information is supplied (or the value is empty) then the errors will be wrapped in <b></b> tags.
Examples of the macros are outlined below some in FTL and some in VTL. Where usage differences exist between the two languages, they are explained in the notes.
The formInput macro takes the path parameter (command.name) and an additional attributes parameter which is empty in the example above. The macro, along with all other form generation macros, performs an implicit spring bind on the path parameter. The binding remains valid until a new bind occurs so the showErrors macro doesn’t need to pass the path parameter again - it simply operates on whichever field a bind was last created for.
The showErrors macro takes a separator parameter (the characters that will be used to separate multiple errors on a given field) and also accepts a second parameter, this time a class name or style attribute. Note that FreeMarker is able to specify default values for the attributes parameter.
<@spring.formInput "command.name"/>
<@spring.showErrors "<br>"/>
Output is shown below of the form fragment generating the name field, and displaying a validation error after the form was submitted with no value in the field. Validation occurs through Spring’s Validation framework.
The generated HTML looks like this:
Name:
<input type="text" name="name" value="">
<br>
<b>required</b>
<br>
<br>
The formTextarea macro works the same way as the formInput macro and accepts the same parameter list. Commonly, the second parameter (attributes) will be used to pass style information or rows and cols attributes for the textarea.
四个选择字段宏可用于在HTML表单中生成通用的UI值选择输入。
-
formSingleSelect
-
formMultiSelect
-
formRadioButtons
-
formCheckboxes
这四个宏中的每一个都接受包含表单字段值的选项的Map,以及与该值相对应的标签。值和标签可以相同。
FTL中的单选按钮示例如下。表单支持对象为此字段指定默认值'London',因此不需要验证。当表单被渲染时,整个城市列表将作为模型中的参考数据以“城市地图”名称提供。
...
Town:
<@spring.formRadioButtons "command.address.town", cityMap, ""/><br><br>
这将呈现一行单选按钮,每个值cityMap使用分隔符“”。没有提供额外的属性(缺少宏的最后一个参数)。城市地图为地图中的每个键值对使用相同的字符串。地图的键是表单实际提交的POST请求参数的内容,地图值是用户看到的标签。在上面的例子中,给定一个三个知名城市的列表和一个默认值的形式支持对象,HTML将会是
Town:
<input type="radio" name="address.town" value="London">London</input>
<input type="radio" name="address.town" value="Paris" checked="checked">Paris</input>
<input type="radio" name="address.town" value="New York">New York</input>
例如,如果您的应用程序希望通过内部代码处理城市,则可以使用适合的键(如下面的示例)创建代码地图。
protected Map<String, String> referenceData(HttpServletRequest request) throws Exception {
Map<String, String> cityMap = new LinkedHashMap<>();
cityMap.put("LDN", "London");
cityMap.put("PRS", "Paris");
cityMap.put("NYC", "New York");
Map<String, String> model = new HashMap<>();
model.put("cityMap", cityMap);
return model;
}
该代码现在将产生输出,其中无线电值是相关的代码,但用户仍然看到更多用户友好的城市名称。
Town:
<input type="radio" name="address.town" value="LDN">London</input>
<input type="radio" name="address.town" value="PRS" checked="checked">Paris</input>
<input type="radio" name="address.town" value="NYC">New York</input>
HTML转义
上面的表单宏的默认使用将导致符合HTML 4.01的HTML标记,并使用Spring绑定支持所使用的web.xml中定义的HTML转义的默认值。为了使标签符合XHTML标准或覆盖默认的HTML转义值,您可以在模板中指定两个变量(或者在您的模型中可以在模板中看到它们)。在模板中指定它们的好处是,它们可以在模板处理中稍后更改为不同的值,以便为表单中的不同字段提供不同的行为。
要切换到标签的XHTML合规性,请true为名为xhtmlCompliant的模型/上下文变量指定一个值:
<#-- for FreeMarker -->
<#assign xhtmlCompliant = true>
在处理此指令后,由Spring宏生成的任何标签现在都将符合XHTML标准。
以类似的方式,可以为每个字段指定HTML转义:
<#-- until this point, default HTML escaping is used -->
<#assign htmlEscape = true>
<#-- next field will use HTML escaping -->
<@spring.formInput "command.name"/>
<#assign htmlEscape = false in spring>
<#-- all future fields will be bound with HTML escaping off -->
1.10.3。Groovy标记
Groovy标记模板引擎 主要用于生成类似XML的标记(XML,XHTML,HTML5等),但可用于生成任何基于文本的内容。Spring框架内置了集成Spring MVC和Groovy Markup的集成功能。
|
Groovy Markup Tempalte引擎需要Groovy 2.3.1+。 |
组态
要配置Groovy标记模板引擎,请执行以下操作:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.groovy();
}
// Configure the Groovy Markup Template Engine...
@Bean
public GroovyMarkupConfigurer groovyMarkupConfigurer() {
GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer();
configurer.setResourceLoaderPath("/WEB-INF/");
return configurer;
}
}
在XML中配置相同:
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:groovy/>
</mvc:view-resolvers>
<!-- Configure the Groovy Markup Template Engine... -->
<mvc:groovy-configurer resource-loader-path="/WEB-INF/"/>
例
与传统的模板引擎不同,Groovy Markup依赖于使用构建器语法的DSL。以下是HTML页面的示例模板:
yieldUnescaped '<!DOCTYPE html>'
html(lang:'en') {
head {
meta('http-equiv':'"Content-Type" content="text/html; charset=utf-8"')
title('My page')
}
body {
p('This is an example of HTML contents')
}
}
1.10.4。脚本视图
Spring框架内置了一个集成功能,可以将Spring MVC与任何可以在JSR-223 Java脚本引擎之上运行的模板库一起使用 。以下是我们在不同脚本引擎上测试过的模板库列表:
|
集成任何其他脚本引擎的基本规则是它必须实现 |
要求
你需要在你的类路径上有脚本引擎:
-
Nashorn JavaScript引擎随Java 8+提供。强烈建议使用最新的更新版本。
-
应该添加JRuby作为Ruby支持的依赖项。
-
应该添加Jython作为Python支持的依赖。
-
org.jetbrains.kotlin:kotlin-script-util应该为Kotlin脚本支持添加依赖关系和META-INF/services/javax.script.ScriptEngineFactory包含org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory行的文件,请参阅 此示例以获取更多详细信息。
你需要有脚本模板库。一种方法来做到这一点是通过WebJars。
脚本模板
声明一个ScriptTemplateConfigurerbean,以便指定要使用的脚本引擎,要加载的脚本文件,要调用哪个函数来渲染模板等等。以下是Mustache模板和Nashorn JavaScript引擎的示例:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("mustache.js");
configurer.setRenderObject("Mustache");
configurer.setRenderFunction("render");
return configurer;
}
}
XML中的内容相同:
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:script-template/>
</mvc:view-resolvers>
<mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render">
<mvc:script location="mustache.js"/>
</mvc:script-template-configurer>
控制器看起来没有什么不同:
@Controller
public class SampleController {
@GetMapping("/sample")
public String test(Model model) {
model.addObject("title", "Sample title");
model.addObject("body", "Sample body");
return "template";
}
}
胡子模板是:
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<p>{{body}}</p>
</body>
</html>
使用以下参数调用渲染函数:
-
String template:模板内容 -
Map model:视图模型 -
RenderingContext renderingContext: the RenderingContext that gives access to the application context, the locale, the template loader and the url (since 5.0)
Mustache.render() is natively compatible with this signature, so you can call it directly.
If your templating technology requires some customization, you may provide a script that implements a custom render function. For example, Handlerbars needs to compile templates before using them, and requires a polyfill in order to emulate some browser facilities not available in the server-side script engine.
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
configurer.setRenderFunction("render");
configurer.setSharedEngine(false);
return configurer;
}
}
|
将非线程安全脚本引擎与非并发设计的模板库(例如在Nashorn上运行的Handlebars或React)一起使用时,将 |
polyfill.js只定义windowHandlebars正确运行所需的对象:
var window = {};
这个基本的render.js实现在使用它之前编译模板。生产准备就绪的实现还应该存储和重用缓存模板/预编译模板。这可以在脚本端完成,以及您需要的任何自定义(例如管理模板引擎配置)。
function render(template, model) {
var compiledTemplate = Handlebars.compile(template);
return compiledTemplate(model);
}
1.10.5。JSP和JSTL
Spring框架内置了集成Spring MVC和JSP和JSTL的集成。
查看解析器
在使用JSP进行开发时,您可以声明一个InternalResourceViewResolver或一个 ResourceBundleViewResolverbean。
ResourceBundleViewResolver依赖于一个属性文件来定义映射到一个类和一个URL的视图名称。通过ResourceBundleViewResolver使用一个解析器,您可以混合不同类型的视图。这里是一个例子:
<!-- the ResourceBundleViewResolver -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views"/>
</bean>
# And a sample properties file is uses (views.properties in WEB-INF/classes):
welcome.(class)=org.springframework.web.servlet.view.JstlView
welcome.url=/WEB-INF/jsp/welcome.jsp
productList.(class)=org.springframework.web.servlet.view.JstlView
productList.url=/WEB-INF/jsp/productlist.jsp
InternalResourceBundleViewResolver也可以用于JSP。作为最佳实践,我们强烈建议将JSP文件放在目录下的'WEB-INF' 目录下,以免客户端直接访问。
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
JSP与JSTL
当使用Java标准标签库时,你必须使用特殊的视图类 JstlView,因为JSTL需要做一些准备工作,比如I18N特性才能工作。
Spring的JSP标签库
Spring提供了请求参数与命令对象的数据绑定,如前面章节所述。为了促进JSP页面的开发并结合这些数据绑定功能,Spring提供了一些使事情更简单的标签。所有Spring标签都具有HTML转义功能来启用或禁用字符转义。
该spring.tld标签库描述符(TLD)包含在spring-webmvc.jar。有关各个标签的全面参考,请浏览 API参考 或查看标签库说明。
Spring的表单标签库
从2.0版开始,Spring提供了一套全面的数据绑定感知标签,用于在使用JSP和Spring Web MVC时处理表单元素。每个标签都提供对其相应HTML标签对应的属性集合的支持,从而使得标签能够被熟悉和直观地使用。标签生成的HTML符合HTML 4.01 / XHTML 1.0标准。
与其他form / input标签库不同,Spring的form标签库与Spring Web MVC集成,使标签可以访问控制器处理的命令对象和引用数据。正如您在下面的示例中所看到的,表单标签使JSP更容易开发,读取和维护。
让我们通过表单标签并查看每个标签如何使用的示例。我们已经包含了生成的HTML代码片段,其中某些代码需要进一步的评论。
组态
表格标签库捆绑在一起spring-webmvc.jar。库描述符被调用spring-form.tld。
要使用该库中的标签,请将以下指令添加到JSP页面的顶部:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
其中form是您希望用于来自此库的标记的标记名称前缀。
表单标签
这个标签呈现一个HTML'form'标签并公开绑定路径到内部标签进行绑定。它将命令对象放入,PageContext以便内部标记可以访问命令对象。在这个库中的其他标签是嵌套的标签 form标记。
假设我们有一个名为的域对象User。它是一个JavaBean,具有诸如firstName和的属性lastName。我们将使用它作为我们的表单控制器返回的表单支持对象form.jsp。以下是一个例子form.jsp:
<form:form>
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>
该firstName和lastName值由放置在命令对象中检索PageContext由页控制器。请继续阅读以查看更多复杂内部标签如何与标签一起使用的示例form。
生成的HTML看起来像一个标准的形式:
<form method="POST">
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value="Harry"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value="Potter"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form>
前面的JSP假定表单支持对象的变量名是 'command'。如果你已经将表单支持对象以另一个名称(绝对是最佳实践)放入模型中,那么你可以将表单绑定到指定的变量,如下所示:
<form:form modelAttribute="user">
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>
输入标签
此标记默认使用绑定值和type ='text'呈现HTML“输入”标记。有关此标记的示例,请参阅表单标记。从Spring 3.1开始,您可以使用其他类型,如“email”,“tel”,“date”等HTML5特定类型。
复选框标记
这个标签呈现一个类型为'checkbox'的HTML'input'标签。
假设我们User有喜好,例如通讯订阅和业余爱好列表。以下是该Preferences课程的一个例子:
public class Preferences {
private boolean receiveNewsletter;
private String[] interests;
private String favouriteWord;
public boolean isReceiveNewsletter() {
return receiveNewsletter;
}
public void setReceiveNewsletter(boolean receiveNewsletter) {
this.receiveNewsletter = receiveNewsletter;
}
public String[] getInterests() {
return interests;
}
public void setInterests(String[] interests) {
this.interests = interests;
}
public String getFavouriteWord() {
return favouriteWord;
}
public void setFavouriteWord(String favouriteWord) {
this.favouriteWord = favouriteWord;
}
}
这form.jsp看起来像:
<form:form>
<table>
<tr>
<td>Subscribe to newsletter?:</td>
<%-- Approach 1: Property is of type java.lang.Boolean --%>
<td><form:checkbox path="preferences.receiveNewsletter"/></td>
</tr>
<tr>
<td>Interests:</td>
<%-- Approach 2: Property is of an array or of type java.util.Collection --%>
<td>
Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/>
Herbology: <form:checkbox path="preferences.interests" value="Herbology"/>
Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/>
</td>
</tr>
<tr>
<td>Favourite Word:</td>
<%-- Approach 3: Property is of type java.lang.Object --%>
<td>
Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/>
</td>
</tr>
</table>
</form:form>
有3种方法checkbox可以满足您的所有复选框需求。
-
Approach One - When the bound value is of type
java.lang.Boolean, theinput(checkbox)is marked as 'checked' if the bound value istrue. Thevalueattribute corresponds to the resolved value of thesetValue(Object)value property. -
Approach Two - When the bound value is of type
arrayorjava.util.Collection, theinput(checkbox)is marked as 'checked' if the configuredsetValue(Object)value is present in the boundCollection. -
Approach Three - For any other bound value type, the
input(checkbox)is marked as 'checked' if the configuredsetValue(Object)is equal to the bound value.
Note that regardless of the approach, the same HTML structure is generated. Below is an HTML snippet of some checkboxes:
<tr>
<td>Interests:</td>
<td>
Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/>
<input type="hidden" value="1" name="_preferences.interests"/>
Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/>
<input type="hidden" value="1" name="_preferences.interests"/>
Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/>
<input type="hidden" value="1" name="_preferences.interests"/>
</td>
</tr>
What you might not expect to see is the additional hidden field after each checkbox. When a checkbox in an HTML page is notchecked, its value will not be sent to the server as part of the HTTP request parameters once the form is submitted, so we need a workaround for this quirk in HTML in order for Spring form data binding to work. The checkbox tag follows the existing Spring convention of including a hidden parameter prefixed by an underscore ("_") for each checkbox. By doing this, you are effectively telling Spring that "the checkbox was visible in the form and I want my object to which the form data will be bound to reflect the state of the checkbox no matter what".
The checkboxes tag
This tag renders multiple HTML 'input' tags with type 'checkbox'.
Building on the example from the previous checkbox tag section. Sometimes you prefer not to have to list all the possible hobbies in your JSP page. You would rather provide a list at runtime of the available options and pass that in to the tag. That is the purpose of the checkboxes tag. You pass in an Array, a List or a Map containing the available options in the "items" property. Typically the bound property is a collection so it can hold multiple values selected by the user. Below is an example of the JSP using this tag:
<form:form>
<table>
<tr>
<td>Interests:</td>
<td>
<%-- Property is of an array or of type java.util.Collection --%>
<form:checkboxes path="preferences.interests" items="${interestList}"/>
</td>
</tr>
</table>
</form:form>
此示例假定“interestList” List作为包含要从中选择的值的字符串的模型属性可用。在使用地图的情况下,地图输入键将用作值,并且地图条目的值将被用作要显示的标签。您还可以使用自定义对象,您可以在其中使用“itemValue”为值提供属性名称,使用“itemLabel”为该标签提供属性名称。
单选按钮标签
这个标签呈现一个类型为'radio'的HTML'input'标签。
典型的使用模式将涉及绑定到相同属性但具有不同值的多个标记实例。
<tr>
<td>Sex:</td>
<td>
Male: <form:radiobutton path="sex" value="M"/> <br/>
Female: <form:radiobutton path="sex" value="F"/>
</td>
</tr>
单选按钮标签
这个标签呈现多个HTML'输入'标签,类型为'radio'。
就像checkboxes上面的标签一样,您可能想要将可用选项作为运行时变量传递。对于这种用法,您可以使用radiobuttons标签。您传递“ Array,” List或“ Map包含”项目“属性中的可用选项。在使用地图的情况下,地图输入键将用作值,并且地图条目的值将被用作要显示的标签。您还可以使用自定义对象,您可以在其中使用“itemValue”为值提供属性名称,使用“itemLabel”为该标签提供属性名称。
<tr>
<td>Sex:</td>
<td><form:radiobuttons path="sex" items="${sexOptions}"/></td>
</tr>
密码标签
该标签使用绑定值呈现一个类型为“password”的HTML“输入”标签。
<tr>
<td>Password:</td>
<td>
<form:password path="password"/>
</td>
</tr>
请注意,默认情况下,密码值不显示。如果您确实希望显示密码值,请将'showPassword'属性的值设置为true,如此。
<tr>
<td>Password:</td>
<td>
<form:password path="password" value="^76525bvHGq" showPassword="true"/>
</td>
</tr>
选择标签
这个标签呈现一个HTML'select'元素。它支持数据绑定到选定的选项,以及使用嵌套option和options标签。
假设User有一个技能列表。
<tr>
<td>Skills:</td>
<td><form:select path="skills" items="${skills}"/></td>
</tr>
如果该User’s技能在Herbology中,则“技能”行的HTML源代码将如下所示:
<tr>
<td>Skills:</td>
<td>
<select name="skills" multiple="true">
<option value="Potions">Potions</option>
<option value="Herbology" selected="selected">Herbology</option>
<option value="Quidditch">Quidditch</option>
</select>
</td>
</tr>
选项标签
这个标签呈现一个HTML'选项'。它根据绑定值适当设置“选定”。
<tr>
<td>House:</td>
<td>
<form:select path="house">
<form:option value="Gryffindor"/>
<form:option value="Hufflepuff"/>
<form:option value="Ravenclaw"/>
<form:option value="Slytherin"/>
</form:select>
</td>
</tr>
如果User’s房子在格兰芬多,那么'House'行的HTML源代码将如下所示:
<tr>
<td>House:</td>
<td>
<select name="house">
<option value="Gryffindor" selected="selected">Gryffindor</option>
<option value="Hufflepuff">Hufflepuff</option>
<option value="Ravenclaw">Ravenclaw</option>
<option value="Slytherin">Slytherin</option>
</select>
</td>
</tr>
选项标签
这个标签呈现HTML'option'标签的列表。它根据绑定值适当地设置'selected'属性。
<tr>
<td>Country:</td>
<td>
<form:select path="country">
<form:option value="-" label="--Please Select"/>
<form:options items="${countryList}" itemValue="code" itemLabel="name"/>
</form:select>
</td>
</tr>
如果User居住在英国,“国家”行的HTML源代码将如下所示:
<tr>
<td>Country:</td>
<td>
<select name="country">
<option value="-">--Please Select</option>
<option value="AT">Austria</option>
<option value="UK" selected="selected">United Kingdom</option>
<option value="US">United States</option>
</select>
</td>
</tr>
如示例所示,option标记与options标记的组合使用会生成相同的标准HTML,但允许您在JSP中明确指定一个仅用于显示的值(其所属的位置),例如示例中的默认字符串: “ - 请选择”。
The items attribute is typically populated with a collection or array of item objects. itemValue and itemLabel simply refer to bean properties of those item objects, if specified; otherwise, the item objects themselves will be stringified. Alternatively, you may specify a Map of items, in which case the map keys are interpreted as option values and the map values correspond to option labels. If itemValue and/or itemLabel happen to be specified as well, the item value property will apply to the map key and the item label property will apply to the map value.
The textarea tag
This tag renders an HTML 'textarea'.
<tr>
<td>Notes:</td>
<td><form:textarea path="notes" rows="3" cols="20"/></td>
<td><form:errors path="notes"/></td>
</tr>
The hidden tag
该标记使用绑定值呈现类型为“隐藏”的HTML“输入”标记。要提交未绑定的隐藏值,请使用input类型为“隐藏” 的HTML 标记。
<form:hidden path="house"/>
如果我们选择将“房屋”价值作为隐藏价值提交,那么HTML将如下所示:
<input name="house" type="hidden" value="Gryffindor"/>
错误标签
此标记在HTML“span”标记中呈现字段错误。它提供对在您的控制器中创建的错误的访问或由与您的控制器关联的任何验证器创建的错误。
假设我们希望在 提交表单后显示firstName和lastName字段的所有错误消息。我们有一个User叫做实例的验证器UserValidator。
public class UserValidator implements Validator {
public boolean supports(Class candidate) {
return User.class.isAssignableFrom(candidate);
}
public void validate(Object obj, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.");
}
}
这form.jsp看起来像:
<form:form>
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
<%-- Show errors for firstName field --%>
<td><form:errors path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
<%-- Show errors for lastName field --%>
<td><form:errors path="lastName"/></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>
如果我们在firstName和lastName字段中提交一个空值的表单,这就是HTML的样子:
<form method="POST">
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value=""/></td>
<%-- Associated errors to firstName field displayed --%>
<td><span name="firstName.errors">Field is required.</span></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value=""/></td>
<%-- Associated errors to lastName field displayed --%>
<td><span name="lastName.errors">Field is required.</span></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form>
如果我们想要显示给定页面的整个错误列表,该怎么办?以下示例显示errors标签还支持一些基本的通配符功能。
-
path="*"- 显示所有错误 -
path="lastName"- 显示与该lastName字段相关的所有错误 -
如果
path省略 - 仅显示对象错误
以下示例将在页面顶部显示错误列表,并在字段旁边显示特定于字段的错误:
<form:form>
<form:errors path="*" cssClass="errorBox"/>
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
<td><form:errors path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
<td><form:errors path="lastName"/></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>
HTML看起来像这样:
<form method="POST">
<span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span>
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value=""/></td>
<td><span name="firstName.errors">Field is required.</span></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value=""/></td>
<td><span name="lastName.errors">Field is required.</span></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form>
该spring-form.tld标签库描述符(TLD)包含在spring-webmvc.jar。有关各个标签的全面参考,请浏览 API参考 或查看标签库说明。
HTTP method conversion
A key principle of REST is the use of the Uniform Interface. This means that all resources (URLs) can be manipulated using the same four HTTP methods: GET, PUT, POST, and DELETE. For each method, the HTTP specification defines the exact semantics. For instance, a GET should always be a safe operation, meaning that is has no side effects, and a PUT or DELETE should be idempotent, meaning that you can repeat these operations over and over again, but the end result should be the same. While HTTP defines these four methods, HTML only supports two: GET and POST. Fortunately, there are two possible workarounds: you can either use JavaScript to do your PUT or DELETE, or simply do a POST with the 'real' method as an additional parameter (modeled as a hidden input field in an HTML form). This latter trick is what Spring’s HiddenHttpMethodFilter确实。这个过滤器是一个普通的Servlet过滤器,因此它可以与任何Web框架(不只是Spring MVC)结合使用。只需将此过滤器添加到您的web.xml中,并将带有隐藏_method参数的POST转换为相应的HTTP方法请求。
为了支持HTTP方法转换,Spring MVC表单标签被更新为支持设置HTTP方法。例如,以下摘自Petclinic更新样本的片段
<form:form method="delete">
<p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>
这实际上会执行一个HTTP POST,在请求参数后面隐藏着'真正的'DELETE方法,可以由HiddenHttpMethodFilterweb.xml中定义的来获取:
<filter>
<filter-name>httpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>httpMethodFilter</filter-name>
<servlet-name>petclinic</servlet-name>
</filter-mapping>
相应的@Controller方法如下所示:
@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
this.clinic.deletePet(petId);
return "redirect:/owners/" + ownerId;
}
HTML5标签
从Spring 3开始,Spring窗体标签库允许输入动态属性,这意味着您可以输入任何HTML5特定的属性。
在Spring 3.1中,表单输入标签支持输入除“文本”以外的类型属性。这旨在允许呈现新的HTML5特定输入类型,例如'email','date','range'等。请注意,输入type ='text'不是必需的,因为'text'是默认类型。
1.10.6。瓷砖
在使用Spring的Web应用程序中,可以将Tiles与任何其他视图技术一样集成。以下以广泛的方式描述如何做到这一点。
|
本节重点介绍Spring对 |
依赖
为了能够使用Tiles,必须在Tiles版本3.0.1或更高版本上添加依赖项,并将其传递依赖项添加 到项目中。
组态
为了能够使用Tiles,你必须使用包含定义的文件来配置它(关于定义和其他Tiles概念的基本信息,请查看 http://tiles.apache.org)。在春天,这是使用TilesConfigurer。看看以下一段示例ApplicationContext配置:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/general.xml</value>
<value>/WEB-INF/defs/widgets.xml</value>
<value>/WEB-INF/defs/administrator.xml</value>
<value>/WEB-INF/defs/customer.xml</value>
<value>/WEB-INF/defs/templates.xml</value>
</list>
</property>
</bean>
如您所见,有五个包含定义的文件,它们都位于'WEB-INF/defs'目录中。在初始化时WebApplicationContext,文件将被加载并且定义工厂将被初始化。在完成之后,Tiles包含的定义文件可以在Spring Web应用程序中用作视图。为了能够使用视图,您必须具有ViewResolver 与Spring使用的其他视图技术一样的视图。下面你可以找到两种可能性,UrlBasedViewResolver和ResourceBundleViewResolver。
您可以通过添加下划线和区域设置来指定特定于语言环境的Tiles定义。例如:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/tiles.xml</value>
<value>/WEB-INF/defs/tiles_fr_FR.xml</value>
</list>
</property>
</bean>
使用此配置,tiles_fr_FR.xml将用于具有fr_FR区域设置的请求,tiles.xml并将在默认情况下使用。
|
由于下划线用于指示区域设置,因此建议避免在Tiles定义的文件名中使用它们。 |
作为UrlBasedViewResolver
在UrlBasedViewResolver给定的实例化viewClass每个查看它来解决。
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/>
</bean>
一个ResourceBundleViewResolver
的ResourceBundleViewResolver具有被提供有含有视图名称和视图类解析器可以使用属性文件:
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views"/>
</bean>
...
welcomeView.(class)=org.springframework.web.servlet.view.tiles3.TilesView
welcomeView.url=welcome (this is the name of a Tiles definition)
vetsView.(class)=org.springframework.web.servlet.view.tiles3.TilesView
vetsView.url=vetsView (again, this is the name of a Tiles definition)
findOwnersForm.(class)=org.springframework.web.servlet.view.JstlView
findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp
...
如您所见,使用时ResourceBundleViewResolver,您可以轻松混合不同的视图技术。
请注意,TilesView该类支持JSTL(JSP标准标记库)。
SimpleSpringPreparerFactory和SpringBeanPreparerFactory
作为一项高级功能,Spring还支持两个特殊的Tiles PreparerFactory 实现。查看Tiles文档,了解如何ViewPreparer在Tiles定义文件中使用引用的详细信息 。
指定SimpleSpringPreparerFactory根据指定的preparer类自动装载ViewPreparer实例,应用Spring的容器回调以及应用配置的Spring BeanPostProcessor。如果Spring的上下文范围注释配置已被激活,ViewPrepare类中的注释将被自动检测和应用。请注意,这需要Tiles定义文件中的preparer 类,就像缺省PreparerFactory一样。
指定SpringBeanPreparerFactory对指定的preparer 名称而不是类进行操作,从DispatcherServlet的应用程序上下文中获取相应的Spring bean。在这种情况下,完整的bean创建过程将处于Spring应用程序上下文的控制之下,从而允许使用显式的依赖注入配置,范围化的bean等。注意,您需要为每个preparer名称定义一个Spring bean定义你的瓷砖定义)。
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/general.xml</value>
<value>/WEB-INF/defs/widgets.xml</value>
<value>/WEB-INF/defs/administrator.xml</value>
<value>/WEB-INF/defs/customer.xml</value>
<value>/WEB-INF/defs/templates.xml</value>
</list>
</property>
<!-- resolving preparer names as Spring bean definition names -->
<property name="preparerFactoryClass"
value="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory"/>
</bean>
1.10.7。RSS,Atom
双方AbstractAtomFeedView并AbstractRssFeedView从基类继承 AbstractFeedView,并用来提供恭敬地Atom和RSS源的观点。它们基于java.net的ROME项目,位于软件包中org.springframework.web.servlet.view.feed。
AbstractAtomFeedView要求您实现该buildFeedEntries()方法并可选择覆盖该buildFeedMetadata()方法(默认实现为空),如下所示。
public class SampleContentAtomView extends AbstractAtomFeedView {
@Override
protected void buildFeedMetadata(Map<String, Object> model,
Feed feed, HttpServletRequest request) {
// implementation omitted
}
@Override
protected List<Entry> buildFeedEntries(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) throws Exception {
// implementation omitted
}
}
类似的要求适用于实施AbstractRssFeedView,如下所示。
public class SampleContentAtomView extends AbstractRssFeedView {
@Override
protected void buildFeedMetadata(Map<String, Object> model,
Channel feed, HttpServletRequest request) {
// implementation omitted
}
@Override
protected List<Item> buildFeedItems(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) throws Exception {
// implementation omitted
}
}
The buildFeedItems() and buildFeedEntires() methods pass in the HTTP request in case you need to access the Locale. The HTTP response is passed in only for the setting of cookies or other HTTP headers. The feed will automatically be written to the response object after the method returns.
For an example of creating an Atom view please refer to Alef Arendsen’s Spring Team Blog entry.
1.10.8. PDF, Excel
Introduction
Returning an HTML page isn’t always the best way for the user to view the model output, and Spring makes it simple to generate a PDF document or an Excel spreadsheet dynamically from the model data. The document is the view and will be streamed from the server with the correct content type to (hopefully) enable the client PC to run their spreadsheet or PDF viewer application in response.
In order to use Excel views, you need to add the Apache POI library to your classpath, and for PDF generation preferably the OpenPDF library.
|
Use the latest versions of the underlying document generation libraries if possible. In particular, we strongly recommend OpenPDF (e.g. OpenPDF 1.0.5) instead of the outdated original iText 2.1.7 since it is actively maintained and fixes an important vulnerability for untrusted PDF content. |
Configuration
Document based views are handled in an almost identical fashion to XSLT views, and the following sections build upon the previous one by demonstrating how the same controller used in the XSLT example is invoked to render the same model as both a PDF document and an Excel spreadsheet (which can also be viewed or manipulated in Open Office).
View definition
First, let’s amend the views.properties file (or xml equivalent) and add a simple view definition for both document types. The entire file now looks like this with the XSLT view shown from earlier:
home.(class)=xslt.HomePage home.stylesheetLocation=/WEB-INF/xsl/home.xslt home.root=words xl.(class)=excel.HomePage pdf.(class)=pdf.HomePage
If you want to start with a template spreadsheet or a fillable PDF form to add your model data to, specify the location as the 'url' property in the view definition
Controller
The controller code we’ll use remains exactly the same from the XSLT example earlier other than to change the name of the view to use. Of course, you could be clever and have this selected based on a URL parameter or some other logic - proof that Spring really is very good at decoupling the views from the controllers!
Excel views
就像我们为XSLT示例所做的那样,我们将继承合适的抽象类,以便在生成输出文档时实现自定义行为。对于Excel,这包括编写org.springframework.web.servlet.view.document.AbstractExcelView(对于由POI org.springframework.web.servlet.view.document.AbstractJExcelView 生成的Excel文件)或(对于JExcelApi生成的Excel文件)的子类并实现该buildExcelDocument()方法。
以下是POI Excel视图的完整列表,其中显示了新电子表格第一列的连续行中模型映射的单词列表:
package excel;
// imports omitted for brevity
public class HomePage extends AbstractExcelView {
protected void buildExcelDocument(Map model, HSSFWorkbook wb, HttpServletRequest req,
HttpServletResponse resp) throws Exception {
HSSFSheet sheet;
HSSFRow sheetRow;
HSSFCell cell;
// Go to the first sheet
// getSheetAt: only if wb is created from an existing document
// sheet = wb.getSheetAt(0);
sheet = wb.createSheet("Spring");
sheet.setDefaultColumnWidth((short) 12);
// write a text at A1
cell = getCell(sheet, 0, 0);
setText(cell, "Spring-Excel test");
List words = (List) model.get("wordList");
for (int i=0; i < words.size(); i++) {
cell = getCell(sheet, 2+i, 0);
setText(cell, (String) words.get(i));
}
}
}
以下是使用JExcelApi生成相同Excel文件的视图:
package excel;
// imports omitted for brevity
public class HomePage extends AbstractJExcelView {
protected void buildExcelDocument(Map model, WritableWorkbook wb,
HttpServletRequest request, HttpServletResponse response) throws Exception {
WritableSheet sheet = wb.createSheet("Spring", 0);
sheet.addCell(new Label(0, 0, "Spring-Excel test"));
List words = (List) model.get("wordList");
for (int i = 0; i < words.size(); i++) {
sheet.addCell(new Label(2+i, 0, (String) words.get(i)));
}
}
}
请注意API之间的差异。我们发现JExcelApi更加直观,而且JExcelApi的图像处理能力稍好一些。但是,使用JExcelApi时,大型Excel文件存在内存问题。
如果您现在修改控制器,使其xl作为view(return new ModelAndView("xl", map);)的名称 返回并再次运行您的应用程序,您应该发现当您请求与之前相同的页面时,将自动创建和下载Excel电子表格。
PDF视图
单词列表的PDF版本更简单。这次,该类扩展 org.springframework.web.servlet.view.document.AbstractPdfView并实现了buildPdfDocument()如下方法:
package pdf;
// imports omitted for brevity
public class PDFPage extends AbstractPdfView {
protected void buildPdfDocument(Map model, Document doc, PdfWriter writer,
HttpServletRequest req, HttpServletResponse resp) throws Exception {
List words = (List) model.get("wordList");
for (int i=0; i<words.size(); i++) {
doc.add( new Paragraph((String) words.get(i)));
}
}
}
再次修改控制器以返回pdf视图return new ModelAndView("pdf", map);,并在应用程序中重新加载URL。这次应该会出现一个PDF文档,列出模型映射中的每个单词。
1.10.9。杰克逊
JSON
在MappingJackson2JsonView使用杰克逊库的ObjectMapper渲染响应内容为JSON。默认情况下,模型映射的全部内容(除框架特定的类外)将被编码为JSON。对于需要过滤地图内容的情况,用户可以指定一组特定的模型属性以通过属性进行编码RenderedAttributes。该extractValueFromSingleKeyModel属性也可用于直接提取和序列化单键模型中的值,而不是模型属性的映射。
通过使用Jackson提供的注释,可以根据需要定制JSON映射。当需要进一步控制时,ObjectMapper可以通过该ObjectMapper属性注入一个自定义,以用于需要为特定类型提供自定义JSON序列化器/反序列化器的情况。
当请求具有名为jsonpor 的查询参数时,支持并自动启用JSONPcallback。JSONP查询参数名称可以通过jsonpParameterNames属性进行定制。
XML
在MappingJackson2XmlView使用 杰克逊XML扩展的XmlMapper 渲染响应内容为XML。如果模型包含多个条目,则应使用modelKeybean属性显式设置要序列化的对象。如果模型包含单个条目,它将自动序列化。
通过使用JAXB或Jackson提供的注释,可以根据需要定制XML映射。当需要进一步控制时,XmlMapper可以通过该ObjectMapper属性注入一个自定义,用于需要为特定类型提供自定义XML序列化器/反序列化器的情况。
1.10.10。XML
1.10.11。XSLT
XSLT是一种用于XML的转换语言,作为Web应用程序中的视图技术很受欢迎。如果您的应用程序自然处理XML,或者您的模型可以轻松转换为XML,那么XSLT可作为视图技术的不错选择。以下部分显示如何将XML文档生成为模型数据,并使用Spring Web MVC应用程序中的XSLT进行转换。
这个例子是一个普通的Spring应用程序,它创建一个单词列表 Controller并将它们添加到模型映射中。该地图与我们的XSLT视图的视图名称一起返回。有关Spring Web MVC 接口的详细信息, 请参阅带注释的控制器Controller。XSLT控制器将把单词列表转换为一个简单的XML文档,用于转换。
豆
配置是简单的Spring应用程序的标准配置。MVC配置必须定义一个XsltViewResolverbean和常规的MVC注释配置。
@EnableWebMvc
@ComponentScan
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public XsltViewResolver xsltViewResolver() {
XsltViewResolver viewResolver = new XsltViewResolver();
viewResolver.setPrefix("/WEB-INF/xsl/");
viewResolver.setSuffix(".xslt");
return viewResolver;
}
}
我们需要一个封装我们词汇生成逻辑的控制器。
调节器
控制器逻辑被封装在一个@Controller类中,处理器方法被定义为这样...
@Controller
public class XsltController {
@RequestMapping("/")
public String home(Model model) throws Exception {
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element root = document.createElement("wordList");
List<String> words = Arrays.asList("Hello", "Spring", "Framework");
for (String word : words) {
Element wordNode = document.createElement("word");
Text textNode = document.createTextNode(word);
wordNode.appendChild(textNode);
root.appendChild(wordNode);
}
model.addAttribute("wordList", root);
return "home";
}
}
到目前为止,我们只创建了一个DOM文档并将其添加到Model地图中。请注意,您也可以将XML文件作为一个文件加载Resource并使用它来代替自定义的DOM文档。
当然,有些软件包可以自动“控制”对象图形,但在Spring中,您可以完全灵活地根据您的选择以您的模型创建DOM。这可以防止XML的转换在模型数据的结构中扮演太重要角色,当使用工具来管理统一过程时这是一种危险。
接下来,XsltViewResolver将解析“主页”XSLT模板文件并将DOM文档合并到其中以生成我们的视图。
转型
最后,XsltViewResolver将解析“home”XSLT模板文件并将DOM文档合并到它中以生成我们的视图。如XsltViewResolver 配置所示,XSLT模板位于'WEB-INF/xsl'目录中的war文件中,并以"xslt"文件扩展名结尾。
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" omit-xml-declaration="yes"/>
<xsl:template match="/">
<html>
<head><title>Hello!</title></head>
<body>
<h1>My First Words</h1>
<ul>
<xsl:apply-templates/>
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="word">
<li><xsl:value-of select="."/></li>
</xsl:template>
</xsl:stylesheet>
这呈现为:
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello!</title>
</head>
<body>
<h1>My First Words</h1>
<ul>
<li>Hello</li>
<li>Spring</li>
<li>Framework</li>
</ul>
</body>
</html>
1.11。MVC配置
MVC Java配置和MVC XML命名空间提供适用于大多数应用程序的默认配置以及配置API来自定义它。
1.11.1。启用MVC配置
在Java配置中使用@EnableWebMvc注释:
@Configuration
@EnableWebMvc
public class WebConfig {
}
在XML中使用<mvc:annotation-driven>元素:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven/>
</beans>
The above registers a number of Spring MVC infrastructure beans also adapting to dependencies available on the classpath: e.g. payload converters for JSON, XML, etc.
1.11.2. MVC Config API
In Java config implement WebMvcConfigurer interface:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// Implement configuration methods...
}
In XML check attributes and sub-elements of <mvc:annotation-driven/>. You can view the Spring MVC XML schema or use the code completion feature of your IDE to discover what attributes and sub-elements are available.
1.11.3. Type conversion
By default formatters for Number and Date types are installed, including support for the @NumberFormat and @DateTimeFormatannotations. Full support for the Joda-Time formatting library is also installed if Joda-Time is present on the classpath.
In Java config, register custom formatters and converters:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
// ...
}
}
In XML, the same:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="org.example.MyConverter"/>
</set>
</property>
<property name="formatters">
<set>
<bean class="org.example.MyFormatter"/>
<bean class="org.example.MyAnnotationFormatterFactory"/>
</set>
</property>
<property name="formatterRegistrars">
<set>
<bean class="org.example.MyFormatterRegistrar"/>
</set>
</property>
</bean>
</beans>
|
请参阅FormatterRegistrar SPI 和 |
1.11.4。验证
默认情况下,如果Bean验证存在的类路径上-例如Hibernate验证,将LocalValidatorFactoryBean被注册为一个全球性的验证与使用@Valid,并Validated在控制器方法的参数。
在Java配置中,您可以自定义全局Validator实例:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public Validator getValidator(); {
// ...
}
}
在XML中,同样的:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven validator="globalValidator"/>
</beans>
请注意,您也可以Validator在本地注册:
@Controller
public class MyController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new FooValidator());
}
}
|
如果你需要在 |
1.11.5。拦截器
在Java配置中,注册拦截器应用于传入请求:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleChangeInterceptor());
registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
}
}
在XML中,同样的:
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/admin/**"/>
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/secure/*"/>
<bean class="org.example.SecurityInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
1.11.6。内容类型
您可以配置Spring MVC如何根据请求确定请求的媒体类型 - 例如Accept标题,URL路径扩展,查询参数等。
默认情况下,URL路径扩展首先检查-有json,xml,rss,并atom 注册为根据路径依赖已知扩展,并“接受”头第二检查。
在Java配置中,自定义请求的内容类型解析:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("json", MediaType.APPLICATION_JSON);
configurer.mediaType("xml", MediaType.APPLICATION_XML);
}
}
在XML中,同样的:
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="mediaTypes">
<value>
json=application/json
xml=application/xml
</value>
</property>
</bean>
1.11.7。消息转换器
如果您想替换Spring MVC创建的默认转换器,或者通过覆盖 如果您只是想定制它们或将其他转换器添加到默认转换器 ,HttpMessageConverter则可以在Java配置中实现 自定义。configureMessageConverters()extendMessageConverters()
下面是一个添加Jackson JSON和XML转换器的例子, ObjectMapper而不是默认的:
@Configuration
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
.modulesToInstall(new ParameterNamesModule());
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}
}
在这个例子中, Jackson2ObjectMapperBuilder 用于创建一个通用的配置,MappingJackson2HttpMessageConverter并且MappingJackson2XmlHttpMessageConverter启用缩进,自定义的日期格式和jackson-module-parameter-names的注册,这些 名称 增加了对访问参数名称的支持(Java 8中添加的功能)。
该构建器使用以下几种方法自定义Jackson的默认属性:
It also automatically registers the following well-known modules if they are detected on the classpath:
-
jackson-datatype-jdk7: support for Java 7 types like
java.nio.file.Path. -
jackson-datatype-joda: support for Joda-Time types.
-
jackson-datatype-jsr310: support for Java 8 Date & Time API types.
-
jackson-datatype-jdk8: support for other Java 8 types like
Optional.
|
Enabling indentation with Jackson XML support requires |
Other interesting Jackson modules are available:
-
jackson-datatype-money: support for
javax.moneytypes (unofficial module) -
jackson-datatype-hibernate: support for Hibernate specific types and properties (including lazy-loading aspects)
It is also possible to do the same in XML:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
<bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
<property name="objectMapper" ref="xmlMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
p:indentOutput="true"
p:simpleDateFormat="yyyy-MM-dd"
p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>
<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>
1.11.8. View Controllers
This is a shortcut for defining a ParameterizableViewController that immediately forwards to a view when invoked. Use it in static cases when there is no Java controller logic to execute before the view generates the response.
An example of forwarding a request for "/" to a view called "home" in Java:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
}
And the same in XML use the <mvc:view-controller> element:
<mvc:view-controller path="/" view-name="home"/>
1.11.9. View Resolvers
The MVC config simplifies the registration of view resolvers.
The following is a Java config example that configures content negotiation view resolution using JSP and Jackson as a default View for JSON rendering:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.jsp();
}
}
And the same in XML:
<mvc:view-resolvers>
<mvc:content-negotiation>
<mvc:default-views>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</mvc:default-views>
</mvc:content-negotiation>
<mvc:jsp/>
</mvc:view-resolvers>
Note however that FreeMarker, Tiles, Groovy Markup and script templates also require configuration of the underlying view technology.
MVC命名空间提供了专用元素。例如FreeMarker:
<mvc:view-resolvers>
<mvc:content-negotiation>
<mvc:default-views>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</mvc:default-views>
</mvc:content-negotiation>
<mvc:freemarker cache="false"/>
</mvc:view-resolvers>
<mvc:freemarker-configurer>
<mvc:template-loader-path location="/freemarker"/>
</mvc:freemarker-configurer>
在Java配置中,只需添加相应的“配置器”bean:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.freeMarker().cache(false);
}
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/freemarker");
return configurer;
}
}
1.11.10。静态资源
该选项提供了一种便捷的方式来从基于资源的位置列表中提供静态资源 。
在下面的示例中,如果以一个请求开始"/resources",则使用相对路径来查找并提供相对于Web应用程序根目录下或“类路径”下的“/ public”的静态资源"/static"。这些资源将在未来1年内到期,以确保最大限度地利用浏览器缓存并减少浏览器发出的HTTP请求。的Last-Modified头部也判断,如果存在一个304 被返回的状态代码。
在Java配置中:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public", "classpath:/static/")
.setCachePeriod(31556926);
}
}
在XML中:
<mvc:resources mapping="/resources/**"
location="/public, classpath:/static/"
cache-period="31556926" />
另请参阅 HTTP缓存对静态资源的支持。
资源处理程序还支持一系列 ResourceResolver和 ResourceTransformer。可用于创建工具链以处理优化的资源。
在VersionResourceResolver可用于根据MD5哈希从内容,固定的应用程序版本,或其它计算资源版本的URL。一ContentVersionStrategy(MD5哈希)是一个很好的选择有一些明显的例外,如与模块加载程序使用的JavaScript资源。
例如在Java配置中;
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public/")
.resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
}
在XML中,同样的:
<mvc:resources mapping="/resources/**" location="/public/">
<mvc:resource-chain>
<mvc:resource-cache/>
<mvc:resolvers>
<mvc:version-resolver>
<mvc:content-version-strategy patterns="/**"/>
</mvc:version-resolver>
</mvc:resolvers>
</mvc:resource-chain>
</mvc:resources>
您可以使用ResourceUrlProvider重写URL并应用完整的解析器和变换器链 - 例如插入版本。MVC配置提供了一个ResourceUrlProvider bean,因此它可以注入到其他人中。您也可以使用ResourceUrlEncodingFilterThymeleaf,JSP,FreeMarker和其他依赖URL标记的重写方法进行重写 HttpServletResponse#encodeURL。
WebJars也支持通过WebJarsResourceResolver 并"org.webjars:webjars-locator"在类路径中存在时自动注册。解析器可以重写URL以包括JAR的版本,也可以匹配传入的URL,而不版本-例如"/jquery/jquery.min.js"到 "/jquery/1.2.0/jquery.min.js"。
1.11.11。默认Servlet
这允许将映射DispatcherServlet到“/”(因此覆盖容器默认Servlet的映射),同时仍允许静态资源请求由容器的默认Servlet处理。它DefaultServletHttpRequestHandler使用“/ **”的URL映射和相对于其他URL映射的最低优先级来配置一个 。
该处理程序将把所有请求转发给默认的Servlet。因此,以所有其他网址的顺序保持最后是非常重要的HandlerMappings。这会是这样的,如果你使用<mvc:annotation-driven>或者相反,如果你设置你自己定制的HandlerMapping情况下,一定要设置其order属性为比的下一个值DefaultServletHttpRequestHandler,这是Integer.MAX_VALUE。
要使用默认设置启用该功能,请使用:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
或者在XML中:
<mvc:default-servlet-handler/>
覆盖“/”Servlet映射的注意事项是,RequestDispatcher默认的Servlet必须通过名称而不是路径来检索。该DefaultServletHttpRequestHandler会尝试自动检测在启动时容器中的默认的Servlet,使用大多数主要的Servlet容器(包括软件Tomcat,Jetty的GlassFish,JBoss和树脂中,WebLogic和WebSphere)已知名称的列表。如果默认Servlet已用不同名称自定义配置,或者在缺省Servlet名称未知的情况下使用了不同的Servlet容器,则必须显式提供默认Servlet的名称,如下例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable("myCustomDefaultServlet");
}
}
或者在XML中:
<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>
1.11.12。路径匹配
这允许自定义与URL匹配和URL处理相关的选项。有关各个选项的详细信息,请查看 PathMatchConfigurer API。
Java配置中的示例:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer
.setUseSuffixPatternMatch(true)
.setUseTrailingSlashMatch(false)
.setUseRegisteredSuffixPatternMatch(true)
.setPathMatcher(antPathMatcher())
.setUrlPathHelper(urlPathHelper());
}
@Bean
public UrlPathHelper urlPathHelper() {
//...
}
@Bean
public PathMatcher antPathMatcher() {
//...
}
}
在XML中,同样的:
<mvc:annotation-driven>
<mvc:path-matching
suffix-pattern="true"
trailing-slash="false"
registered-suffixes-only="true"
path-helper="pathHelper"
path-matcher="pathMatcher"/>
</mvc:annotation-driven>
<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>
13年1月11日。高级Java配置
@EnableWebMvc进口DelegatingWebMvcConfiguration(1)提供默认Spring配置为Spring MVC应用和(2)检测和代表到WebMvcConfigurer的自定义该配置。
对于高级模式,@EnableWebMvc直接删除并扩展 DelegatingWebMvcConfiguration而不是实现WebMvcConfigurer:
@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {
// ...
}
您可以保留现有方法,WebConfig但现在也可以覆盖基类中的bean声明,并且您仍然可以WebMvcConfigurer在类路径中包含任何数量的其他方法。
1.11.14。高级XML配置
MVC命名空间没有高级模式。如果你需要定制一个你不能改变的bean的属性,你可以使用BeanPostProcessorSpring 的生命周期钩子ApplicationContext:
@Component
public class MyPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
// ...
}
}
请注意,MyPostProcessor需要在XML中显式声明为bean或通过<component-scan/>声明检测到。
1.12。HTTP / 2
需要Servlet 4容器来支持HTTP / 2,并且Spring Framework 5与Servlet API 4兼容。从编程模型的角度来看,没有什么具体的应用程序需要做。但是有一些与服务器配置相关的考虑事项 有关更多详细信息,请查看 HTTP / 2 wiki页面。
Servlet API确实公开了一个与HTTP / 2相关的构造。该 javax.servlet.http.PushBuilder罐用于积极推动资源向客户和它支持的方法参数到@RequestMapping方法。
2. REST客户端
本节介绍用于客户端访问REST端点的选项。
2.1。RestTemplate
RestTemplate是遵循类似的方法在Spring框架的其他模板类(如原春REST客户端JdbcTemplate,JmsTemplate通过提供的参数化方法的列表来执行HTTP请求,等等)。
RestTemplate有一个同步API并依赖于阻塞I / O。对于低并发性的客户端场景,这是可以的。在服务器环境中或编排远程调用序列时,更喜欢使用WebClient提供更高效的执行模型,包括对流式传输的无缝支持。
请参阅RestTemplate了解更多关于使用 RestTemplate。
2.2。Web客户端
WebClient是一个被动客户端,它提供了一个替代方案RestTemplate。它公开了一个功能流畅的API,并依赖于非阻塞I / O,这使得它能够更高效地支持高并发(即使用少量线程) RestTemplate。WebClient非常适合流式场景。
请参阅WebClient获取更多关于使用WebClient。
3.测试
本节总结了spring-testSpring MVC应用程序的可用选项。
Servlet API Mocks
用于单元测试控制器,过滤器和其他Web组件的Servlet API合约的模拟实现。有关更多详细信息,请参阅Servlet API模拟对象。
TestContext框架
支持在JUnit和TestNG测试中加载Spring配置,包括跨测试方法高效缓存加载的配置,并支持WebApplicationContext使用a 加载配置MockServletContext。有关更多详细信息,请参阅TestContext Framework。
Spring MVC测试
一个框架,也被称为MockMvc用于测试注释的控制器DispatcherServlet,即通过 支持注释并使用Spring MVC基础结构完成,但没有HTTP服务器。有关更多详细信息,请参阅 Spring MVC测试。
客户端REST
spring-test提供了一个MockRestServiceServer可用作模拟服务器的测试客户端代码的内部使用RestTemplate。有关更多详细信息,请参阅客户端REST测试。
WebTestClient
WebTestClient是为测试WebFlux应用程序而构建的,但它也可以用于端到端集成测试,通过HTTP连接到任何服务器。它是一个非阻塞的被动客户端,非常适合测试异步和流式场景。
4. WebSockets
参考文档的这一部分包含对Servlet堆栈,WebSocket消息传递的支持,其中包括原始WebSocket交互,通过SockJS进行的WebSocket仿真以及通过STOMP作为WebSocket上的子协议的pub-sub消息传递。
4.1。介绍
WebSocket协议RFC 6455提供了一种标准化的方式,通过单个TCP连接在客户端和服务器之间建立全双工,双向通信通道。它是来自HTTP的一种不同的TCP协议,但被设计为通过HTTP工作,使用端口80和443并允许重新使用现有的防火墙规则。
WebSocket交互始于使用HTTP "Upgrade"标头进行升级的HTTP请求,或者在此情况下切换到WebSocket协议:
GET / spring-websocket-portfolio / portfolio HTTP / 1.1 主机:localhost:8080 升级:websocket 连接:升级 Sec-WebSocket-Key:Uc9l9TMkWGbHFD2qnFHltg == Sec-WebSocket协议:v10.stomp,v11.stomp Sec-WebSocket-Version:13 起源:http:// localhost:8080
与通常的200状态代码不同,具有WebSocket支持的服务器将返回:
HTTP / 1.1 101交换协议 升级:websocket 连接:升级 Sec-WebSocket-Accept:1qVdfYHU9hPOl4JYYNXF623Gzn0 = Sec-WebSocket协议:v10.stomp
握手成功后,HTTP升级请求的TCP套接字将保持打开状态,以便客户端和服务器继续发送和接收消息。
关于WebSockets如何工作的完整介绍超出了本文的范围。请阅读RFC 6455,HTML5的WebSocket章节,或者Web上的许多介绍和教程之一。
请注意,如果WebSocket服务器在Web服务器(例如nginx)后面运行,则可能需要对其进行配置,以将WebSocket升级请求传递到WebSocket服务器。同样,如果应用程序在云环境中运行,请检查云提供程序与WebSocket支持相关的说明。
4.1.1。HTTP vs WebSocket
尽管WebSocket被设计为与HTTP兼容并以HTTP请求开始,但了解这两种协议导致非常不同的体系结构和应用程序编程模型是很重要的。
In HTTP and REST, an application is modeled as many URLs. To interact with the application clients access those URLs, request-response style. Servers route requests to the appropriate handler based on the HTTP URL, method, and headers.
By contrast in WebSockets there is usually just one URL for the initial connect and subsequently all application messages flow on that same TCP connection. This points to an entirely different asynchronous, event-driven, messaging architecture.
WebSocket is also a low-level transport protocol which unlike HTTP does not prescribe any semantics to the content of messages. That means there is no way to route or process a message unless client and server agree on message semantics.
WebSocket clients and servers can negotiate the use of a higher-level, messaging protocol (e.g. STOMP), via the "Sec-WebSocket-Protocol" header on the HTTP handshake request, or in the absence of that they need to come up with their own conventions.
4.1.2. When to use it?
WebSockets can make a web page dynamic and interactive. However in many cases a combination of Ajax and HTTP streaming and/or long polling could provide a simple and effective solution.
For example news, mail, and social feeds need to update dynamically but it may be perfectly okay to do so every few minutes. Collaboration, games, and financial apps on the other hand need to be much closer to real time.
单独的延迟并不是决定性因素。如果消息量相对较低(例如监视网络故障),HTTP流或轮询可能提供有效的解决方案。它是低延迟,高频率和高音量的组合,是WebSocket使用的最佳案例。
请记住,在互联网上,限制性代理无法控制,可能会阻止WebSocket交互,因为它们没有配置为传递 Upgrade标头,或者因为它们关闭长时间连接而显得空闲?这意味着在防火墙内部使用WebSocket用于内部应用程序是一个比直接面向公众的应用程序更直接的决定。
4.2。WebSocket API
Spring框架提供了一个WebSocket API,可用于编写处理WebSocket消息的客户端和服务器端应用程序。
4.2.1。WebSocketHandler
创建的WebSocket服务器是为实现简单WebSocketHandler或更可能要么延长TextWebSocketHandler或BinaryWebSocketHandler:
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;
public class MyHandler extends TextWebSocketHandler {
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
// ...
}
}
有专门的WebSocket Java-config和XML命名空间支持将上述WebSocket处理程序映射到特定的URL:
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler");
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
等同的XML配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>
以上内容适用于Spring MVC应用程序,并应包含在DispatcherServlet的配置中。但是,Spring的WebSocket支持不依赖于Spring MVC。WebSocketHandler 在WebSocketHttpRequestHandler的帮助下将其集成到其他HTTP服务环境中 相对简单。
4.2.2。WebSocket握手
The easiest way to customize the initial HTTP WebSocket handshake request is through a HandshakeInterceptor, which exposes "before" and "after" the handshake methods. Such an interceptor can be used to preclude the handshake or to make any attributes available to the WebSocketSession. For example, there is a built-in interceptor for passing HTTP session attributes to the WebSocket session:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyHandler(), "/myHandler")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
}
And the XML configuration equivalent:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
<websocket:handshake-interceptors>
<bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
</websocket:handshake-interceptors>
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>
更高级的选项是扩展DefaultHandshakeHandler执行WebSocket握手步骤,包括验证客户端来源,协商子协议等。如果需要配置自定义RequestUpgradeStrategy以适应WebSocket服务器引擎和尚不支持的版本(另请参阅部署了解有关此主题的更多信息),应用程序可能还需要使用此选项。Java-config和XML命名空间都可以配置自定义 HandshakeHandler。
|
Spring提供了一个 |
4.2.3。部署
Spring WebSocket API很容易集成到一个Spring MVC应用程序中,该应用程序DispatcherServlet既提供HTTP WebSocket握手,也提供其他HTTP请求。通过调用也可以轻松地集成到其他HTTP处理场景中WebSocketHttpRequestHandler。这很方便,易于理解。但是,JSR-356运行时需要特别注意。
Java WebSocket API(JSR-356)提供了两种部署机制。第一个涉及启动时的Servlet容器类路径扫描(Servlet 3功能); 另一个是在Servlet容器初始化时使用的注册API。这两种机制都不能使用单个“前端控制器”来处理所有HTTP处理 - 包括WebSocket握手和所有其他HTTP请求 - 例如Spring MVC's DispatcherServlet。
这是JSR-356的一个重要限制,Spring的WebSocket支持RequestUpgradeStrategy即使在JSR-356运行时运行时也支持服务器特定的地址。Tomcat,Jetty,GlassFish,WebLogic,WebSphere和Undertow(以及WildFly)目前都存在这样的策略。
|
已经创建了一个请求来克服Java WebSocket API中的上述限制,并且可以在WEBSOCKET_SPEC-211中进行跟踪 。Tomcat,Undertow和WebSphere提供了他们自己的API替代品,这使得它成为可能,而且Jetty也是可能的。我们希望更多的服务器能够遵循相同的原则。 |
第二个考虑因素是具有JSR-356支持的Servlet容器有望执行ServletContainerInitializer(SCI)扫描,可能会显着降低应用程序的启动速度。如果在升级到具有JSR-356支持的Servlet容器版本后观察到重大影响,则应该可以通过使用以下内容中的<absolute-ordering />元素来选择性地启用或禁用Web碎片(和SCI扫描)web.xml:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<absolute-ordering/>
</web-app>
然后,您可以选择性地启用Web名称片段,例如Spring自己 SpringServletContainerInitializer提供的Servlet 3 Java初始化API支持(如果需要):
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<absolute-ordering>
<name>spring_web</name>
</absolute-ordering>
</web-app>
4.2.4。服务器配置
每个底层WebSocket引擎都公开可控制运行时特性的配置属性,例如消息缓冲区大小,空闲超时等等。
对于Tomcat,WildFly和GlassFish将添加ServletServerContainerFactoryBean到您的WebSocket Java配置中:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
}
或WebSocket XML命名空间:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<bean class="org.springframework...ServletServerContainerFactoryBean">
<property name="maxTextMessageBufferSize" value="8192"/>
<property name="maxBinaryMessageBufferSize" value="8192"/>
</bean>
</beans>
|
对于客户端WebSocket配置,您应该使用 |
对于Jetty,您需要提供预配置的Jetty,WebSocketServerFactory并DefaultHandshakeHandler通过WebSocket Java配置将其插入到Spring中:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(echoWebSocketHandler(),
"/echo").setHandshakeHandler(handshakeHandler());
}
@Bean
public DefaultHandshakeHandler handshakeHandler() {
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
policy.setInputBufferSize(8192);
policy.setIdleTimeout(600000);
return new DefaultHandshakeHandler(
new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
}
}
或WebSocket XML命名空间:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/echo" handler="echoHandler"/>
<websocket:handshake-handler ref="handshakeHandler"/>
</websocket:handlers>
<bean id="handshakeHandler" class="org.springframework...DefaultHandshakeHandler">
<constructor-arg ref="upgradeStrategy"/>
</bean>
<bean id="upgradeStrategy" class="org.springframework...JettyRequestUpgradeStrategy">
<constructor-arg ref="serverFactory"/>
</bean>
<bean id="serverFactory" class="org.eclipse.jetty...WebSocketServerFactory">
<constructor-arg>
<bean class="org.eclipse.jetty...WebSocketPolicy">
<constructor-arg value="SERVER"/>
<property name="inputBufferSize" value="8092"/>
<property name="idleTimeout" value="600000"/>
</bean>
</constructor-arg>
</bean>
</beans>
4.2.5。允许的来源
从Spring Framework 4.1.5开始,WebSocket和SockJS的默认行为是只接受相同的原始请求。也可以允许所有或指定的来源列表。此检查主要是为浏览器客户端设计的。没有什么能够阻止其他类型的客户端修改Origin头部值(更多细节参见 RFC 6454:Web Origin Concept)。
这3种可能的行为是:
-
只允许相同的原始请求(默认):在此模式下,当启用SockJS时,Iframe HTTP响应头
X-Frame-Options设置为SAMEORIGIN,并且JSONP传输被禁用,因为它不允许检查请求的来源。因此,启用此模式时不支持IE6和IE7。 -
允许指定的来源列表:每个提供的允许来源必须以
http://或开头https://。在此模式下,启用SockJS时,禁用基于IFrame和JSONP的传输。因此,启用此模式时,不支持IE6至IE9。 -
允许所有来源:启用此模式,您应该提供
*作为允许的原始值。在此模式下,所有传输都可用。
WebSocket和SockJS允许的来源可以按如下所示进行配置:
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("http://mydomain.com");
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
等同的XML配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers allowed-origins="http://mydomain.com">
<websocket:mapping path="/myHandler" handler="myHandler" />
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>
4.3。SockJS后备
在公共互联网上,您控制范围之外的限制性代理可能会阻止WebSocket交互,因为它们未配置为传递Upgrade标头,或者因为它们关闭了闲置的长期连接。
这个问题的解决方案是WebSocket仿真,即尝试首先使用WebSocket,然后回退基于HTTP的技术,模拟WebSocket交互并公开相同的应用程序级API。
在Servlet堆栈上,Spring Framework为SockJS协议提供服务器(也是客户端)支持。
4.3.1。概观
SockJS的目标是让应用程序使用WebSocket API,但在运行时必要时回退到非WebSocket替代方案,即无需更改应用程序代码。
SockJS包括:
-
该SockJS JavaScript客户端 -在浏览器中使用客户端库。
-
SockJS服务器实现包括Spring Framework
spring-websocket模块中的一个。 -
从4.1
spring-websocket还提供了一个SockJS Java客户端。
SockJS被设计用于浏览器。它使用各种技术支持各种浏览器版本。有关SockJS传输类型和浏览器的完整列表,请参阅 SockJS客户端页面。传输分为3大类:WebSocket,HTTP Streaming和HTTP Long Polling。有关这些类别的概述,请参阅 此博客文章。
SockJS客户端首先发送"GET /info"从服务器获取基本信息。之后,它必须决定使用什么交通工具。如果可能的话使用WebSocket。如果没有,在大多数浏览器中至少有一个HTTP流选项,如果不是,则使用HTTP(长)轮询。
所有传输请求都具有以下URL结构:
HTTP://主机:端口/对myApp / myEndpoint / {服务器ID} / {会话ID} / {}运输
-
{server-id}- 用于在群集中路由请求,但不用于其他情况。 -
{session-id}- 关联属于SockJS会话的HTTP请求。 -
{transport}- 表示传输类型,例如“websocket”,“xhr-streaming”等。
WebSocket传输只需要一个HTTP请求来执行WebSocket握手。之后的所有消息都在该套接字上交换。
HTTP传输需要更多的请求。例如,Ajax / XHR流依赖于对服务器到客户端消息的一个长期运行请求以及对客户端到服务器消息的附加HTTP POST请求。长查询是相似的,除了它在每个服务器到客户端发送之后结束当前请求。
SockJS增加了最小的消息框架。例如,服务器最初发送字母o(“打开”帧),消息作为[“message1”,“message2”](JSON编码阵列),字母h(“心跳”帧)发送,如果没有消息流默认25秒,字母c(“关闭”框)关闭会话。
要了解更多信息,请在浏览器中运行示例并观看HTTP请求。SockJS客户端允许修复传输列表,因此可以一次查看每个传输。SockJS客户端还提供了一个调试标志,可在浏览器控制台中启用有用的消息。在服务器端启用 TRACE日志记录org.springframework.web.socket。有关更多详细信息,请参阅SockJS协议 叙述测试。
4.3.2。启用SockJS
通过Java配置,SockJS很容易启用:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").withSockJS();
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
和XML配置等效:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
<websocket:sockjs/>
</websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
</beans>
以上内容适用于Spring MVC应用程序,并应包含在DispatcherServlet的配置中。但是,Spring的WebSocket和SockJS支持不依赖于Spring MVC。在SockJsHttpRequestHandler的帮助下集成到其他HTTP服务环境中相对简单 。
在浏览器端,应用程序可以使用模拟W3C WebSocket API 的 sockjs-client(版本1.0.x),并与服务器通信,根据运行的浏览器选择最佳传输选项。查看 sockjs-client页面并浏览器支持的传输类型列表。客户端还提供了多个配置选项,例如,指定要包含哪些传输。
4.3.3。IE 8,9
一段时间以来,Internet Explorer 8和9仍然很常见。他们是拥有SockJS的关键原因。本节介绍有关在这些浏览器中运行的重要注意事项。
SockJS客户端通过微软的XDomainRequest支持IE 8和9中的Ajax / XHR流媒体 。这适用于跨域,但不支持发送cookie。Cookie对于Java应用程序来说非常重要。然而,由于SockJS客户端可以用于许多服务器类型(不仅仅是Java),它需要知道cookies是否重要。如果是这样,SockJS客户端更倾向于使用Ajax / XHR进行流式传输,否则它依赖基于iframe的技术。
最先"/info"从SockJS客户端请求是针对可能影响客户的传输选择信息的请求。其中一个细节是服务器应用程序是否依赖于cookie,例如用于身份验证或使用粘性会话进行群集。Spring的SockJS支持包含一个名为的属性sessionCookieNeeded。它是默认启用的,因为大多数Java应用程序都依赖于JSESSIONID cookie。如果您的应用程序不需要它,您可以关闭此选项,并且SockJS客户端应该xdr-streaming在IE 8和9中选择。
如果你使用基于iframe的运输,并且在任何情况下,这是好事,知道浏览器可以指示通过设置HTTP响应标题来阻止特定网页上的使用iframe X-Frame-Options来DENY, SAMEORIGIN或ALLOW-FROM <origin>。这是用来防止 点击劫持。
|
Spring Security 3.2+支持设置 参见7.1节。 有关如何配置 |
如果您的应用程序添加了X-Frame-Options响应标头(因为它应该!)并依赖于基于iframe的传输,您需要将标头值设置为 SAMEORIGIN或ALLOW-FROM <origin>。除此之外,Spring SockJS支持还需要知道SockJS客户端的位置,因为它是从iframe加载的。默认情况下,iframe被设置为从CDN位置下载SockJS客户端。将此选项配置为与应用程序来源相同的URL是个好主意。
在Java配置中,这可以按照如下所示完成。XML名称空间通过<websocket:sockjs>元素提供了一个类似的选项:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").withSockJS()
.setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js");
}
// ...
}
|
在初始开发期间,请启用SockJS客户端 |
4.3.4。心跳
SockJS协议要求服务器发送心跳消息以阻止代理挂断连接。Spring SockJS配置有一个属性heartbeatTime,可以用来定制频率。默认情况下,假设没有其他消息在该连接上发送,则在25秒后发送心跳。这25秒的值符合以下 IETF关于公共互联网应用的建议。
|
当通过WebSocket / SockJS使用STOMP时,如果STOMP客户端和服务器协商要交换的心跳,则SockJS心跳将被禁用。 |
Spring SockJS支持还允许将其配置TaskScheduler为用于安排心跳任务。任务调度程序由具有基于可用处理器数量的默认设置的线程池支持。应用程序应根据其特定需求考虑自定义设置。
4.3.5。客户端断开连接
HTTP流和HTTP长轮询SockJS传输需要连接保持比平常更长的打开时间。有关这些技术的概述,请参阅 此博客文章。
在Servlet容器中,这是通过Servlet 3异步支持来完成的,该异步支持允许退出Servlet容器线程处理请求并继续写入来自另一个线程的响应。
一个特定的问题是Servlet API不会为已经消失的客户端提供通知,请参阅SERVLET_SPEC-44。但是,随后尝试写入响应时,Servlet容器会引发异常。由于Spring的SockJS服务支持服务器发送的心跳(默认情况下每25秒),这意味着如果在更短的时间内发送消息,通常会在该时间段或更早的时间内检测到客户端断开连接。
|
因此,网络IO故障可能会发生,原因很简单,因为客户端已断开连接,这可能会在日志中填充不必要的堆栈跟踪。Spring尽最大努力来识别代表客户端断开连接(特定于每个服务器)的这种网络故障,并使用在中 |
4.3.6。SockJS和CORS
如果允许跨源请求(请参阅允许的源),SockJS协议使用CORS在XHR流式传输和轮询传输中支持跨域。因此CORS标题会自动添加,除非检测到响应中存在CORS标题。因此,如果应用程序已经配置为提供CORS支持(例如通过Servlet过滤器),那么Spring的SockJsService将跳过这一部分。
也可以通过suppressCorsSpring的SockJsService中的属性来禁用这些CORS头文件 。
以下是SockJS预期的标题和值的列表:
-
"Access-Control-Allow-Origin"- 从“Origin”请求头的值初始化。 -
"Access-Control-Allow-Credentials"- 始终设置为true。 -
"Access-Control-Request-Headers"- 从等价请求头中的值初始化。 -
"Access-Control-Allow-Methods"- 传输支持的HTTP方法(请参阅TransportType枚举)。 -
"Access-Control-Max-Age"- 设置为31536000(1年)。
对于确切的执行看到addCorsHeaders的AbstractSockJsService还有TransportType在源代码中枚举。
或者,如果CORS配置允许它考虑排除带有SockJS端点前缀的URL,从而让Spring SockJsService处理它。
4.3.7。SockJsClient
提供SockJS Java客户端,以便在不使用浏览器的情况下连接到远程SockJS端点。当需要在两个服务器之间通过公共网络进行双向通信时,即网络代理可能排除使用WebSocket协议时,这可能特别有用。SockJS Java客户端对于测试目的也非常有用,例如模拟大量的并发用户。
SockJS Java客户端支持“websocket”,“xhr-streaming”和“xhr-polling”传输。其余的仅适用于浏览器。
的WebSocketTransport可被配置成与:
-
StandardWebSocketClient在JSR-356运行时 -
JettyWebSocketClient使用Jetty 9+本机WebSocket API -
任何Spring的实现
WebSocketClient
根据XhrTransport定义,既然支持“xhr-streaming”和“xhr-polling”,从客户角度来看,除了用于连接服务器的URL之外,没有任何区别。目前有两种实现方式:
-
RestTemplateXhrTransport使用Spring的RestTemplateHTTP请求。 -
JettyXhrTransport使用Jetty的HttpClientHTTP请求。
下面的示例显示了如何创建SockJS客户端并连接到SockJS端点:
List<Transport> transports = new ArrayList<>(2);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
transports.add(new RestTemplateXhrTransport());
SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.doHandshake(new MyWebSocketHandler(), "ws://example.com:8080/sockjs");
|
SockJS使用JSON格式的数组来处理消息。默认情况下使用Jackson 2,并且需要位于类路径中。或者,您可以配置自定义实现 |
要使用SockJsClient模拟大量的并发用户,您需要配置底层HTTP客户端(用于XHR传输)以允许足够数量的连接和线程。以Jetty为例:
HttpClient jettyHttpClient = new HttpClient();
jettyHttpClient.setMaxConnectionsPerDestination(1000);
jettyHttpClient.setExecutor(new QueuedThreadPool(1000));
请考虑自定义这些服务器端SockJS相关属性(有关详细信息,请参阅Javadoc):
@Configuration
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/sockjs").withSockJS()
.setStreamBytesLimit(512 * 1024)
.setHttpMessageCacheSize(1000)
.setDisconnectDelay(30 * 1000);
}
// ...
}
4.4。STOMP
WebSocket协议定义了两种类型的消息,文本和二进制,但其内容未定义。为客户端和服务器定义了一种协商子协议的机制 - 即更高级别的消息传递协议,用于在WebSocket之上定义每种消息可以发送哪种消息,每种消息的格式和内容是什么等等上。子协议的使用是可选的,但是客户端和服务器需要就定义消息内容的一些协议达成一致。
4.4.1。概观
STOMP是一种简单的面向文本的消息传递协议,最初是为脚本语言(如Ruby,Python和Perl)创建的,用于连接企业消息代理。它旨在解决常用消息传递模式的最小子集。STOMP可用于任何可靠的双向流媒体网络协议,如TCP和WebSocket。尽管STOMP是一种面向文本的协议,但消息负载可以是文本或二进制。
STOMP是一个基于帧的协议,其帧在HTTP上建模。STOMP框架的结构:
命令 头1:VALUE1 标题2:VALUE2 车身^ @
客户可以使用SEND或SUBSCRIBE命令发送或订阅消息以及描述消息的内容和由谁来接收消息的“目标”头。这使得一个简单的发布 - 订阅机制可以用来通过代理发送消息到其他连接的客户端,或者发送消息到服务器来请求执行一些工作。
在使用Spring的STOMP支持时,Spring WebSocket应用程序充当客户端的STOMP代理。消息被路由到@Controller消息处理方法或简单的内存代理,用于跟踪订阅并向订阅用户广播消息。您还可以配置Spring以与专用的STOMP代理(例如RabbitMQ,ActiveMQ等)一起工作,以实现消息的实际广播。在这种情况下,Spring维护与代理的TCP连接,将消息转发给它,并将消息从它传递到连接的WebSocket客户端。因此,Spring Web应用程序可以依靠统一的基于HTTP的安全性,通用验证以及熟悉的编程模型消息处理工作。
这里是客户订阅接收股票报价的例子,服务器可以周期性地发送股票报价,例如通过计划任务通过SimpMessagingTemplate经纪人发送消息:
订阅 ID:分1 目的地:/topic/price.stock.* ^ @
以下是客户端发送交易请求的示例,服务器可以通过@MessageMapping方法处理,稍后在执行后向客户端广播交易确认消息和详细信息:
发送 目的地:/队列/贸易 内容类型:application / JSON 内容长度:44 { “动作”: “买入”, “股票”: “MMM”, “股份”,44} ^ @
目的地的含义在STOMP规范中有意不透明。它可以是任何字符串,完全取决于STOMP服务器来定义它们支持的目的地的语义和语法。然而,对于目的地来说,这是非常常见的路径类字符串,其"/topic/.."意味着发布 - 订阅(一对多)并且"/queue/"意味着点对点(一对一)的消息交换。
STOMP服务器可以使用MESSAGE命令向所有用户广播消息。以下是向订阅客户端发送股票报价的服务器示例:
信息 消息ID:nxahklf6-1 订阅:分1 目的地:/topic/price.stock.MMM { “股票”: “MMM”, “价格”:129.45} ^ @
知道服务器不能发送未经请求的消息很重要。所有来自服务器的消息都必须响应特定的客户端订阅,并且服务器消息的“subscription-id”头必须与客户端订阅的“id”头相匹配。
以上概述旨在提供对STOMP协议的最基本的了解。建议全面查看协议 规范。
4.4.2。优点
使用STOMP作为子协议使Spring Framework和Spring Security能够提供更丰富的编程模型,而不是使用原始WebSockets。关于HTTP与原始TCP的关系以及Spring MVC和其他Web框架如何提供丰富的功能都可以做到这一点。以下是一些好处:
-
不需要发明自定义消息协议和消息格式。
-
STOMP客户端可用,包括 Spring框架中的Java客户端。
-
消息代理(如RabbitMQ,ActiveMQ等)可以用于(可选)管理订阅和广播消息。
-
应用程序逻辑可以
@Controller根据STOMP目标报头与任何数量的路由到它们的消息进行组织,也可以用WebSocketHandler一个给定的连接处理原始WebSocket消息。 -
使用Spring Security来保护基于STOMP目标和消息类型的消息。
4.4.3。启用STOMP
STOMP在WebSocket的支持是可用的spring-messaging和 spring-websocket模块。一旦你有这些依赖关系,你可以通过WebSocket和SockJS Fallback公开一个STOMP端点,如下所示:
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/app");
config.enableSimpleBroker("/topic", "/queue");
}
}
"/portfolio" 是WebSocket(或SockJS)客户端需要连接到WebSocket握手的端点的HTTP URL。 |
|
目标标题开头的STOMP消息将"/app"被路由到 类中的@MessageMapping方法@Controller。 |
|
| 使用内置的消息代理进行订阅和广播; 将目标标题以“/ topic”或“/ queue”开头的消息路由到代理。 |
XML中的相同配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker application-destination-prefix="/app">
<websocket:stomp-endpoint path="/portfolio">
<websocket:sockjs/>
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/topic, /queue"/>
</websocket:message-broker>
</beans>
|
对于内置的简单代理,“/ topic”和“/ queue”前缀没有任何特殊含义。它们仅仅是一个区分pub-sub和点对点消息传递的约定(即许多订户与一个消费者)。在使用外部代理时,请检查代理的STOMP页面以了解它支持哪种类型的STOMP目标和前缀。 |
要从浏览器进行连接,对于SockJS,您可以使用 sockjs-client。对于STOMP,许多应用程序使用了功能完备的jmesnil / stomp-websocket库(也称为stomp.js),并且已经在生产中使用了多年,但不再进行维护。目前, JSteunou / webstomp-client是该库最积极的维护和发展的后继者,下面的示例代码基于它:
var socket = new SockJS("/spring-websocket-portfolio/portfolio");
var stompClient = webstomp.over(socket);
stompClient.connect({}, function(frame) {
}
或者,如果通过WebSocket连接(没有SockJS):
var socket = new WebSocket("/spring-websocket-portfolio/portfolio");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
}
有关更多示例代码,请参阅:
-
股票投资组合样本申请
4.4.4。消息流
一旦暴露了STOMP端点,Spring应用程序将成为连接客户端的STOMP代理。本节介绍服务器端的消息流。
该spring-messaging模块包含源自Spring Integration的消息传递应用程序的基础支持,后来被提取并合并到Spring Framework中,以便在许多Spring项目和应用程序场景中广泛使用 。以下是一些可用的消息抽象的列表:
-
消息 - 包含标头和有效载荷的消息的简单表示。
-
MessageHandler - 处理消息的合同。
-
MessageChannel - 发送消息的合同,允许生产者和消费者之间松散耦合。
-
SubscribableChannel -
MessageChannel与MessageHandler订阅者。 -
ExecutorSubscribableChannel -
SubscribableChannel使用一个Executor传递消息。
Java配置(即@EnableWebSocketMessageBroker)和XML名称空间配置(即<websocket:message-broker>)使用上述组件来组装消息工作流程。下图显示了启用简单的内置消息代理时使用的组件:

上图中有3个消息通道:
-
"clientInboundChannel"- 用于传递从WebSocket客户端收到的消息。 -
"clientOutboundChannel"- 用于向WebSocket客户端发送服务器消息。 -
"brokerChannel"- 用于从服务器端应用程序代码向消息代理发送消息。
下图显示了外部代理(例如RabbitMQ)配置为管理订阅和广播消息时使用的组件:

上图中的主要区别在于使用“代理中继”将消息传递到TCP上的外部STOMP代理,并将消息从代理传递到订阅的客户端。
当从WebSocket connectin接收到消息时,它们被解码为STOMP帧,然后转换为Spring Message表示形式,并发送给"clientInboundChannel"进一步处理。例如STOMP消息,其目的地标题开头"/app"可被路由到@MessageMapping在注释的控制器的方法,而"/topic"和"/queue"消息可以被直接路由到消息代理。
对@Controller来自客户端的STOMP消息进行注释处理可以通过该消息向消息代理发送消息"brokerChannel",并且代理将通过该消息将消息广播给匹配的订户"clientOutboundChannel"。相同的控制器也可以响应HTTP请求做同样的事情,因此客户端可以执行HTTP POST,然后@PostMapping方法可以向消息代理发送消息以广播到订阅的客户端。
让我们通过一个简单的例子来追踪流程。鉴于以下服务器设置:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio");
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
@Controller
public class GreetingController {
@MessageMapping("/greeting") {
public String handle(String greeting) {
return "[" + getTimestamp() + ": " + greeting;
}
}
-
客户端连接
"http://localhost:8080/portfolio"并建立WebSocket连接后,STOMP帧开始流动。 -
客户端发送SUBSCRIBE帧与目标标头
"/topic/greeting"。一旦收到并解码,该消息就被发送给该消息"clientInboundChannel",然后路由到存储客户订阅的消息代理。 -
客户端发送SEND帧
"/app/greeting"。该"/app"前缀有助于它的路线注解控制器。后"/app"前缀被剥离,剩下的"/greeting"目的地的部分被映射到@MessageMapping在方法GreetingController。 -
从返回的值
GreetingController变成一个SpringMessage,其有效负载基于返回值和默认的目标头"/topic/greeting"(从输入目标派生而来,"/app"取而代之"/topic")。结果消息被发送到“brokerChannel”并由消息代理处理。 -
消息代理找到所有匹配的订户,并通过
"clientOutboundChannel"消息被编码为STOMP帧并通过WebSocket连接发送MESSAGE帧。
下一节提供了更多关于注释方法的细节,包括支持的参数和返回值的种类。
4.4.5。带注释的控制器
应用程序可以使用带注释的@Controller类来处理来自客户端的消息。这些类可以声明@MessageMapping,@SubscribeMapping以及@ExceptionHandler 接下来描述的方法。
@MessageMapping
该@MessageMapping批注可在方法基于他们的目的地将消息路由使用。它在方法级别以及类型级别都得到支持。类型级别@MessageMapping用于表示控制器中所有方法的共享映射。
默认情况下,目标映射应该是Ant风格的路径模式,例如“/ foo *”,“/ foo / **”。这些模式包括对模板变量的支持,例如“/ foo / {id}”,可以使用@DestinationVariable方法参数进行引用。
|
应用程序可以选择切换到以点分隔的目标约定。见点作为分隔符。 |
@MessageMapping 方法可以使用以下参数具有灵活的签名:
| 方法论证 | 描述 |
|---|---|
|
|
用于访问完整的消息。 |
|
|
用于访问。中的标题 |
|
|
通过类型访问器方法访问标题。 |
|
|
为了访问消息的有效载荷,通过配置的转换(例如从JSON) 此注释的存在并不是必需的,因为如果没有其他参数匹配,则默认采用此注释。 有效载荷参数可以用 |
|
|
用于访问特定标题值以及使用类型转换( |
|
|
用于访问消息中的所有标题。这个参数必须是可赋值的 |
|
|
用于访问从消息目标中提取的模板变量。必要时,值将被转换为声明的方法参数类型。 |
|
|
反映在WebSocket HTTP握手时登录的用户。 |
当一个@MessageMapping方法返回一个值,默认值被序列化到有效负载通过配置MessageConverter,再送到作为Message到"brokerChannel"从那里被广播到订户。出站消息的目的地与入站消息的目的地相同,但前缀为"/topic"。
您可以使用@SendTo方法注释来自定义发送有效负载的目标。@SendTo也可以在类级别使用,以共享发送消息的默认目标目标。@SendToUser是仅向与消息相关联的用户发送消息的变体。详情请参阅用户目的地。
从返回值@MessageMapping的方法可与被包装ListenableFuture, CompletableFuture或CompletionStage以异步产生的有效负载。
作为从@MessageMapping方法中返回有效载荷的替代方法,您还可以使用该方法发送消息SimpMessagingTemplate,这也是如何在封面下处理返回值的。请参阅发送消息。
@SubscribeMapping
该@SubscribeMapping注释结合使用@MessageMapping以缩小到订阅消息的映射。在这种情况下,@MessageMapping 注释仅指定目标,而@SubscribeMapping仅指示对订阅消息的兴趣。
关于映射和输入参数的@SubscribeMapping方法通常与任何@MessageMapping方法没有区别。例如,您可以将它与类型级别相结合@MessageMapping以表示共享目标前缀,并且可以使用与任何@ MessageMapping`方法相同的方法参数。
关键的区别@SubscribeMapping在于,该方法的返回值被序列化为一个有效负载,并且不是发送到“brokerChannel”,而是发送到“clientOutboundChannel”,直接有效地回复客户端而不是通过代理进行广播。这对于实现一次性的请求 - 回复消息交换是有用的,并且从不执行订阅。这种模式的一个常见场景是数据必须加载和呈现时的应用程序初始化。
一个@SubscribeMapping方法也可以被注释,@SendTo在这种情况下,返回值被发送到"brokerChannel"显式指定的目标目的地。
@MessageExceptionHandler
应用程序可以使用@MessageExceptionHandler方法处理来自@MessageMapping方法的异常 。感兴趣的异常可以在注释本身中声明,或者如果你想访问异常实例,可以通过方法参数声明:
@Controller
public class MyController {
// ...
@MessageExceptionHandler
public ApplicationError handleException(MyException exception) {
// ...
return appError;
}
}
@MessageExceptionHandler方法支持灵活的方法签名,并支持相同的方法参数类型和返回值作为@MessageMapping方法。
通常,@MessageExceptionHandler方法适用于@Controller声明它们的类(或类层次结构)中。如果希望此类方法在全局应用,跨控制器应用,则可以在标记为的类中声明它们@ControllerAdvice。这与Spring MVC中的类似支持相当。
4.4.6。发送信息
如果您想从应用程序的任何部分向连接的客户端发送消息,该怎么办?任何应用程序组件都可以向其发送消息"brokerChannel"。最简单的方法是SimpMessagingTemplate注入一个注入,并用它来发送消息。通常应该很容易按类型注入,例如:
@Controller
public class GreetingController {
private SimpMessagingTemplate template;
@Autowired
public GreetingController(SimpMessagingTemplate template) {
this.template = template;
}
@RequestMapping(path="/greetings", method=POST)
public void greet(String greeting) {
String text = "[" + getTimestamp() + "]:" + greeting;
this.template.convertAndSend("/topic/greetings", text);
}
}
但是如果存在另一个相同类型的bean,它也可以通过其名称“brokerMessagingTemplate”进行限定。
4.4.7。简单的经纪人
内置的简单消息代理处理来自客户端的订阅请求,将它们存储在内存中,并将消息广播到具有匹配目标的连接客户端。代理支持类似路径的目标,包括订阅Ant风格的目标模式。
|
应用程序也可以使用点分隔的目标(vs斜线)。见点作为分隔符。 |
4.4.8。外部经纪人
简单的代理非常适合入门,但仅支持STOMP命令的一个子集(例如,没有acks,收据等),依赖于简单的消息发送循环,并且不适用于群集。作为替代,应用程序可以升级到使用全功能消息代理。
以下是启用全功能代理的示例配置:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/topic", "/queue");
registry.setApplicationDestinationPrefixes("/app");
}
}
等同的XML配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker application-destination-prefix="/app">
<websocket:stomp-endpoint path="/portfolio" />
<websocket:sockjs/>
</websocket:stomp-endpoint>
<websocket:stomp-broker-relay prefix="/topic,/queue" />
</websocket:message-broker>
</beans>
上述配置中的“STOMP代理中继”是一个Spring MessageHandler ,它通过将消息转发给外部消息代理来处理消息。为此,它建立与代理的TCP连接,将所有消息转发给它,然后通过其WebSocket会话将从代理接收到的所有消息转发给客户端。本质上,它充当一个“中继”,可以在两个方向上转发消息。
|
请 为您的项目添加 |
此外,应用程序组件(例如HTTP请求处理方法,业务服务等)也可以将消息发送到代理中继,如发送消息中所述,以便将消息广播到订阅的WebSocket客户端。
实际上,代理中继可以实现强大且可扩展的消息广播。
4.4.9。连接到经纪人
STOMP代理中继维护与代理的单个“系统”TCP连接。此连接仅用于源自服务器端应用程序的消息,不用于接收消息。您可以为此连接配置STOMP凭证,即STOMP框架login和passcode标题。这在XML命名空间和Java配置中都显示为 systemLogin/ systemPasscode默认值为guest/的属性guest。
STOMP代理中继还为每个连接的WebSocket客户端创建一个单独的TCP连接。您可以配置STOMP凭证以用于代表客户端创建的所有TCP连接。这在XML命名空间和Java配置中都显示为clientLogin/ clientPasscode默认值为guest/的属性guest。
|
STOMP代理中继始终 在代表客户端转发给代理的每个帧上设置 |
STOMP代理中继还通过“系统”TCP连接向消息代理发送和接收心跳。您可以配置发送和接收心跳(每个默认10秒)的时间间隔。如果与代理的连接丢失,代理中继将继续尝试每5秒重新连接一次,直至成功。
任何Spring bean都可以实现ApplicationListener<BrokerAvailabilityEvent>,以便在与代理的“系统”连接丢失并重新建立时接收通知。例如,当没有活动的“系统”连接时,股票报价服务广播股票报价可以停止尝试发送消息。
默认情况下,STOMP代理中继始终连接,如果连接丢失,则根据需要重新连接到相同的主机和端口。如果您希望提供多个地址,则在每次尝试连接时,都可以配置地址提供者,而不是固定的主机和端口。例如:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
// ...
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/queue/", "/topic/").setTcpClient(createTcpClient());
registry.setApplicationDestinationPrefixes("/app");
}
private ReactorNettyTcpClient<byte[]> createTcpClient() {
Consumer<ClientOptions.Builder<?>> builderConsumer = builder -> {
builder.connectAddress(()-> {
// Select address to connect to ...
});
};
return new ReactorNettyTcpClient<>(builderConsumer, new StompReactorNettyCodec());
}
}
STOMP代理中继也可以配置virtualHost属性。这个属性的值将被设置为host每CONNECT帧的头部,并且例如在建立TCP连接的实际主机与提供基于云的STOMP服务的主机不同的云环境中可能是有用的。
4.4.10。点作为分隔符
当消息被路由到@MessageMapping方法时,它们将与AntPathMatcher默认模式匹配, 并预计将使用斜杠“/”作为分隔符。这在Web应用程序中是一个很好的约定,与HTTP URL类似。但是,如果您更习惯于使用消息传递约定,则可以切换为使用点“。”。作为分隔符。
在Java配置中:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
// ...
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setPathMatcher(new AntPathMatcher("."));
registry.enableStompBrokerRelay("/queue", "/topic");
registry.setApplicationDestinationPrefixes("/app");
}
}
在XML中:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker application-destination-prefix="/app" path-matcher="pathMatcher">
<websocket:stomp-endpoint path="/stomp"/>
<websocket:stomp-broker-relay prefix="/topic,/queue" />
</websocket:message-broker>
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher">
<constructor-arg index="0" value="."/>
</bean>
</beans>
之后,控制器可以使用点“”。作为@MessageMapping方法中的分隔符:
@Controller
@MessageMapping("foo")
public class FooController {
@MessageMapping("bar.{baz}")
public void handleBaz(@DestinationVariable String baz) {
// ...
}
}
客户现在可以发送消息给"/app/foo.bar.baz123"。
在上面的示例中,我们没有更改“代理中继”上的前缀,因为这些前缀完全取决于外部消息代理。检查您使用的代理的STOMP文档页面,查看它支持的目标标头的约定。
另一方面,“简单经纪人”确实依赖于配置,PathMatcher因此如果您切换也适用于代理的分隔符,并且方式将消息中的目标与订阅中的模式相匹配。
4.4.11。认证
通过WebSocket消息会话的每个STOMP都以HTTP请求开始 - 可以是升级到WebSocket的请求(即WebSocket握手),或者SockJS回退一系列SockJS HTTP传输请求。
Web应用程序已经具有用于保护HTTP请求的身份验证和授权。通常,用户通过Spring Security使用某种机制(如登录页面,HTTP基本身份验证或其他)进行身份验证。经过身份验证的用户的安全上下文保存在HTTP会话中,并与相同的基于cookie的会话中的后续请求相关联。
因此,对于WebSocket握手或SockJS HTTP传输请求,通常会有一个经过身份验证的用户可以访问HttpServletRequest#getUserPrincipal()。Spring会自动将该用户与为其创建的WebSocket或SockJS会话相关联,并随后通过该用户头在该会话中传输所有STOMP消息。
简而言之,典型的Web应用程序需要做的并不是什么特别的事情,除了它已经做的安全性之外。用户在HTTP请求级别通过基于cookie的HTTP会话维护安全上下文进行身份验证,该会话随后与为该用户创建的WebSocket或SockJS会话相关联,并在每个Message流经应用程序的每个会话中产生用户标题。
请注意,STOMP协议在CONNECT帧上有一个“登录”和“密码”标头。那些最初是为TCP而设计的,并且仍然需要用于STOMP。但是,对于默认情况下的WebSocket上的STOMP,Spring将忽略STOMP协议级别的授权标头,并假定用户已在HTTP传输级别进行身份验证,并且期望WebSocket或SockJS会话包含经过身份验证的用户。
|
Spring Security提供 WebSocket子协议授权 ,该授权使用a |
4.4.12。令牌认证
Spring Security OAuth 提供对基于令牌的安全性的支持,包括JSON Web令牌(JWT)。这可以用作Web应用程序中的认证机制,包括STOMP over WebSocket交互,就像上一节所述,即通过基于cookie的会话维护身份。
与此同时,基于cookie的会话并不总是最适合用于不希望完全维护服务器端会话的应用程序,或者在通常使用头进行身份验证的移动应用程序中。
该WebSocket协议RFC 6455 “没有规定该服务器可以在WebSocket的握手过程中验证客户端的任何特定的方式。” 但实际上,浏览器客户端只能使用标准的身份验证标头(即基本的HTTP身份验证)或Cookie,并且不能提供自定义标头。同样,SockJS JavaScript客户端不提供发送带有SockJS传输请求的HTTP头的方法,请参阅 sockjs-client issue 196。相反,它确实允许发送可用于发送令牌的查询参数,但它有其自身的缺点,例如,令牌可能会无意中通过URL记录在服务器日志中。
|
上述限制适用于基于浏览器的客户端,并且不适用于支持使用WebSocket和SockJS请求发送头信息的基于Spring Java的STOMP客户端。 |
因此,希望避免使用cookie的应用程序在HTTP协议级别上可能没有任何可用于身份验证的好选择。他们可能更喜欢使用STOMP消息协议级别的头进行身份验证,而不是使用cookie。有两个简单的步骤可以做到这一点:
-
使用STOMP客户端在连接时传递认证头。
-
使用a处理验证头
ChannelInterceptor。
以下是注册自定义身份验证拦截器的示例服务器端配置。请注意,拦截器只需要在CONNECT上进行身份验证并设置用户标头Message。Spring会记录并保存经过身份验证的用户,并将其与随后的STOMP消息关联到同一会话:
@Configuration
@EnableWebSocketMessageBroker
public class MyConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new ChannelInterceptorAdapter() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
Authentication user = ... ; // access authentication header(s)
accessor.setUser(user);
}
return message;
}
});
}
}
还要注意,当使用Spring Security的消息授权时,目前您需要确保在ChannelInterceptorSpring Security之前订购认证配置。这最好通过在自己的WebSocketMessageBrokerConfigurer标记为 的实现中声明自定义拦截器来完成@Order(Ordered.HIGHEST_PRECEDENCE + 99)。
4.4.13。用户目的地
应用程序可以发送针对特定用户的消息,并且Spring的STOMP支持可以识别出"/user/"为此目的而前缀的目标。例如,客户端可能会订阅目标"/user/queue/position-updates"。这个目的地将被处理UserDestinationMessageHandler并转换成用户会话唯一的目的地,例如"/queue/position-updates-user123"。这提供了订购通用命名目的地的便利,同时确保与订阅相同目的地的其他用户没有冲突,以便每个用户可以接收独特的库存位置更新。
在发送端,消息可以被发送到目的地,例如"/user/{username}/queue/position-updates",该目的地 又将被转换UserDestinationMessageHandler为一个或多个目的地,对于与用户相关联的每个会话都有一个目的地。这允许应用程序内的任何组件发送针对特定用户的消息,而不必知道任何超过其名称和通用目的地的消息。这也通过注释和消息传递模板来支持。
例如,消息处理方法可以将消息发送给与通过@SendToUser注释处理的消息相关联的用户(也在类级别上受支持以共享公共目标):
@Controller
public class PortfolioController {
@MessageMapping("/trade")
@SendToUser("/queue/position-updates")
public TradeResult executeTrade(Trade trade, Principal principal) {
// ...
return tradeResult;
}
}
如果用户有多个会话,默认情况下所有订阅给定目标的会话都是针对的。但是,有时候,可能需要仅定位发送正在处理的消息的会话。这可以通过将该broadcast属性设置为false 来完成,例如:
@Controller
public class MyController {
@MessageMapping("/action")
public void handleAction() throws Exception{
// raise MyBusinessException here
}
@MessageExceptionHandler
@SendToUser(destinations="/queue/errors", broadcast=false)
public ApplicationError handleException(MyBusinessException exception) {
// ...
return appError;
}
}
|
虽然用户目的地通常暗示已通过身份验证的用户,但并非严格要求。与认证用户无关的WebSocket会话可以订阅用户目标。在这种情况下, |
例如,通过注入SimpMessagingTemplate由Java配置或XML命名空间创建的消息,也可以从任何应用程序组件向用户目标发送消息("brokerMessagingTemplate"如果需要,则使用bean名称进行验证@Qualifier):
@Service
public class TradeServiceImpl implements TradeService {
private final SimpMessagingTemplate messagingTemplate;
@Autowired
public TradeServiceImpl(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
// ...
public void afterTradeExecuted(Trade trade) {
this.messagingTemplate.convertAndSendToUser(
trade.getUserName(), "/queue/position-updates", trade.getResult());
}
}
|
将用户目标与外部消息代理一起使用时,请查看代理文档以了解如何管理不活动队列,以便在用户会话结束时删除所有唯一用户队列。例如,RabbitMQ在使用目的地时创建自动删除队列 |
在多应用程序服务器场景中,由于用户连接到不同的服务器,用户目标可能保持未解决状态。在这种情况下,您可以配置一个目的地以传播未解析的消息,以便其他服务器有机会尝试。这可以通过做userDestinationBroadcast财产 MessageBrokerRegistry在Java中的配置和user-destination-broadcast该属性message-broker的XML元素。
4.4.14。事件和拦截
发布了几个ApplicationContext事件(下面列出),可以通过实现Spring的ApplicationListener接口来接收。
-
BrokerAvailabilityEvent- 指示代理何时变得可用/不可用。虽然“简单”代理在启动时立即可用,并且在应用程序运行时仍然保持这种状态,但STOMP“代理中继”可能会失去与全功能代理的连接,例如,如果代理重新启动。代理中继具有重新连接逻辑,并在代理恢复时重新建立与代理的“系统”连接,因此,只要状态从连接切换到断开连接,就会发布此事件,反之亦然。使用SimpMessagingTemplate应该组件订阅此事件,并避免在代理不可用时发送消息。无论如何,他们应该准备MessageDeliveryException在发送消息时处理。 -
SessionConnectEvent- 收到新的STOMP CONNECT时发布,指示新客户端会话的开始。该事件包含代表连接的消息,包括会话ID,用户信息(如果有)以及客户端可能发送的任何自定义头。这对于跟踪客户端会话很有用。订阅此事件的组件可以使用SimpMessageHeaderAccessor或 包装所包含的消息StompMessageHeaderAccessor。 -
SessionConnectedEvent-SessionConnectEvent在经纪人发送STOMP CONNECTED框架以响应CONNECT 后不久发布。在这一点上,STOMP会议可以被视为完全建立。 -
SessionSubscribeEvent- 收到新的STOMP SUBSCRIBE时发布。 -
SessionUnsubscribeEvent- 收到新的STOMP UNSUBSCRIBE时发布。 -
SessionDisconnectEvent- STOMP会话结束时发布。DISCONNECT可能已从客户端发送,或者也可能在WebSocket会话关闭时自动生成。在某些情况下,此事件可能会在每个会话中多次发布。对于多个断开连接事件,组件应该是幂等的。
|
在使用全功能代理时,STOMP“代理中继”会在代理暂时不可用时自动重新连接“系统”连接。但客户端连接不会自动重新连接。假设心跳已启用,客户通常会注意到代理在10秒内没有响应。客户需要实现自己的重新连接逻辑。 |
此外,应用程序可以通过ChannelInterceptor在相应的消息通道上注册一个直接拦截每个传入和传出的消息。例如拦截入站消息:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new MyChannelInterceptor());
}
}
自定义ChannelInterceptor可以扩展空方法基类 ChannelInterceptorAdapter并使用StompHeaderAccessor或SimpMessageHeaderAccessor 访问有关该消息的信息。
public class MyChannelInterceptor extends ChannelInterceptorAdapter {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
StompCommand command = accessor.getStompCommand();
// ...
return message;
}
}
4.4.15。STOMP客户端
Spring通过WebSocket客户端提供STOMP,并通过TCP客户端提供STOMP。
开始创建和配置WebSocketStompClient:
WebSocketClient webSocketClient = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new StringMessageConverter());
stompClient.setTaskScheduler(taskScheduler); // for heartbeats
在上面的例子中StandardWebSocketClient可以被替换,SockJsClient 因为那也是一个实现WebSocketClient。所述SockJsClient可以使用的WebSocket或基于HTTP的传输作为后备。欲了解更多信息,请参阅 SockJsClient。
接下来建立一个连接并为STOMP会话提供一个处理程序:
String url = "ws://127.0.0.1:8080/endpoint";
StompSessionHandler sessionHandler = new MyStompSessionHandler();
stompClient.connect(url, sessionHandler);
当会话准备好使用时,会通知处理程序:
public class MyStompSessionHandler extends StompSessionHandlerAdapter {
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
// ...
}
}
一旦会话建立,任何有效载荷都可以被发送,并且会被配置的序列化MessageConverter:
session.send("/topic/foo", "payload");
您也可以订阅目的地。这些subscribe方法需要订阅消息的处理程序,并返回Subscription可用于取消订阅的句柄。对于每个接收到的消息,处理程序可以指定负载应该反序列化的目标对象类型:
session.subscribe("/topic/foo", new StompFrameHandler() {
@Override
public Type getPayloadType(StompHeaders headers) {
return String.class;
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
// ...
}
});
要启用STOMP心跳配置WebSocketStompClient(TaskScheduler 可选择自定义心跳间隔),10秒钟进行写操作,导致心跳被发送,10秒钟进入非活动状态,关闭连接。
|
当 |
STOMP协议还支持客户端必须添加“收据”头的接收,服务器在处理发送或订阅后使用RECEIPT帧响应此头。为了支持这个StompSession提议 setAutoReceipt(boolean)导致要在以后每发送添加或订阅了“回执”标头。或者,您也可以手动将“收据”标题添加到StompHeaders。发送和订阅都返回一个Receiptable 可用于注册接收成功和失败回调的实例。对于此功能,客户端必须配置为a TaskScheduler 和收据过期之前的时间(默认为15秒)。
请注意,除了处理消息的异常回调以及传输级错误(包括)之外,它StompSessionHandler本身StompFrameHandler允许它处理ERROR帧。handleExceptionhandleTransportErrorConnectionLostException
4.4.16。WebSocket范围
每个WebSocket会话都有一个属性映射。该映射作为头部附加到入站客户端消息,并可以通过控制器方法访问,例如:
@Controller
public class MyController {
@MessageMapping("/action")
public void handle(SimpMessageHeaderAccessor headerAccessor) {
Map<String, Object> attrs = headerAccessor.getSessionAttributes();
// ...
}
}
也可以在websocket范围中声明一个Spring管理的bean 。WebSocket范围的bean可以注入到控制器以及在“clientInboundChannel”上注册的任何通道拦截器。这些通常是单身,比单个WebSocket会话更长。因此,您将需要为WebSocket范围的bean使用范围代理模式:
@Component
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBean {
@PostConstruct
public void init() {
// Invoked after dependencies injected
}
// ...
@PreDestroy
public void destroy() {
// Invoked when the WebSocket session ends
}
}
@Controller
public class MyController {
private final MyBean myBean;
@Autowired
public MyController(MyBean myBean) {
this.myBean = myBean;
}
@MessageMapping("/action")
public void handle() {
// this.myBean from the current WebSocket session
}
}
与任何自定义范围一样,Spring MyBean首次从控制器访问新实例时初始化新实例,并将实例存储在WebSocket会话属性中。随后返回相同的实例,直到会话结束。WebSocket范围的bean将会调用所有的Spring生命周期方法,如上例所示。
4.4.17。性能
在性能方面没有银弹。影响它的因素很多,包括消息的大小,卷的大小,应用程序的方法是否执行需要阻止的工作,以及网络速度等外部因素。本节的目标是提供可用配置选项的概述以及关于如何推理缩放的一些想法。
在消息传递应用程序中,消息通过通道传递给由线程池支持的异步执行。配置这样的应用程序需要对通道和消息流的了解。因此建议查看消息流。
明显的起点是配置支持"clientInboundChannel"和的线程池 "clientOutboundChannel"。默认情况下,这两个配置都是可用处理器数量的两倍。
如果处理注释方法中的消息主要是CPU绑定的,那么"clientInboundChannel"应该保持接近处理器的数量。如果他们所做的工作有更多的IO限制,并且需要阻塞或等待数据库或其他外部系统,那么线程池的大小将需要增加。
|
一个常见的困惑是,配置核心池大小(例如10)和最大池大小(例如20)会导致一个线程池中有10到20个线程。事实上,如果容量保留为默认值Integer.MAX_VALUE,那么线程池将永远不会超出核心池大小,因为所有其他任务都将排队。 请查阅Javadoc |
在这一"clientOutboundChannel"方面,所有关于发送消息到WebSocket客户端。如果客户端在快速网络上,那么线程的数量应该接近可用处理器的数量。如果速度较慢或带宽较低,则消耗消息需要更长的时间,并且会给线程池造成负担。因此增加线程池的大小将是必要的。
虽然“clientInboundChannel”的工作负载可以预测 - 毕竟它基于应用程序的工作 - 如何配置“clientOutboundChannel”更难,因为它基于超出应用程序控制的因素。出于这个原因,有两个与发送消息相关的附加属性。那些是"sendTimeLimit" 和"sendBufferSizeLimit"。这些用于配置允许发送多长时间以及向客户端发送消息时可以缓冲多少数据。
总体思路是,在任何时候,只有一个线程可以用于发送给客户端。所有其他消息同时得到缓冲,您可以使用这些属性来决定允许发送消息多长时间,同时可以缓冲多少数据。请查看此配置的Javadoc和XML模式的文档以获取重要的其他详细信息。
以下是示例配置:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setSendTimeLimit(15 * 1000).setSendBufferSizeLimit(512 * 1024);
}
// ...
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker>
<websocket:transport send-timeout="15000" send-buffer-size="524288" />
<!-- ... -->
</websocket:message-broker>
</beans>
上面显示的WebSocket传输配置也可用于配置传入STOMP消息的最大允许大小。虽然理论上WebSocket消息的大小几乎是无限的,但实际上WebSocket服务器会施加限制 - 例如,Tomcat上的8K和Jetty上的64K。出于这个原因,STOMP客户端(如JavaScript webstomp-client 等)在16K边界处分割较大的STOMP消息,并将它们作为多个WebSocket消息发送,因此需要服务器进行缓冲和重新组装。
Spring的STOMP支持WebSocket支持,因此应用程序可以为STOMP消息配置最大大小,而不考虑WebSocket服务器特定的消息大小。请记住,WebSocket消息大小将根据需要自动调整,以确保它们可以至少携带16K WebSocket消息。
以下是示例配置:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setMessageSizeLimit(128 * 1024);
}
// ...
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker>
<websocket:transport message-size="131072" />
<!-- ... -->
</websocket:message-broker>
</beans>
有关缩放的重要一点是使用多个应用程序实例。目前,用简单的经纪人来做这件事是不可能的。但是,在使用全功能代理(如RabbitMQ)时,每个应用程序实例都连接到代理,并且从一个应用程序实例广播的消息可以通过代理广播到通过任何其他应用程序实例连接的WebSocket客户端。
4.4.18。监控
使用@EnableWebSocketMessageBroker或<websocket:message-broker>关键基础架构组件时,会自动收集统计信息和计数器,以提供对应用程序内部状态的重要信息。该配置还声明一个类型的bean,WebSocketMessageBrokerStats它在一个地方收集所有可用的信息,默认情况下INFO每30分钟记录一次。这个bean可以通过Spring的导出到JMX MBeanExporter,以便在运行时查看,例如通过JDK jconsole。以下是可用信息的摘要。
- 客户端WebSocket会话
-
- 当前
-
表示当前有多少个客户端会话,WebSocket与HTTP流式传输和轮询SockJS会话进一步细分了计数。
- 总
-
表示已经建立了多少次会话。
- 异常关闭
-
- 连接失败
-
这些会话已经建立,但在60秒内没有收到任何消息后关闭。这通常表示代理或网络问题。
- 发送限制超出
-
会话超过配置的发送超时或慢速客户端可能发生的发送缓冲区限制后会关闭(请参阅上一节)。
- 运输错误
-
会话在传输错误(例如未能读取或写入WebSocket连接或HTTP请求/响应)后关闭。
- STOMP框架
-
已处理CONNECT,CONNECTED和DISCONNECT帧的总数,指示在STOMP级别连接的客户端数量。请注意,当会话异常关闭或客户端关闭而不发送DISCONNECT帧时,DISCONNECT计数可能会更低。
- STOMP代理中继
-
- TCP连接
-
指示代理客户端WebSocket会话为代理建立多少个TCP连接。这应该等于客户端WebSocket会话的数量+ 1个额外的共享“系统”连接,用于从应用程序内发送消息。
- STOMP框架
-
代表客户转发或从代理接收的CONNECT,CONNECTED和DISCONNECT帧的总数。请注意,无论客户端WebSocket会话如何关闭,都会向代理发送DISCONNECT帧。因此,较低的DISCONNECT帧数指示代理正在主动关闭连接,可能是因为没有及时到达的心跳,无效的输入帧或其他。
- 客户端入站频道
-
来自支持“clientInboundChannel”的线程池的统计数据,可以深入了解传入消息处理的健康状况。在这里排队的任务表明应用程序处理消息的速度可能太慢。如果存在I / O绑定任务(例如数据库查询速度缓慢,对第三方REST API的HTTP请求等),则考虑增加线程池大小。
- 客户端出站频道
-
来自支持“clientOutboundChannel”的线程池的统计数据,可以深入了解广播消息对客户端的健康状况。在这里排队的任务表明客户端速度太慢而无法使用消息。解决此问题的一种方法是增加线程池大小以适应预期的并发慢速客户端的数量。另一种选择是减少发送超时和发送缓冲区大小限制(请参阅上一节)。
- SockJS任务计划程序
-
来自用于发送心跳的SockJS任务调度程序的线程池中的统计信息。请注意,当在STOMP级别上协商心跳时,SockJS心跳将被禁用。
4.4.19。测试
有两种使用Spring的STOMP和WebSocket支持测试应用程序的主要方法。首先是编写服务器端测试,验证控制器的功能及其注释的消息处理方法。第二是编写完整的端到端测试,涉及运行客户端和服务器。
这两种方法不是相互排斥的。相反,每个人在整体测试策略中都占有一席之地。服务器端测试更专注,易于编写和维护。另一方面,端到端集成测试更加完整并且测试更多,但他们也更多地参与编写和维护。
服务器端测试的最简单形式是编写控制器单元测试。然而,这并没有足够的用处,因为控制器的大部分操作取决于其注释。纯粹的单元测试根本无法测试。
理想情况下,测试中的控制器应该像运行时一样被调用,就像使用Spring MVC测试框架测试控制器处理HTTP请求的方法一样。即不运行Servlet容器,而是依靠Spring Framework来调用带注释的控制器。就像使用Spring MVC测试一样,这里有两种可能的选择,使用“基于上下文”或“独立”的设置:
-
在Spring TestContext框架的帮助下加载实际的Spring配置,注入“clientInboundChannel”作为测试字段,并使用它发送消息以由控制器方法处理。
-
手动设置调用控制器(即
SimpAnnotationMethodMessageHandler)所需的最小Spring框架基础设施,并将控制器的消息直接传递给它。
5.其他Web框架
5.1。介绍
本章详细介绍Spring与第三方Web框架的集成。
Spring框架的核心价值主张之一是支持 选择。从一般意义上讲,Spring不会强迫某人使用或购买任何特定的体系结构,技术或方法(尽管它肯定会推荐一些)。这种自由选择与开发人员及其开发团队最相关的体系结构,技术或方法的自由最明显的地方在于Web提供了自己的Web框架(Spring MVC),而同时提供与众多流行的第三方Web框架的集成。
5.2。通用配置
在深入了解每个支持的Web框架的集成细节之前,让我们先看看不是特定于任何一个Web框架的Spring配置。(本节同样适用于Spring自己的Web框架Spring MVC。)
(Spring的)轻量级应用程序模型所支持的其中一个概念(为了获得更好的词)是分层体系结构的概念。请记住,在“经典”分层架构中,Web层只是许多层中的一个; 它作为服务器端应用程序的入口点之一,并委托服务层定义的服务对象(外观)以满足业务特定(和表示技术不可知)用例。在Spring中,这些服务对象,任何其他特定于业务的对象,数据访问对象等都存在于独特的“业务上下文”中,其中包含“ 否” Web或表示层对象(表示对象,如Spring MVC控制器通常配置为不同的'呈现语境')。 WebApplicationContext),其中包含应用程序中的所有“业务bean”。
关于具体细节:所需做的就是ContextLoaderListener 在web.xml一个Web应用程序的标准Java EE servlet 文件中声明一个 ,并添加一个 contextConfigLocation<context-param />部分(在同一个文件中),该部分定义了哪一组Spring XML配置文件加载。
在<listener />配置下面找到:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在<context-param />配置下面查找:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>
如果您不指定contextConfigLocation上下文参数, ContextLoaderListener则会查找调用/WEB-INF/applicationContext.xml来加载的文件。一旦加载了上下文文件,Spring就会WebApplicationContext 根据bean定义创建一个 对象并将其存储在ServletContextWeb应用程序中。
所有Java Web框架都建立在Servlet API之上,因此可以使用以下代码片段来访问由此ApplicationContext 创建的“业务上下文” ContextLoaderListener。
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
这个 WebApplicationContextUtils 类是为了方便,所以你不必记住ServletContext 属性的名称。如果对象不存在于该 键下,则它的getWebApplicationContext()方法将返回。而不是冒险进入你的应用程序,最好使用该方法。这个方法在丢失时抛出异常。nullWebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTENullPointerExceptionsgetRequiredWebApplicationContext()ApplicationContext
一旦你有了一个引用WebApplicationContext,你可以通过它们的名字或类型来获取bean。大多数开发人员通过名称检索bean,然后将其转换为其实现的接口之一。
幸运的是,本节中的大多数框架都有更简单的查找bean的方法。他们不仅可以很容易地从Spring容器获取bean,而且还允许您在其控制器上使用依赖注入。每个Web框架部分都有关于其特定集成策略的更多细节。
5.3。JSF
JavaServer Faces(JSF)是JCP的标准组件,事件驱动的Web用户界面框架。从Java EE 5开始,它是Java EE伞的官方组成部分。
对于流行的JSF运行时以及流行的JSF组件库,请查看 Apache MyFaces项目。MyFaces项目还提供了常见的JSF扩展,如MyFaces Orchestra:基于Spring的JSF扩展,提供丰富的对话范围支持。
|
Spring Web Flow 2.0通过其新建立的Spring Faces模块提供了丰富的JSF支持,既可用于以JSF为中心的用法(如本节所述),也可用于以Spring为中心的用法(在Spring MVC调度程序中使用JSF视图)。查看 Spring Web Flow网站获取详细信息! |
Spring的JSF集成中的关键元素是JSF ELResolver机制。
5.3.1。Spring Bean解析器
SpringBeanFacesELResolver是JSF 1.2+兼容的ELResolver实现,与JSF 1.2和JSP 2.1使用的标准Unified EL集成。像SpringBeanVariableResolver,将其委托给Spring的“业务环境” WebApplicationContext 第一,然后到JSF实现的默认的解析器。
在配置方面,只需SpringBeanFacesELResolver在JSF faces-context.xml文件中定义:
<faces-config>
<application>
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
...
</application>
</faces-config>
5.3.2。这时,FacesContextUtils
VariableResolver当在faces-config.xml中将一个属性映射到bean时,定制效果很好,但有时可能需要显式地获取一个bean。这个FacesContextUtils 班让这个很简单。它类似于WebApplicationContextUtils,除了它需要一个FacesContext参数而不是一个ServletContext参数。
ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());
5.4。Apache Struts 2.x
由Craig McClanahan发明,Struts是由Apache软件基金会主办的一个开源项目。当时,它大大简化了JSP / Servlet编程范例,并赢得了许多使用专有框架的开发人员。它简化了编程模型,它是开源的(因此像啤酒一样是免费的),并且它拥有一个大型的社区,这使得该项目在Java Web开发人员中成长并受到欢迎。
查看Struts 附带的Spring集成的Struts Spring插件。
5.5。挂毯5.x
来自Tapestry主页:
Tapestry是一个“ 面向组件的框架,用于在Java中创建动态,健壮,高度可伸缩的Web应用程序。 ”
虽然Spring拥有自己强大的Web层,但使用Web用户界面的Tapestry和较低层的Spring容器组合构建企业Java应用程序有许多独特的优势。

浙公网安备 33010602011771号