20200319 Spring Web MVC 2-5

2. REST客户端

本节描述了客户端对REST端点的访问选项。

2.1。 RestTemplate

RestTemplate 是执行HTTP请求的同步客户端。它是原始的Spring REST客户端,并在基础HTTP客户端库上公开了简单的模板方法API。

从5.0开始,无阻塞,反应式WebClient提供了RestTemplate的现代替代方案,并有效支持同步和异步以及流方案。 RestTemplate将在将来的版本中弃用,并且以后将不会添加主要的新功能。

有关详细信息,请参见REST端点

2.2。 WebClient

WebClient是执行HTTP请求的非阻塞,反应式客户端。它是在5.0中引入的,提供了RestTemplate的现代替代方案,并有效支持同步和异步以及流方案。

RestTemplate相比,WebClient支持以下内容:

  • 非阻塞I / O。
  • 反应性流背压。
  • 高并发,占用硬件资源更少。
  • 利用Java 8 lambda的函数式风格,流畅的API。
  • 同步和异步交互。
  • 从服务器向上流或向下流。(Streaming up to or streaming down from a server.)

有关更多详细信息,请参见WebClient

3.测试

本节总结了Spring MVC应用程序在spring-test中可用的选项。

  • Servlet API模拟:用于单元测试控制器,过滤器和其他Web组件的Servlet API约定的模拟实现。有关更多详细信息,请参见Servlet API模拟对象。
  • TestContext Framework:支持在JUnit和TestNG测试中加载Spring配置,包括跨测试方法高效地缓存已加载的配置,并支持通过MockServletContext加载WebApplicationContext。有关更多详细信息,请参见TestContext Framework
  • Spring MVC Test:一种框架,也称为MockMvc,用于通过DispatcherServlet(即支持注释)测试带注释的控制器,该框架具有Spring MVC基础结构,但没有HTTP服务器。有关更多详细信息,请参见Spring MVC Test
  • 客户端REST:spring-test提供一个MockRestServiceServer,您可以用作模拟服务器来测试内部使用RestTemplate的客户端代码。有关更多详细信息,请参见客户端REST测试
  • WebTestClient:专门用于测试WebFlux应用程序,但也可以用于通过HTTP连接到任何服务器的端到端集成测试。它是一个无阻塞的反应式客户端,非常适合测试异步和流传输方案。

4. WebSockets

参考文档的此部分涵盖对Servlet堆栈的支持,包括原始WebSocket交互的WebSocket消息传递,通过SockJS进行WebSocket仿真以及通过STOMP作为WebSocket的子协议进行发布-订阅消息传递。

4.1。WebSocket介绍

WebSocket协议RFC 6455提供了一种标准化方法,可通过单个TCP连接在客户端和服务器之间建立全双工双向通信通道。它是与HTTP不同的TCP协议,但旨在通过端口80443通过HTTP工作,并允许重复使用现有的防火墙规则。

WebSocket交互始于一个HTTP请求,该请求使用HTTP Upgrade标头进行升级,或在这种情况下切换到WebSocket协议。以下示例显示了这种交互:

GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: websocket      # Upgrade标头。
Connection: Upgrade     # 使用Upgrade连接。
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080

具有WebSocket支持的服务器代替通常的200状态代码,返回类似于以下内容的输出:

HTTP/1.1 101 Switching Protocols    # 协议切换
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp

成功握手后,HTTP升级请求的基础TCP套接字将保持打开状态,客户端和服务器均可继续发送和接收消息。

WebSockets的工作原理的完整介绍超出了本文档的范围。请参阅RFC 6455,HTML5的WebSocket章节或Web上的许多简介和教程中的任何一篇。

请注意,如果WebSocket服务器在Web服务器(例如nginx)后面运行,则可能需要对其进行配置,以将WebSocket升级请求传递到WebSocket服务器。同样,如果应用程序在云环境中运行,请检查与WebSocket支持相关的云提供商的说明。

4.1.1。HTTP与WebSocket

尽管WebSocket被设计为与HTTP兼容并以HTTP请求开头,但重要的是要了解这两个协议会导致非常不同的体系结构和应用程序编程模型。

在HTTP和REST中,应用程序被建模为许多URL。为了与应用程序交互,客户端访问那些URL,即请求-响应样式。服务器根据HTTP URL,方法和标头将请求路由到适当的处理程序。

相比之下,在WebSockets中,通常只有一个URL用于初始连接。随后,所有应用程序消息都在同一TCP连接上流动。这指向了一个完全不同的异步,事件驱动的消息传递体系结构。

WebSocket也是一种低级传输协议,与HTTP不同,它不对消息的内容规定任何语义。这意味着除非客户端和服务器就消息语义达成一致,否则就无法路由或处理消息。

WebSocket客户端和服务器可以通过HTTP握手请求上的Sec-WebSocket-Protocol标头协商使用更高级别的消息传递协议(例如STOMP)。在这种情况下,他们需要提出自己的约定。

4.1.2。何时使用WebSockets

