开发Java教程Spring后端开发Spring Web开发指南:REST、WebSocket与CORSDiebug2026年08月25日 # Spring Web开发指南:REST、WebSocket与CORS > 本文基于DDKK.COM的Spring教程系列进行二次创作,原文链接:https://ddkk.com/category/j2ee/spring/1/index.html ## 20. CORS支持 ## 20.1 简介 出于安全考虑,浏览器禁止AJAX调用驻留在当前来源之外的资源。 例如,当您在一个标签中检查您的银行帐户时,您可以在另一个标签中打开evil.com网站。 evil.com的脚本不能使用您的凭据向您的银行API发出AJAX请求(例如,从您的帐户中提款)! Cross-origin resource sharing(CORS) 是 大多数浏览器实现的W3C规范,允许您以灵活的方式指定什么样的跨域请求被授权,而不是使用一些较不安全和不太强大的黑客工具(如IFRAME或JSONP)。 从Spring Framework 4.2开始,CORS支持开箱即用。. CORS 请求(including preflight ones with anOPTIONSmethod) 被自动调度到各种已注册的HandlerMappings. 由于CorsProcessor的实现(默认为DefaultCorsProcessor),为了根据您提供的CORS配置添加相关的CORS响应头(如Access-Control-Allow-Origin),它们处理CORS预检请求并拦截CORS简单实际的请求。 ## 20.2 Controller 方法CORS 配置 你如果想使用CORS ,可以在@RequestMapping上增加@CrossOrigin注解. 默认情况下@CrossOrigin允许@RequestMapping注释中指定的所有起始点和HTTP方法: @RestController @RequestMapping("/account") public class AccountController { @CrossOrigin @RequestMapping("/{id}") public Account retrieve(@PathVariable Long id) { // ... } @RequestMapping(method = RequestMethod.DELETE, path = "/{id}") public void remove(@PathVariable Long id) { // ... } } 对于整个 controller的CORS 的支持: @CrossOrigin(origins = "http://domain2.com", maxAge = 3600) @RestController @RequestMapping("/account") public class AccountController { @RequestMapping("/{id}") public Account retrieve(@PathVariable Long id) { // ... } @RequestMapping(method = RequestMethod.DELETE, path = "/{id}") public void remove(@PathVariable Long id) { // ... } } 在上面的例子中,retrieve() 和 remove()对实现了对CORS 的支持,因此你可以使用@CrossOrigin 属性对自已的程序进行自定义的CORS 配置。 您甚至可以同时使用控制器级和方法级的CORS配置; 然后,Spring将组合两个注释的属性以创建合并的CORS配置。 @CrossOrigin(maxAge = 3600) @RestController @RequestMapping("/account") public class AccountController { @CrossOrigin("http://domain2.com") @RequestMapping("/{id}") public Account retrieve(@PathVariable Long id) { // ... } @RequestMapping(method = RequestMethod.DELETE, path = "/{id}") public void remove(@PathVariable Long id) { // ... } } 20.3 全局CORS 配置 除了细粒度,基于注释的配置,您也可能想要定义一些全局CORS配置。 这与使用过滤器类似,但可以在Spring MVC中声明,并结合细粒度的@CrossOrigin配置。 默认情况下,所有起始点和GET,HEAD和POST方法都是允许的。 ## 20.3.1 JavaConfig 配置全局CORS 如下所示: @Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**"); } } 你也可以轻易的改变任意属性,以及仅将此CORS配置应用于特定路径模式: @Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://domain2.com") .allowedMethods("PUT", "DELETE") .allowedHeaders("header1", "header2", "header3") .exposedHeaders("header1", "header2") .allowCredentials(false).maxAge(3600); } } 20.3.2 XML 命名空间 以下最小的XML配置为/ **路径模式启用CORS,具有与上述JavaConfig示例相同的默认属性: <mvc:cors> <mvc:mapping path="/**" /> </mvc:cors> 也可以使用自定义属性声明几个CORS映射: <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="false" max-age="123" /> <mvc:mapping path="/resources/**" allowed-origins="http://domain1.com" /> </mvc:cors> 20.4 高级自定义 CorsConfiguration 允许你定义CORS 请求怎样被处理: 允许origins, headers, methods, 如.:他可以使用以下几种方式处理: AbstractHandlerMapping#setCorsConfiguration()允许指定映射到路径模式(如/ api / **)的几个 CorsConfiguration实例的Map 子类通过重载AbstractHandlerMapping#getCorsConfiguration(Object, HttpServletRequest)方法提供自已的CorsConfiguration. Handlers 通过实现 CorsConfigurationSource接口(像ResourceHttpRequestHandler现在做的一样) 对每个请求提供一个CorsConfiguration 实例. ## 20.5 基于 CORS 支持的过滤 为了支持基于过滤器的安全框架(如Spring Security)的CORS,或者与其他不支持本地CORS的库一起支持CORS,Spring Framework还提供了一个 CorsFilter. 而不是使用@CrossOrigin或WebMvcConfigurer#addCorsMappings(CorsRegistry),您需要注册一个自定义过滤器,如下所示: import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; public class MyCorsFilter extends CorsFilter { public MyCorsFilter() { super(configurationSource()); } private static UrlBasedCorsConfigurationSource configurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("http://domain1.com"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } } 您需要确保CorsFilter在其他过滤器之前过滤, 请参考this blog post ,了解如何配置Spring Boot。 ## 21. 与其他Web框架集成 ## 21.1 简介 ## Spring Web Flow Spring Web Flow (SWF) 旨在成为管理Web应用程序页面流的最佳解决方案。 SWF与Servlet和Portlet环境中的Spring MVC和JSF等现有框架集成。 如果您有一个业务流程(或流程)将受益于会话模型而不是纯粹的请求模型,则SWF可能是解决方案。 SWF允许您将逻辑页面流作为在不同情况下可重用的自包含模块捕获,因此非常适合构建引导用户通过驱动业务流程的受控导航的Web应用程序模块。 有关SWF的更多信息,请参阅Spring Web Flow website. 本章详细介绍了Spring与第三方Web框架的集成,如 JSF. Spring框架的核心价值主张之一是自由选择。在一般意义上,Spring并不强迫某人使用或购买任何特定的架构,技术或方法(尽管它肯定建议其他人使用)。这种选择与开发人员及其开发团队最相关的架构,技术或方法的自由可以说是在Web领域最为明显的一个领域,Spring领域同时提供了自己的Web框架(Spring MVC)提供与许多受欢迎的第三方网络框架的集成。这允许人们继续利用在特定的Web框架(如JSF)中获得的任何和所有技能,同时能够享受Spring在其他领域(如数据访问,声明式交易)管理,灵活配置和应用程序组装。 放弃了the woolly sales patter(c.f.前一段),本章的其余部分将集中在Web框架与Spring集成的细节。开发人员从其他语言开始Java经常评论的一件事就是Java中可用的Web框架的丰富程度。 Java中确实有大量的Web框架;事实上,在一个章节中有太多的东西可以覆盖任何细节的外表。本章从Java中选出了四个更受欢迎的Web框架,从所有支持的Web框架通用的Spring配置开始,然后详细说明每个支持的Web框架的特定集成选项。 请注意,本章不会尝试解释如何使用任何受支持的Web框架。例如,如果要将JSF用于Web应用程序的表示层,那么假设您已经熟悉了JSF本身。如果您需要有关任何支持的Web框架本身的更多详细信息,请参阅本章末尾 Section 21.6, “Further Resources” . ## 21.2 普通配置 在介绍每个支持的Web框架的集成细节之前,我们首先来看看Spring配置,这些配置不是特定于任何一个Web框架的。 (本节同样适用于Spring自己的Web框架,Spring MVC。) (Spring’s)轻量级应用程序模型支持的一个概念(为了想要更好的词)就是分层架构。请记住,在“经典”分层架构中,网络层只是多层次之一;它作为服务器端应用程序的入口点之一,它委托服务层定义的服务对象(外观),以满足业务特定(和表示技术不可知)用例。在Spring中,这些服务对象,任何其他特定于业务的对象,数据访问对象等存在于不同的“业务上下文”中,其不包含web或表示层对象(诸如Spring MVC控制器之类的呈现对象通常在独特的“演示文稿”)。本节详细介绍了如何在一个应用程序中配置包含所有“business bean”的Spring容器(WebApplicationContext)。 具体细节:所有需要做的是在一个人的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上下文参数,ContextLoaderListener将查找一个名为/WEB-INF/applicationContext.xml的文件加载。一旦加载了上下文文件,Spring将基于bean定义创建一个WebApplicationContext对象,并将其存储在Web应用程序的ServletContext中。 所有Java Web框架都构建在Servlet API之上,因此可以使用以下代码片段来访问由ContextLoaderListener创建的“business context”ApplicationContext。 WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext); TheWebApplicationContextUtils类是为了方便起见,因此您不必记住ServletContext属性的名称。如果WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE键下不存在一个对象,它的getWebApplicationContext()方法将返回null。而不是在应用程序中获得NullPointerExceptions的风险,最好使用getRequiredWebApplicationContext()方法。当ApplicationContext丢失时,此方法抛出异常。 一旦你引用了WebApplicationContext,你可以通过它们的名字或类型检索bean。大多数开发人员通过名称检索bean,然后将其转换为其实现的接口之一. 幸运的是,本节中的大部分框架具有更简单的查找bean的方法。它不仅使得从Spring容器获取bean变得容易,而且还允许您在其控制器上使用依赖注入。每个Web框架部分都有其具体整合策略的更多细节。 ## 21.3 JavaServer Faces 1.2 JavaServer Faces(JSF)是JCP的标准组件,事件驱动的Web用户界面框架。 从Java EE 5开始,它是Java EE umbrella的官方部分. 对于受欢迎的JSF运行时以及流行的JSF组件库,请查看 Apache MyFaces project. MyFaces项目还提供常见的JSF扩展 ,比如MyFaces Orchestra: 一个基于Spring的JSF扩展,可以提供丰富的会话范围支持。 Spring Web Flow 2.0通过其新建立的Spring Faces模块提供丰富的JSF支持,无论是以JSF为中心的用途(如本节所述)和Spring中心使用(在Spring MVC调度程序中使用JSF视图)。 查看 Spring Web Flow website 了解详情! Spring的JSF集成中的关键要素是JSF ELResolver机制。 ## 21.3.1 SpringBeanFacesELResolver (JSF 1.2+) SpringBeanFacesELResolver是符合JSF 1.2的ELResolver实现, 与JSF 1.2和JSP 2.1使用的标准Unified EL集成像SpringBeanVariableResolver一样,它首先将Spring的“business context”WebApplicationContext委托给基于JSF实现的默认解析器. 在配置方面,只需在JSF 1.2 faces-context.xml文件中定义SpringBeanFacesELResolver: <faces-config> <application> <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver> ... </application> </faces-config> 21.3.2 FacesContextUtils 当将属性映射到faces-config.xml中的bean时,自定义VariableResolver可以正常工作,但有时可能需要明确地获取一个bean,TheFacesContextUtils类使这个变得容易. 它类似于WebApplicationContextUtils,除了它需要一个FacesContext参数,而不是一个ServletContext参数. ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance()); 21.4 Apache Struts 2.x Craig McClanahan创建,Struts是由Apache Software Foundation托管的一个开源项目。 当时,它大大简化了JSP / Servlet编程范例,并赢得了许多正在使用专有框架的开发人员。 它简化了编程模式,它是开放源代码(因此像啤酒一样自由),它拥有一个大型社区,这使得该项目在Java Web开发人员中成长和流行. 查看Strut Spring Plugin ,用于Struts附带的内置Spring集成。 ## 21.5 Tapestry 5.x 查看Tapestry homepage: Tapestry是一个“面向组件的框架,用于在Java中创建动态,强大,高度可扩展的Web应用程序”。 虽然Spring拥有自已强大的 powerful web layer, 但是通过组合Tapestry用于Web用户界面和用于较低层的Spring容器来构建企业Java应用程序,有许多独特的优势。 有关更多信息,请查看 integration module for Spring. ## 21.6 Further Resources 关于这章节各种Web框架的详解介绍,请点击下面的链接. The JSF homepage The Struts homepage The Tapestry homepage ## 22. WebSocket支持 参考文档的这一部分涵盖了Spring框架对Web应用程序中WebSocket风格消息传递的支持,包括使用STOMP作为应用程序级WebSocket子协议。 Section 22.1, “Introduction” 建立一个WebSocket的大致框架,涵盖应用挑战,设计考虑以及何时适合的想法。 Section 22.2,“WebSocket API” 介绍了服务端的Spring WebSocket API,Section 22.3,“SockJS Fallback Options” 介绍了SockJS 协议,并且展示如何配置和使用它. Section 22.4.1, “Overview of STOMP” 介绍 STOMP 信息协议. Section 22.4.2, “Enable STOMP over WebSocket” 展示如何在Spring配置STOMP. Section 22.4.4, “Annotation Message Handling” 以下部分说明如何编写注释消息处理方法,发送消息,选择消息代理选项,以及与特殊“用户”目的地的工作. 最后, Section 22.4.18,“Testing Annotated Controller Methods” 列出了测试STOMP / WebSocket应用程序的三种方法. ## 22.1 介绍 对于web应用程序,WebSocket 协议RFC 6455定义了一个很重要的功能:全双工,客户端与服务器之间的双向通信. 这是一个令人兴奋的新功能,在漫长的技术历史上,使Web更具交互性,包括Java Applet,XMLHttpRequest,Adobe Flash,ActiveXObject,各种Comet 技术,服务器发送的事件等. WebSocket协议的介绍超出了本文档的范围。但是,至少要了解,HTTP仅用于初始握手,这依赖于内置于HTTP中的机制来请求协议升级(或在这种情况下为协议交换机),服务器可以使用HTTP状态101对其进行响应 (切换协议)如果它同意。 假设握手成功,HTTP升级请求下面的TCP套接字保持打开,客户端和服务器都可以使用它来彼此发送消息. Spring Framework 4包括一个全新的WebSocket支持的spring-websocket模块。它与Java WebSocket API标准(JSR-356)兼容,并且还提供额外的附加值,如在其余介绍中所述。 ## 22.1.1 WebSocket 后备选项 采用WebSocket 一个重要的挑战是在一些浏览器中缺乏对其的支持,值得注意的是, IE 第一个支持WebSocket 的版本是10 (详情请参照http://caniuse.com/websockets ). 更多,一些限制性代理可以配置为阻止尝试执行HTTP升级或在一段时间后断开连接,因为它已经打开了太久. InfoQ的文章e“How HTML5 Web Sockets Interact With Proxy Servers”中提供了Peter Lubbers对此主题的一个很好的概述。 因此,为了今天构建一个WebSocket应用程序,需要后备选项才能在必要时模拟WebSocket API。 Spring Framework提供了基于SockJS协议的透明后备选项。 这些选项可以通过配置启用,不需要修改应用程序. ## 22.1.2 消息架构 除了中短期面临的挑战之外,使用WebSocket可以提出重要的设计注意事项,这对于早期的认识至关重要,特别是与我们今天建立Web应用程序相关的知识。 今天REST风格在Web应用中广受欢迎,这是一种依赖于许多URL(资源),少数HTTP方法(动词)以及诸如使用超媒体(链接),以及无状态架构。. 相比之下,WebSocket应用程序可能仅使用单个URL进行初始HTTP握手。此后,所有消息共享并在相同的TCP连接上流动。这指向一个完全不同的,异步的,事件驱动的消息架构。更接近于传统消息传递应用 (如:JMS,AMQP). Spring Framework 4包括一个新的 spring-messaging 模块 ,其中包含Spring Integration 项目的的关键抽象,例如 Message, MessageChannel, MessageHandler以及其他可以座位消息架构的基础, 该模块还包括一组用于将消息映射到方法的注释,类似于基于Spring MVC注释的编程模型。 ## 22.1.3 WebSocket中的子协议支持 WebSocket确实建立了消息架构,但并不要求使用任何特定的消息协议。它是一个非常窄的TCP层,将字节流转换为消息流(文本或二进制),而不是更多。应用来解释消息的含义。 不同于HTTP(它是应用程序级协议),在WebSocket协议中,框架或容器的传入消息中没有足够的信息来知道如何路由或处理它。因此,WebSocket可以说是太低级别,只是一个非常简单的应用程序。可以做到这一点,但它可能会导致在顶部创建一个框架。这与目前使用Web框架而不是单独使用Servlet API的大多数Web应用程序相当。 为此,WebSocket RFC定义了子协议的使用。在握手期间,客户端和服务器可以使用头部Sec-WebSocket协议来同意子协议,即较高的应用级协议使用。不需要使用子协议,即使不使用子协议,应用程序仍然需要选择客户端和服务器可以理解的消息格式。该格式可以是自定义,框架特定或标准消息传递协议。 Spring框架支持使用STOMP – 一种简单的消息传递协议,最初创建用于脚本语言,并由HTTP启发的框架。 STOMP被广泛支持,非常适合在WebSocket和Web上使用。 ## 22.1.4 我应该使用WebSocket? 有关使用WebSocket的所有设计考虑,思考“什么时候使用?”是合理的。 WebSocket最适合在Web应用程序中,客户端和服务器需要以高频率和低延迟交换事件。优选的项目类别包括但不限于在金融,游戏,合作等方面的应用。这种应用对时间延迟非常敏感,并且还需要以高频率交换各种各样的消息。 但是,对于其他应用程序类型,可能并非如此。例如,一个新闻或社交软件显示突发新闻,因为它可用可能是完全可以的简单的轮询一次每隔几分钟。这里的延迟很重要,但是如果新闻需要几分钟的时间就可以接受。 即使在延迟至关重要的情况下,如果消息量相对较低(例如监控网络故障),则长时间轮询的使用应被视为一种相对简单的替代方案,其可靠性可靠,并且在效率方面是可比较的的消息相对较低)。 低延迟和高频率的消息可以使WebSocket协议的使用成为关键。即使在这样的应用程序中,选择仍然是所有客户端 – 服务器通信是否应该通过WebSocket消息完成,而不是使用HTTP和REST。答案将因应用而异;然而,有可能某些功能可以通过WebSocket和REST API来暴露,以便为客户提供替代方案。此外,REST API调用可能需要向通过WebSocket连接的感兴趣的客户端广播消息。 Spring Framework允许@Controller和@RestController类具有HTTP请求处理和WebSocket消息处理方法。此外,Spring MVC请求处理方法或任何应用方法可以轻松地向所有感兴趣的WebSocket客户端或特定用户广播消息。 ## 22.2 WebSocket API TheSpring架构提供的WebSocket API 被设计成应用于各类WebSocket 引擎. 当前这个列表包括WebSocket 运行时 ,例如 Tomcat 7.0.47+, Jetty 9.1+, GlassFish 4.1+, WebLogic 12.1.3+, and Undertow 1.0+ (and WildFly 8.0+). 随着更多的WebSocket运行时可用,可能会添加额外的支持。 ## 22.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。 在WebSocketHttpRequestHandler的帮助下 ,将WebSocketHttpRequestHandler集成到其他HTTP服务环境中相对简单. ## 22.2.2 自定义he WebSocket 握手 自定义初始HTTP WebSocket握手请求的最简单的方法是通过HandshakeInterceptor,它将握手方法之前的“before”和“after”。 这样的拦截器可以用于阻止握手或使任何属性可用于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 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> 更高级的选项是扩展执行WebSocket握手步骤的DefaultHandshakeHandler,包括验证客户端,协商子协议等。 如果需要配置自定义RequestUpgradeStrategy以适应不支持的WebSocket服务器引擎和版本,应用程序也可能需要使用此选项(有关此主题的更多信息,请参见第22.2.4节“部署注意事项”))。 Java-config和XML命名空间都可以配置自定义HandshakeHandler。 ## 22.2.3 WebSocketHandler 装饰 Spring提供了一个WebSocketHandlerDecorator基类,可用于使用附加行为来装饰WebSocketHandler。 在使用WebSocket Java-config或XML命名空间时,默认情况下提供并添加了日志记录和异常处理实现。 ExceptionWebSocketHandlerDecorator捕获从任何WebSocketHandler方法引发的所有未捕获的异常,并关闭表示服务器错误的状态1011的WebSocket会话。 ## 22.2.4 部署注意事项 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运行时运行时,通过提供特定于服务器的RequestUpgradeStrategy,Spring的WebSocket支持的JSR-356的一个重大限制。 第二个考虑因素是具有JSR-356支持的Servlet容器预计将执行ServletContainerInitializer(SCI)扫描,这可能会减慢应用程序启动速度,在某些情况下会显着降低。 如果在升级到支持JSR-356的Servlet容器版本之后观察到重大影响,则可以通过使用Web.XML中的 元素来选择性地启用或禁用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 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> 22.2.5 配置WebSocket 引擎 每个底层WebSocket引擎都会公开控制运行时特性的配置属性,例如消息缓冲区大小,空闲超时等。 对于Tomcat,WildFly和GlassFish,在您的WebSocket Java配置中添加一个ServletServerContainerFactoryBean: @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> 对于Jetty, 你需要通过WebSocket Java config预先配置Jetty WebSocketServerFactory 和插件注入到Spring’s 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))); } } 或者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> 22.2.6允许域名配置 作为Spring Framework 4.1.5, the default behavior for WebSocket 和SockJS默认的行为仅仅是接收相同的域名请求 . 也可以允许所有或指定的域名列表。 此检查主要是为浏览器客户端设计的。 没有什么可以阻止其他类型的客户端修改Origin标头值(需要查看更多详情:RFC 6454: The Web Origin Concept). 只允许相同的域名请求(默认):在此模式下,当启用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> 22.3 SockJS后备选项 正如简介所说,As explained in the introduction,WebSocket is not supported in all browsers yet and may be precluded by restrictive network proxies. This is why Spring provides fallback options that emulate the WebSocket API as close as possible based on the SockJS protocol (version 0.3.3). ## 24.1 介绍 Spring提供了使用多种技术实现远程访问支持的集成类。远程访问支持使得具有远程访问功能的服务开发变得相当简单,而这些服务由普通的 (Spring) POJO实现。目前,Spring支持以下几种远程技术: 远程方法调用(RMI)。通过使用RmiProxyFactoryBean和RmiServiceExporter,Spring同时支持传统的RMI(与java.rmi.Remote接口和java.rmi.RemoteException配合使用)和通过RMI调用器的透明远程调用(透明远程调用可以使用任何Java接口)。 Spring的HTTP调用器。Spring提供了一个特殊的远程处理策略,允许通过HTTP进行Java序列化,支持任何Java接口(就像RMI调用器)。相应的支持类是HttpInvokerProxyFactoryBean和HttpInvokerServiceExporter。 Hessian。通过HessianProxyFactoryBean和HessianServiceExporter,可以使用Caucho提供的基于HTTP的轻量级二进制协议来透明地暴露服务。 JAX-WS。Spring通过JAX-WS为web服务提供远程访问支持。(JAX-WS: 从Java EE 5 和 Java 6开始引入,作为JAX-RPC的继承者) JMS。通过JmsInvokerServiceExporter和JmsInvokerProxyFacotryBean类,使用JMS作为底层协议来提供远程服务。 AMQP。Spring AMQP项目支持AMQP作为底层协议来提供远程服务。 在讨论Spring的远程服务功能时,我们将使用以下的域模型和对应的服务: public class Account implements Serializable{ private String name; public String getName(){ return name; } public void setName(String name) { this.name = name; } } public interface AccountService { public void insertAccount(Account account); public List<Account> getAccounts(String name); } // the implementation doing nothing at the moment public class AccountServiceImpl implements AccountService { public void insertAccount(Account acc) { // do something... } public List<Account> getAccounts(String name) { // do something... } } 我们将从使用RMI把服务暴露给远程客户端开始,同时讨论使用RMI的一些缺点。然后我们将继续演示一个使用Hessian的例子。 ## 24.2 使用RMI暴露服务 使用Spring的RMI支持,你可以通过RMI基础架构透明地暴露你的服务。完成Spring的RMI设置后,你基本上具有类似于远程EJB配 置,除了没有对安全上下文传递和远程事务传递的标准支持。当使用RMI调用器时,Spring对这些额外的调用上下文提供了钩子,你可以在此插入安全框架 或者自定义的安全凭证。 ## 24.2.1 使用RmiServiceExporter导出服务 使用RmiServiceExporter,我们可以把AccountService对象的接口暴露成RMI对象。可以使用RmiProxyFactoryBean或者在传统RMI服务中使用普通RMI来访问该接口。RmiServiceExporter明确支持使用RMI调用器暴露任何非RMI的服务。 当然,我们首先需要在Spring容器中设置我们的服务: <bean id="accountService" class="example.AccountServiceImpl"> <!-- any additional properties, maybe a DAO? --> </bean> 下一步我们需要使用RmiServiceExporter来暴露我们的服务: <bean class="org.springframework.remoting.rmi.RmiServiceExporter"> <!-- does not necessarily have to be the same name as the bean to be exported --> <property name="serviceName" value="AccountService"/> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> <!-- defaults to 1099 --> <property name="registryPort" value="1199"/> </bean> 正如你所见,我们覆盖了RMI注册的端口号。通常你的应用服务器还维护一个RMI注册表,明智的做法是不要和它冲突。此外,服务名是用来绑定服务的。现在服务绑定在‘rmi://HOST:1199/AccountService’。我们将在客户端使用这个URL来链接到服务。 Note:servicePort属性被省略了(默认值为0).这表示在与服务通信时将使用匿名端口. 24.2.2 在客户端链接服务 我们的客户端是一个使用AccountService来管理account的简单对象: public class SimpleObject { private AccountService accountService; public void setAccountService(AccountService accountService) { this.accountService = accountService; } // additional methods using the accountService } 为了把服务链接到客户端上,我们将创建一个单独的Spring容器,包含这个简单对象和链接配置位的服务: <bean class="example.SimpleObject"> <property name="accountService" ref="accountService"/> </bean> <bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean"> <property name="serviceUrl" value="rmi://HOST:1199/AccountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> 这就是我们为支持远程account服务在客户端所需要做的。Spring将透明地创建一个调用器并且通过RmiServiceExporter使得account服务支持远程服务。在客户端,我们用RmiProxyFactoryBean连接它。 ## 24.3 使用Hessian通过HTTP远程调用服务 Hessian提供一种基于HTTP的二进制远程协议。它由Caucho开发的,可以在 http://www.caucho.com 找到更多有关Hessian的信息。 ## 24.3.1 为Hessian和co.配置DispatcherServlet Hessian使用一个自定义Servlet通过HTTP进行通讯。使用Spring的DispatcherServlet原理,从Spring Web MVC使用中可以看出,可以很容易的配置这样一个Servlet来暴露你的服务。首先我们要在你的应用里创建一个新的Servlet(以下摘录自web.xml): <servlet> <servlet-name>remoting</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>remoting</servlet-name> <url-pattern>/remoting/*</url-pattern> </servlet-mapping> 你可能对Spring的DispatcherServlet很熟悉,这样你将需要在’WEB-INF’目录中创建一个名为’remoting-servlet.xml’(在你的servlet名称后) 的Spring容器配置上下文。这个应用上下文将在下一节中里使用。 或者,可以考虑使用Spring中更简单的HttpRequestHandlerServlet。这允许你在根应用上下文(默认是’WEB-INF/applicationContext.xml’)中嵌入远程exporter定义。每个servlet定义指向特定的exporter bean。在这种情况下,每个servlet的名称需要和目标exporter bean的名称相匹配。 ## 24.3.2 使用HessianServiceExporter暴露你的bean 在新创建的remoting-servlet.xml应用上下文里,我们将创建一个HessianServiceExporter来暴露你的服务: <bean id="accountService" class="example.AccountServiceImpl"> <!-- any additional properties, maybe a DAO? --> </bean> <bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> 现在我们准备好在客户端连接服务了。不必显示指定处理器的映射,所以使用BeanNameUrlHandlerMapping把URL请求映射到服务上:因此,服务将通过其包含的bean名称指定的URL导出 DispatcherServlet’s mapping (as defined above): ’http://HOST:8080/remoting/AccountService’ 或者, 在你的根应用上下文中创建一个HessianServiceExporter(比如在’WEB-INF/applicationContext.xml’中): <bean name="accountExporter" class="org.springframework.remoting.caucho.HessianServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> 在后一情况下, 在’web.xml’中为这个导出器定义一个相应的servlet,也能得到同样的结果:这个导出器映射到request路径/remoting/AccountService。注意这个servlet名称需要与目标导出器bean的名称相匹配。 <servlet> <servlet-name>accountExporter</servlet-name> <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>accountExporter</servlet-name> <url-pattern>/remoting/AccountService</url-pattern> </servlet-mapping> 24.3.3 在客户端上链接服务 使用HessianProxyFactoryBean,我们可以在客户端链接服务。与RMI示例一样也适用相同的原理。我们将创建一个单独的bean工厂或者应用上下文,并指明SimpleObject使用AccountService来管理accounts的以下bean: <bean class="example.SimpleObject"> <property name="accountService" ref="accountService"/> </bean> <bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean"> <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> 24.3.4 对通过Hessian暴露的服务使用HTTP基本认证 Hessian的优点之一是,我们可以轻松应用HTTP基本身份验证,因为这两种协议都是基于HTTP的。你的正常HTTP 服务器安全机制可以通过使用web.xml安全功能来应用。通常,你不会为每个用户都建立不同的安全证书,而是在Hessian/BurlapProxyFactoryBean级别共享安全证书(类似一个JDBCDataSource)。 <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"> <property name="interceptors" ref="authorizationInterceptor"/> </bean> <bean id="authorizationInterceptor" class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor"> <property name="authorizedRoles" value="administrator,operator"/> </bean> 这个是我们显式使用了BeanNameUrlHandlerMapping的例子,并设置了一个拦截器,只允许管理员和操作员调用这个应用上下文中提及的bean。 Note: 当然,这个例子并不表现出灵活的安全架构。有关安全性方面的更多选项,请查看Spring Security项目http://projects.spring.io/spring-security/。 ## 24.4 使用HTTP调用器暴露服务 与使用自身序列化机制的轻量级协议Hessian相反,Spring HTTP调用器使用标准Java序列化机制通过HTTP暴露业务。如果你的参数或返回值是复杂类型,并且不能通过Hessian的序列化机制进行序列化,HTTP调用器就很有优势(请参阅下一节,以便在选择远程处理技术时进行更多考虑)。 在底层,Spring使用JDK提供的标准工具或Commons的HttpComponents来实现HTTP调用。如果你需要更先进和更易用的功能,请使用后者。你可以参考 hc.apache.org/httpcomponents-client-ga/ 以获取更多信息。 ## 24.4.1 暴露服务对象 为服务对象设置HTTP调用器基础架构类似于使用Hessian进行相同操作的方式。就象为Hessian支持提供的HessianServiceExporter Spring的HTTP调用器提供了org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter。 为了在Spring Web MVC的DispatcherServlet中暴露AccountService(之前章节提及过), 需要在调度程序的应用程序上下文中使用以下配置: <bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> 如Hessian章节部分所述,这个导出器定义将通过DispatcherServlet的标准映射工具暴露出来。 或者, 在你的根应用上下文中(比如’WEB-INF/applicationContext.xml’)创建一个HttpInvokerServiceExporter: <bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> 此外,在’web.xml’中为该导出器定义相应的servlet ,其中servlet名称与目标导出器的bean名称相匹配: <servlet> <servlet-name>accountExporter</servlet-name> <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>accountExporter</servlet-name> <url-pattern>/remoting/AccountService</url-pattern> </servlet-mapping> 如果你在一个servlet容器之外运行程序和使用Oracle的Java6, 那么你可以使用内置的HTTP服务器实现。你可以配置SimpleHttpServerFactoryBean和SimpleHttpInvokerServiceExporter在一起,像下面这个例子一样: <bean name="accountExporter" class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter"> <property name="service" ref="accountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> <bean id="httpServer" class="org.springframework.remoting.support.SimpleHttpServerFactoryBean"> <property name="contexts"> <util:map> <entry key="/remoting/AccountService" value-ref="accountExporter"/> </util:map> </property> <property name="port" value="8080" /> </bean> 24.4.2 在客户端连接服务 同样,从客户端连接业务与你使用Hessian所做的很相似。使用代理,Spring可以将你的HTTP POST调用请求转换成被暴露服务的URL。 <bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"> <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/> <property name="serviceInterface" value="example.AccountService"/> </bean> 如前所述,你可以选择要使用的HTTP客户端。默认情况下,HttpInvokerProxy使用JDK的HTTP功能,但你也可以通过设置httpInvokerRequestExecutor属性来使用ApacheHttpComponents客户端: <property name="httpInvokerRequestExecutor"> <bean class="org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor"/> </property> ## 24.5 Web 服务 Spring提供了对标准Java Web服务API的全面支持: 使用JAX-WS暴露Web服务 使用JAX-WS访问Web服务 除了在Spring Core中支持 JAX-WS,Spring portfolio也提供了一种特性Spring Web Services,一种为契约优先和文档驱动的web服务所提供的方案,强烈建议用来创建现代化的,面向未来的web服务。 ## 24.5.1使用JAX- WS暴露基于servlet的web服务 Spring为JAX-WS servlet的端点实现提供了一个方便的基类 – SpringBeanAutowiringSupport. 为了暴露我们的AccountService,我们扩展Spring的SpringBeanAutowiringSupport类并在这里实现了我们的业务逻辑,通常委派调用业务层。我们在Spring管理的bean里面简单地使用Spring的@Autowired 注解来表达这样的依赖关系。 /** * JAX-WS compliant AccountService implementation that simply delegates * to the AccountService implementation in the root web application context. * * This wrapper class is necessary because JAX-WS requires working with dedicated * endpoint classes. If an existing service needs to be exported, a wrapper that * extends SpringBeanAutowiringSupport for simple Spring bean autowiring (through * the @Autowired annotation) is the simplest JAX-WS compliant way. * * This is the class registered with the server-side JAX-WS implementation. * In the case of a Java EE 5 server, this would simply be defined as a servlet * in web.xml, with the server detecting that this is a JAX-WS endpoint and reacting * accordingly. The servlet name usually needs to match the specified WS service name. * * The web service engine manages the lifecycle of instances of this class. * Spring bean references will just be wired in here. */ import org.springframework.web.context.support.SpringBeanAutowiringSupport; @WebService(serviceName="AccountService") public class AccountServiceEndpoint extends SpringBeanAutowiringSupport { @Autowired private AccountService biz; @WebMethod public void insertAccount(Account acc) { biz.insertAccount(acc); } @WebMethod public Account[] getAccounts(String name) { return biz.getAccounts(name); } } 我们的AccountServletEndpoint需要和Spring在同一个上下文的web应用里运行,以允许访问Spring的功能。为JAX-WS servlet端点部署使用标准规约是Java EE 5 环境下的默认情况。 ## 24.5.2 使用JAX-WS暴露单独web服务 Oracle JDK 1.6附带的内置JAX-WS provider 使用内置的HTTP服务器来暴露web服务。Spring的SimpleJaxWsServiceExporter类检测所有在Spring应用上下文中配置有@WebService注解的bean,然后通过默认的JAX-WS服务器(JDK 1.6 HTTP服务器)导出。 在这种场景下,端点实例将被作为Spring bean来定义和管理。它们将使用JAX-WS引擎来注册,但其生命周期将由Spring应用程序上下文决定。这意味着Spring的显示依赖注入可用于端点实例。当然通过@Autowired来进行注解驱动的注入也会起作用。 <bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter"> <property name="baseAddress" value="http://localhost:8080/"/> </bean> <bean id="accountServiceEndpoint" class="example.AccountServiceEndpoint"> ... </bean> ... AccountServiceEndpoint可能来自于Spring的SpringBeanAutowiringSupport,也可能不是。因为这里的端点是由Spring完全管理的bean。这意味着端点实现可能像下面这样没有任何父类定义 – 而且Spring的@Autowired配置注解仍然能够使用: @WebService(serviceName="AccountService") public class AccountServiceEndpoint { @Autowired private AccountService biz; @WebMethod public void insertAccount(Account acc) { biz.insertAccount(acc); } @WebMethod public List<Account> getAccounts(String name) { return biz.getAccounts(name); } } 24.5.3 使用JAX-WS RI的Spring支持来暴露服务 Oracle的JAX-WS RI被作为GlassFish项目的一部分来开发,它使用了Spring支持来作为JAX-WS Commons项目的一部分。这允许把JAX-WS端点作为Spring管理的bean来定义。这与前面章节讨论的单独模式类似 – 但这次是在Servlet环境中。注意这在Java EE 5环境中是不可迁移的,建议在没有EE的web应用环境如Tomcat中嵌入JAX-WS RI。 与标准的暴露基于servlet的端点方式不同之处在于端点实例的生命周期将被Spring管理。这里在web.xml将只有一个JAX-WS servlet定义。在标准的Java EE 5风格中(如上所示),你将对每个服务端点定义一个servlet,每个服务端点都代理到Spring bean (通过使用@Autowired,如上所示)。 关于安装和使用详情请查阅https://jax-ws-commons.dev.java.net/spring/。 ## 24.5.4 使用JAX-WS访问web服务 Spring提供了2个工厂bean来创建JAX-WS web服务代理,它们是LocalJaxWsServiceFactoryBean和JaxWsPortProxyFactoryBean。前一个只能返回一个JAX-WS服务对象来让我们使用。后面的是可以返回我们业务服务接口的代理实现的完整版本。这个例子中我们使用后者来为AccountService端点再创建一个代理: <bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean"> <property name="serviceInterface" value="example.AccountService"/> <property name="wsdlDocumentUrl" value="http://localhost:8888/AccountServiceEndpoint?WSDL"/> <property name="namespaceUri" value="http://example/"/> <property name="serviceName" value="AccountService"/> <property name="portName" value="AccountServiceEndpointPort"/> </bean> serviceInterface是我们客户端将使用的远程业务接口。wsdlDocumentUrl是WSDL文件的URL. Spring需要用它作为启动点来创建JAX-WS服务。namespaceUri对应.wsdl文件中的targetNamespace。serviceName对应.wsdl文件中的服务名。portName对应.wsdl文件中的端口号。 现在我们可以很方便的访问web服务,因为我们有一个可以将它暴露为AccountService接口的bean工厂。我们可以在Spring中这样使用: <bean id="client" class="example.AccountClientImpl"> ... <property name="service" ref="accountWebService"/> </bean> 从客户端代码上我们可以把这个web服务当成一个普通的类进行访问: public class AccountClientImpl { private AccountService service; public void setService(AccountService service) { this.service = service; } public void foo() { service.insertAccount(...); } } Note: 上面例子被稍微简化了,因为JAX-WS需要端点接口及实现类来使用@WebService,@SOAPBinding等注解。 这意味着你不能简单地使用普通的Java接口和实现来作为JAX-WS端点,你需要首先对它们进行相应的注解。这些需求详情请查阅JAX-WS文档。 ## 24.6 JMS 使用JMS来作为底层的通信协议透明暴露服务也是可能的。Spring框架中对JMS的远程支持也很基础 – 它在同一线程和同一个非事务 Session上发送和接收,这些吞吐量将非常依赖于实现。需要注意的是这些单线程和非事务的约束仅适用于Spring的JMS远程处理支持。请参见 第26章, JMS (Java消息服务),Spring对基于JMS的消息的丰富支持。 下面的接口可同时用在服务端和客户端。 package com.foo; public interface CheckingAccountService { public void cancelAccount(Long accountId); } 对于上面接口的使用在服务的端简单实现如下: package com.foo; public class SimpleCheckingAccountService implements CheckingAccountService { public void cancelAccount(Long accountId) { System.out.println("Cancelling account [" + accountId + "]"); } } 这个包含JMS设施的bean的配置文件可同时用在客户端和服务端: <?xml version=”1.0″ encoding=”UTF-8″?> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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"> <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://ep-t43:61616"/> </bean> <bean id="queue" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg value="mmm"/> </bean> </beans> 24.6.1 服务端配置 在服务端你只需要使用JmsInvokerServiceExporter来暴露服务对象。 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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"> <bean id="checkingAccountService" class="org.springframework.jms.remoting.JmsInvokerServiceExporter"> <property name="serviceInterface" value="com.foo.CheckingAccountService"/> <property name="service"> <bean class="com.foo.SimpleCheckingAccountService"/> </property> </bean> <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destination" ref="queue"/> <property name="concurrentConsumers" value="3"/> <property name="messageListener" ref="checkingAccountService"/> </bean> </beans> package com.foo; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Server { public static void main(String[] args) throws Exception { new ClassPathXmlApplicationContext(new String[]{"com/foo/server.xml", "com/foo/jms.xml"}); } } 24.6.2 客户端配置 客户端只需要创建一个客户端代理来实现上面的接口(CheckingAccountService)。根据后面的bean定义创建的结果对象可以被注入到其它客户端对象中,而这个代理会负责通过JMS将调用转发到服务端。 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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"> <bean id="checkingAccountService" class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean"> <property name="serviceInterface" value="com.foo.CheckingAccountService"/> <property name="connectionFactory" ref="connectionFactory"/> <property name="queue" ref="queue"/> </bean> </beans> package com.foo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Client { public static void main(String[] args) throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext( new String[] {"com/foo/client.xml", "com/foo/jms.xml"}); CheckingAccountService service = (CheckingAccountService) ctx.getBean("checkingAccountService"); service.cancelAccount(new Long(10)); } } ## 24.7 AMQP 有关更多信息,请参考Spring AMQP Reference Document ‘Spring Remoting with AMQP’ section。 --- *本文基于DDKK.COM的Spring教程系列进行二次创作* *原文链接:https://ddkk.com/zhuanlan/j2ee/spring/1/81.html*