javascript
Jetty 9.1上的Java WebSockets(JSR-356)
最終發(fā)布了Jetty 9.1 ,將Java WebSockets(JSR-356)引入了非EE環(huán)境。 這真是個好消息,今天的帖子將介紹如何將這個出色的新API與Spring Framework一起使用。
JSR-356定義了基于注釋的簡潔模型,以允許現(xiàn)代Java Web應用程序使用WebSockets API輕松創(chuàng)建雙向通信通道。 它不僅涵蓋服務器端,還涵蓋客戶端端,這使得該API真正易于在任何地方使用。
讓我們開始吧! 我們的目標是構建一個WebSockets服務器,該服務器接受來自客戶端的消息并將其廣播到當前連接的所有其他客戶端。 首先,讓我們定義消息格式,作為此簡單的Message類,它將交換服務器和客戶端。 我們可以將自己限制為String之類的東西,但我想向您介紹另一個新API的功能– 用于JSON處理的Java API(JSR-353) 。
package com.example.services;public class Message {private String username;private String message;public Message() {}public Message( final String username, final String message ) {this.username = username;this.message = message;}public String getMessage() {return message;}public String getUsername() {return username;}public void setMessage( final String message ) {this.message = message;}public void setUsername( final String username ) {this.username = username;} }為了分隔與服務器和客戶端有關的聲明, JSR-356定義了兩個基本注釋:分別為@ServerEndpoint和@ClientEndpoit 。 我們的客戶端端點,我們將其稱為BroadcastClientEndpoint ,將僅偵聽服務器發(fā)送的消息:
package com.example.services;import java.io.IOException; import java.util.logging.Logger;import javax.websocket.ClientEndpoint; import javax.websocket.EncodeException; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session;@ClientEndpoint public class BroadcastClientEndpoint {private static final Logger log = Logger.getLogger( BroadcastClientEndpoint.class.getName() );@OnOpenpublic void onOpen( final Session session ) throws IOException, EncodeException {session.getBasicRemote().sendObject( new Message( "Client", "Hello!" ) );}@OnMessagepublic void onMessage( final Message message ) {log.info( String.format( "Received message '%s' from '%s'",message.getMessage(), message.getUsername() ) );} }就是這樣! 很干凈,的代碼不言自明的片:當客戶端得到了連接到服務器和@ 的onMessage被稱為每次服務器@OnOpen被稱為發(fā)送消息給客戶端。 是的,它非常簡單,但是有一個警告: JSR-356實現(xiàn)可以處理任何簡單對象,但不能處理諸如Message is之類的復雜對象。 為了解決這個問題, JSR-356引入了編碼器和解碼器的概念。
我們都喜歡JSON ,那么為什么不定義自己的JSON編碼器和解碼器呢? JSON處理的Java API(JSR-353)可以為我們完成這項簡單的任務。 要創(chuàng)建編碼器,只需實現(xiàn)Encoder.Text <Message>并使用JsonObjectBuilder將對象基本序列化為某個字符串,在我們的情況下為JSON字符串。
package com.example.services;import javax.json.Json; import javax.json.JsonReaderFactory; import javax.websocket.EncodeException; import javax.websocket.Encoder; import javax.websocket.EndpointConfig;public class Message {public static class MessageEncoder implements Encoder.Text< Message > {@Overridepublic void init( final EndpointConfig config ) {}@Overridepublic String encode( final Message message ) throws EncodeException {return Json.createObjectBuilder().add( "username", message.getUsername() ).add( "message", message.getMessage() ).build().toString();}@Overridepublic void destroy() {}} }對于解碼器部分,一切看起來都非常相似,我們必須實現(xiàn)Decoder.Text <Message>并使用JsonReader從字符串反序列化我們的對象。
package com.example.services;import javax.json.JsonObject; import javax.json.JsonReader; import javax.json.JsonReaderFactory; import javax.websocket.DecodeException; import javax.websocket.Decoder;public class Message {public static class MessageDecoder implements Decoder.Text< Message > {private JsonReaderFactory factory = Json.createReaderFactory( Collections.< String, Object >emptyMap() );@Overridepublic void init( final EndpointConfig config ) {}@Overridepublic Message decode( final String str ) throws DecodeException {final Message message = new Message();try( final JsonReader reader = factory.createReader( new StringReader( str ) ) ) {final JsonObject json = reader.readObject();message.setUsername( json.getString( "username" ) );message.setMessage( json.getString( "message" ) );}return message;}@Overridepublic boolean willDecode( final String str ) {return true;}@Overridepublic void destroy() {}} }最后,我們需要告訴客戶端(和服務器,它們共享相同的解碼器和編碼器),我們擁有用于消息的編碼器和解碼器。 最簡單的方法就是將它們聲明為@ServerEndpoint和@ClientEndpoit批注的一部分。
import com.example.services.Message.MessageDecoder; import com.example.services.Message.MessageEncoder;@ClientEndpoint( encoders = { MessageEncoder.class }, decoders = { MessageDecoder.class } ) public class BroadcastClientEndpoint { }為了使客戶的示例更完整,我們需要某種方式使用BroadcastClientEndpoint連接到服務器并基本上交換消息。 ClientStarter類最終確定圖片:
package com.example.ws;import java.net.URI; import java.util.UUID;import javax.websocket.ContainerProvider; import javax.websocket.Session; import javax.websocket.WebSocketContainer;import org.eclipse.jetty.websocket.jsr356.ClientContainer;import com.example.services.BroadcastClientEndpoint; import com.example.services.Message;public class ClientStarter {public static void main( final String[] args ) throws Exception {final String client = UUID.randomUUID().toString().substring( 0, 8 );final WebSocketContainer container = ContainerProvider.getWebSocketContainer(); final String uri = "ws://localhost:8080/broadcast"; try( Session session = container.connectToServer( BroadcastClientEndpoint.class, URI.create( uri ) ) ) {for( int i = 1; i <= 10; ++i ) {session.getBasicRemote().sendObject( new Message( client, "Message #" + i ) );Thread.sleep( 1000 );}}// Application doesn't exit if container's threads are still running( ( ClientContainer )container ).stop();} }只需幾句注釋,此代碼即可完成操作:我們正在ws:// localhost:8080 / broadcast連接到WebSockets端點,隨機選擇一些客戶端名稱(來自UUID)并生成10條消息,每條消息都有1秒的延遲(請確保我們有時間將它們全部收回來)。
服務器部分看起來并沒有很大不同,并且在這一點上無需任何其他注釋就可以理解(服務器可能只是將接收到的每條消息廣播給所有連接的客戶端)。 這里要提到的重要一點:每當新客戶端連接到服務器端點時,都會創(chuàng)建服務器端點的新實例(這就是對等體集合是靜態(tài)的),這是默認行為,可以輕松更改。
package com.example.services;import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Set;import javax.websocket.EncodeException; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint;import com.example.services.Message.MessageDecoder; import com.example.services.Message.MessageEncoder;@ServerEndpoint( value = "/broadcast", encoders = { MessageEncoder.class }, decoders = { MessageDecoder.class } ) public class BroadcastServerEndpoint {private static final Set< Session > sessions = Collections.synchronizedSet( new HashSet< Session >() ); @OnOpenpublic void onOpen( final Session session ) {sessions.add( session );}@OnClosepublic void onClose( final Session session ) {sessions.remove( session );}@OnMessagepublic void onMessage( final Message message, final Session client ) throws IOException, EncodeException {for( final Session session: sessions ) {session.getBasicRemote().sendObject( message );}} }為了使該端點可用于連接,我們應該啟動WebSockets容器并在其中注冊該端點。 與往常一樣, Jetty 9.1可以輕松地以嵌入式模式運行:
package com.example.ws;import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;import com.example.config.AppConfig;public class ServerStarter {public static void main( String[] args ) throws Exception {Server server = new Server( 8080 );// Create the 'root' Spring application contextfinal ServletHolder servletHolder = new ServletHolder( new DefaultServlet() );final ServletContextHandler context = new ServletContextHandler();context.setContextPath( "/" );context.addServlet( servletHolder, "/*" );context.addEventListener( new ContextLoaderListener() ); context.setInitParameter( "contextClass", AnnotationConfigWebApplicationContext.class.getName() );context.setInitParameter( "contextConfigLocation", AppConfig.class.getName() );server.setHandler( context );WebSocketServerContainerInitializer.configureContext( context ); server.start();server.join(); } }上面片段中最重要的部分是WebSocketServerContainerInitializer.configureContext :它實際上是創(chuàng)建WebSockets容器的實例。 因為我們還沒有添加任何端點,所以容器基本上位于此處,什么也不做。 Spring Framework和AppConfig配置類將為我們完成最后的連接。
package com.example.config;import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.websocket.DeploymentException; import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig;import org.eclipse.jetty.websocket.jsr356.server.AnnotatedServerEndpointConfig; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.WebApplicationContext;import com.example.services.BroadcastServerEndpoint;@Configuration public class AppConfig {@Inject private WebApplicationContext context;private ServerContainer container;public class SpringServerEndpointConfigurator extends ServerEndpointConfig.Configurator {@Overridepublic < T > T getEndpointInstance( Class< T > endpointClass ) throws InstantiationException {return context.getAutowireCapableBeanFactory().createBean( endpointClass ); }}@Beanpublic ServerEndpointConfig.Configurator configurator() {return new SpringServerEndpointConfigurator();}@PostConstructpublic void init() throws DeploymentException {container = ( ServerContainer )context.getServletContext().getAttribute( javax.websocket.server.ServerContainer.class.getName() );container.addEndpoint( new AnnotatedServerEndpointConfig( BroadcastServerEndpoint.class, BroadcastServerEndpoint.class.getAnnotation( ServerEndpoint.class ) ) {@Overridepublic Configurator getConfigurator() {return configurator();}});} }如前所述,默認情況下,容器會在每次新客戶端連接時創(chuàng)建服務器端點的新實例,并通過調用構造函數(shù)來實現(xiàn),在本例中為BroadcastServerEndpoint.class.newInstance() 。 這可能是理想的行為,但是由于我們使用的是Spring Framework和依賴項注入,因此此類新對象基本上是不受管的bean。 由于JSR-356的精心設計(在我看來),通過實現(xiàn)ServerEndpointConfig.Configurator可以很容易地提供您自己的創(chuàng)建端點實例的方式。 SpringServerEndpointConfigurator就是這種實現(xiàn)方式的一個示例:每次請求新的終結點實例時,它都會創(chuàng)建一個新的托管Bean(如果您想要單個實例,則可以在AppConfig中將終結點的一個實例創(chuàng)建為Bean并一直返回)。
我們檢索WebSockets容器的方式特定于Jetty :從名稱為“ javax.websocket.server.ServerContainer”的上下文屬性中獲取 (將來可能會更改)。 容器到達后,我們通過提供我們自己的ServerEndpointConfig (基于Jetty已經(jīng)提供的AnnotatedServerEndpointConfig )來添加新的(托管!)端點。
要構建和運行我們的服務器和客戶端,我們只需要這樣做:
mvn clean package java -jar target\jetty-web-sockets-jsr356-0.0.1-SNAPSHOT-server.jar // run server java -jar target/jetty-web-sockets-jsr356-0.0.1-SNAPSHOT-client.jar // run yet another client例如,通過運行服務器和幾個客戶端(我運行了其中的四個 ,即“ 392f68ef ”,“ 8e3a869d ”,“ ca3a06d0 ”,“ 6cb82119 ”),您可能會在控制臺的輸出中看到每個客戶端都接收到所有消息來自所有其他客戶端(包括其自身的消息):
Nov 29, 2013 9:21:29 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Hello!' from 'Client' Nov 29, 2013 9:21:29 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #1' from '392f68ef' Nov 29, 2013 9:21:29 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #2' from '8e3a869d' Nov 29, 2013 9:21:29 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #7' from 'ca3a06d0' Nov 29, 2013 9:21:30 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #4' from '6cb82119' Nov 29, 2013 9:21:30 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #2' from '392f68ef' Nov 29, 2013 9:21:30 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #3' from '8e3a869d' Nov 29, 2013 9:21:30 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #8' from 'ca3a06d0' Nov 29, 2013 9:21:31 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #5' from '6cb82119' Nov 29, 2013 9:21:31 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #3' from '392f68ef' Nov 29, 2013 9:21:31 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #4' from '8e3a869d' Nov 29, 2013 9:21:31 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #9' from 'ca3a06d0' Nov 29, 2013 9:21:32 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #6' from '6cb82119' Nov 29, 2013 9:21:32 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #4' from '392f68ef' Nov 29, 2013 9:21:32 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #5' from '8e3a869d' Nov 29, 2013 9:21:32 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #10' from 'ca3a06d0' Nov 29, 2013 9:21:33 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #7' from '6cb82119' Nov 29, 2013 9:21:33 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #5' from '392f68ef' Nov 29, 2013 9:21:33 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #6' from '8e3a869d' Nov 29, 2013 9:21:34 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #8' from '6cb82119' Nov 29, 2013 9:21:34 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #6' from '392f68ef' Nov 29, 2013 9:21:34 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #7' from '8e3a869d' Nov 29, 2013 9:21:35 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #9' from '6cb82119' Nov 29, 2013 9:21:35 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #7' from '392f68ef' Nov 29, 2013 9:21:35 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #8' from '8e3a869d' Nov 29, 2013 9:21:36 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #10' from '6cb82119' Nov 29, 2013 9:21:36 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #8' from '392f68ef' Nov 29, 2013 9:21:36 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #9' from '8e3a869d' Nov 29, 2013 9:21:37 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #9' from '392f68ef' Nov 29, 2013 9:21:37 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #10' from '8e3a869d' Nov 29, 2013 9:21:38 PM com.example.services.BroadcastClientEndpoint onMessage INFO: Received message 'Message #10' from '392f68ef' 2013-11-29 21:21:39.260:INFO:oejwc.WebSocketClient:main: Stopped org.eclipse.jetty.websocket.client.WebSocketClient@3af5f6dc太棒了! 我希望這篇介紹性博客文章能夠顯示在Java中使用現(xiàn)代Web通信協(xié)議變得多么容易,這要歸功于Java WebSockets(JSR-356) , 用于JSON處理的Java API(JSR-353)以及諸如Jetty 9.1之類的出色項目!
- 與往常一樣,完整的項目可在GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2013/12/java-websockets-jsr-356-on-jetty-9-1.html
總結
以上是生活随笔為你收集整理的Jetty 9.1上的Java WebSockets(JSR-356)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 停车场备案承诺书模板(停车场备案承诺书)
- 下一篇: clinux开发(c linux 开发)