WebSockets可以使网页具有动态性和交互性。但是,在许多情况下,结合使用Ajax和HTTP流或长时间轮询可以提供一种简单有效的解决方案。

例如,新闻,邮件和社交订阅源需要动态更新,但是每几分钟进行一次更新可能是完全可以的。另一方面,协作,游戏和金融应用程序需要更接近实时。

仅延迟并不是决定因素。如果消息量相对较少(例如,监视网络故障),则HTTP流或轮询可以提供有效的解决方案。低延迟,高频率和高流量的结合才是使用WebSocket的最佳案例。

还要记住,在Internet上,不受控制的代理可能会阻止WebSocket交互,这是因为未将它们配置为传递Upgrade标头,或者是因为它们关闭了长期处于空闲状态的连接。 这意味着与面向公众的应用程序相比,将WebSocket用于防火墙内部的应用程序是一个更直接的决定。

4.2。WebSocket API

Spring框架提供了一个WebSocket API,可用于编写处理WebSocket消息的客户端和服务器端应用程序。

4.2.1。 WebSocketHandler

创建WebSocket服务器就像实现WebSocketHandler一样简单,或者更可能地,扩展TextWebSocketHandlerBinaryWebSocketHandler。 以下示例使用TextWebSocketHandler

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配置和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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://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。在WebSocketHttpRequestHandler的帮助下,将WebSocketHandler集成到其他HTTP服务环境中相对简单。

当直接使用或间接使用WebSocketHandler API(例如通过 STOMP 消息传递)时,由于基础标准WebSocket会话(JSR-356)不允许并发发送,因此应用程序必须同步消息的发送。一种选择是用ConcurrentWebSocketSessionDecoratorWebSocketSession包裹起来 。

4.2.2。WebSocket握手

定制初始HTTP WebSocket握手请求的最简单方法是通过HandshakeInterceptor,它公开了“握手之前”和“握手之后”的方法。 您可以使用此类拦截器来避免握手或使任何属性对WebSocketSession可用。 以下示例使用内置的拦截器将HTTP会话属性传递到WebSocket会话:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyHandler(), "/myHandler")
            .addInterceptors(new HttpSessionHandshakeInterceptor());
    }

}

下面的示例显示与前面的示例等效的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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://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配置和XML名称空间都使配置自定义HandshakeHandler成为可能。

Spring提供了一个WebSocketHandlerDecorator基类,可用于装饰WebSocketHandler并具有其他行为。 使用WebSocket Java配置或XML名称空间时,默认情况下会提供并添加日志记录和异常处理实现。 ExceptionWebSocketHandlerDecorator捕获由任何WebSocketHandler方法引起的所有未捕获的异常,并关闭状态为1011(指示服务器错误)的WebSocket会话。

4.2.3. Deployment

Spring WebSocket API易于集成到Spring MVC应用程序中,在该应用程序中DispatcherServlet可以同时服务HTTP WebSocket握手和其他HTTP请求。通过调用WebSocketHttpRequestHandler也很容易集成到其他HTTP处理方案中。这是方便且易于理解的。但是,对于JSR-356运行时,需要特别注意。

Java WebSocket API(JSR-356)提供了两种部署机制。第一个涉及启动时的Servlet容器类路径扫描(Servlet 3功能)。另一个是在Servlet容器初始化时使用的注册API。这两种机制都无法将单个“前端控制器”用于所有HTTP处理(包括WebSocket握手和所有其他HTTP请求)(例如Spring MVC的DispatcherServlet)。

这是JSR-356的一个重大限制,即使在JSR-356运行时中运行,Spring的WebSocket支持也可以通过服务器特定的RequestUpgradeStrategy实现来解决。Tomcat,Jetty,GlassFish,WebLogic,WebSphere和Undertow(和WildFly)目前存在此类策略。

已经创建了克服Java WebSocket API中的上述限制的请求,可以在eclipse-ee4j/websocket-api#211中进行跟踪 。Tomcat,Undertow和WebSphere提供了自己的API替代方案,使之可以做到这一点,而Jetty也可以实现。我们希望更多的服务器可以做到这一点。

另一个要考虑的因素是,期望具有JSR-356支持的Servlet容器执行ServletContainerInitializer(SCI)扫描,这可能会减慢应用程序的启动速度-在某些情况下会大大降低。如果在升级到支持JSR-356的Servlet容器版本后观察到重大影响,则应该可以通过使用web.xml中的<absolute-ordering />元素来选择性地启用或禁用Web片段(和SCI扫描),如以下示例所示:

<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
        https://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
        https://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. Server Configuration

每个基础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;
    }

}

下面的示例显示与前面的示例等效的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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://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配置,应使用WebSocketContainerFactoryBean (XML)或ContainerProvider.getWebSocketContainer()(Java配置)。

对于Jetty,您需要提供一个预先配置的Jetty WebSocketServerFactory,然后通过WebSocket Java配置将其插入Spring的DefaultHandshakeHandler。以下示例显示了如何执行此操作:

@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)));
    }

}

下面的示例显示与前面的示例等效的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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://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。允许的来源(Allowed Origins)

从Spring Framework 4.1.5开始,WebSocket和SockJS的默认行为是仅接受同源请求。也可以允许所有或指定的来源列表。此检查主要用于浏览器客户端。没有任何措施可以阻止其他类型的客户端修改Origin标头值(有关更多详细信息,请参阅 RFC 6454:Web Origin概念)。

三种可能的行为是:

  • 仅允许相同来源的请求(默认):在此模式下,启用SockJS时,Iframe HTTP响应标头X-Frame-Options设置为SAMEORIGIN,并且JSONP传输被禁用,因为它不允许检查请求的来源。因此,启用此模式时,不支持IE6和IE7。
  • 允许指定来源列表:每个允许的来源必须以http://https://开头。在此模式下,启用SockJS时,将禁用IFrame传输。因此,启用此模式时,不支持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("https://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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers allowed-origins="https://mydomain.com">
        <websocket:mapping path="/myHandler" handler="myHandler" />
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>

4.3. SockJS Fallback

在公共Internet上,控件之外的限制性代理可能会阻止WebSocket交互,这可能是因为未将它们配置为传递Upgrade标头,或者是因为它们关闭了长期处于空闲状态的连接。

解决此问题的方法是WebSocket仿真—也就是说,先尝试使用WebSocket,然后再尝试使用基于HTTP的技术来模拟WebSocket交互并公开相同的应用程序级API。

在Servlet堆栈上,Spring框架为SockJS协议提供服务器(以及客户端)支持。

4.3.1。总览

4.3.2。启用SockJS

4.3.3。IE 8和9

4.3.4。心跳(Heartbeats)

4.3.5。客户端断开连接

4.3.6。SockJS和CORS

4.3.7。 SockJsClient

4.4. STOMP

5.其他Web框架

本章详细介绍了Spring与第三方Web框架的集成。

Spring框架的核心价值主张之一就是支持选择。从一般意义上讲,Spring不会强迫您使用或购买任何特定的体系结构,技术或方法(虽然肯定会推荐一些)。这种自由选择和选择与开发人员及其开发团队最相关的架构,技术或方法的自由在网络领域可以说是最明显的,在Web领域,Spring提供了自己的Web框架(Spring MVCSpring WebFlux)。同时,支持与许多流行的第三方Web框架集成。

5.1。通用配置

在深入研究每个受支持的Web框架的集成细节之前,让我们首先看一下并非特定于任何Web框架的通用Spring配置。(本节同样适用于Spring自己的Web框架变体。)

Spring的轻量级应用程序模型拥护的一个概念是分层体系结构。请记住,在“经典”分层体系结构中,Web层只是许多层之一。它充当服务器端应用程序的入口点之一,并且委派服务层中定义的服务对象(外观),以满足特定于业务(与表示技术无关)的用例。在Spring中,这些服务对象,任何其他特定于业务的对象,数据访问对象和其他对象都存在于不同的“业务上下文”中,其中不包含Web或表示层对象(表示对象,例如Spring MVC控制器,通常是在不同的“展示上下文”中进行配置)。本节详细介绍如何配置Spring容器( WebApplicationContext),其中包含应用程序中的所有“Business Bean”。

继续讲细节,您要做的就是在Web应用程序的标准Java EE servlet web.xml文件中声明ContextLoaderListener并添加 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 context参数,则 ContextLoaderListener 查找并加载一个名为/WEB-INF/applicationContext.xml 的文件。加载上下文文件后,Spring将根据Bean定义创建一个WebApplicationContext 对象,并将其存储在Web应用程序的ServletContext中。

所有Java Web框架都建立在Servlet API的基础上,因此您可以使用以下代码片段来访问由ContextLoaderListener创建的“业务上下文”ApplicationContext

以下示例显示了如何获取WebApplicationContext

WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);

WebApplicationContextUtils类是为了方便起见,因此您无需记住ServletContext属性的名称。 如果WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE键下不存在对象,则其getWebApplicationContext()方法返回null。 与其冒险在您的应用程序中获取NullPointerExceptions,不如使用getRequiredWebApplicationContext()方法。 当缺少ApplicationContext时,此方法将引发异常。

一旦有了对WebApplicationContext的引用,就可以按其名称或类型检索bean。大多数开发人员都按名称检索bean,然后将其转换为已实现的接口之一。

幸运的是,本节中的大多数框架都具有更简单的查找bean的方法。它们不仅使从Spring容器中获取bean变得容易,而且还使您可以在其控制器上使用依赖项注入。每个Web框架部分都有其特定集成策略的更多详细信息。

5.2。JSF

5.3。Apache Struts 2.x

5.4。Apache Tapestry 5.x

5.5。更多资源

以下链接提供了有关本章中描述的各种Web框架的更多资源。

猜你喜欢

转载自www.cnblogs.com/huangwenjie/p/12525091.html