Jetty 9.1的发布将Java WebSockets (JSR-356) 带入了非Java EE环境,从而开启了微服务时代。我们可以将Jetty的容器包含在java应用程序中(注意,不是Java代码运行在容器中,而是相反),这种微服务轻量概念开始得到提倡推广,为模块化开启新的探索方向。

  该案例目标是要建设一个从客户端程序接受消息并广播到当前连接的所有其他客户端WebSocket服务器。假设有一个消息模型:

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

客户端代码:

@ClientEndpoint
public class BroadcastClientEndpoint {
    private static final Logger log = Logger.getLogger( 
        BroadcastClientEndpoint.class.getName() );

@OnOpen
    public void onOpen( final Session session ) throws IOException, EncodeException  {
        session.getBasicRemote().sendObject( new Message( "Client", "Hello!" ) );
    }

@OnMessage
    public void onMessage( final Message message ) {
        log.info( String.format( "Received message '%s' from '%s'",
            message.getMessage(), message.getUsername() ) );
    }
}

@OnOpen 是当客户端连接到服务器开始调用,@OnMessage是每次服务器向客户端发送消息时调用。

消息传递使用Json,这里使用JSR-353规范的Json类将对象进行序列化。我们需要在Message里面加上一下Json反序列化,也就是将Json转为Message对象:

public class Message {
    public static class MessageDecoder implements Decoder.Text< Message > {
        private JsonReaderFactory factory = Json.createReaderFactory( Collections.< String, Object >emptyMap() );

@Override
        public void init( final EndpointConfig config ) {
        }

@Override
        public 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;
        }

@Override
        public boolean willDecode( final String str ) {
            return true;
        }

@Override
        public void destroy() {
        }
    }
}

我们需要告诉客户端,我们有一个Json编码器和解码器,在BroadcastClientEndpoint类上加入:

@ClientEndpoint( encoders = { MessageEncoder.class }, decoders = { MessageDecoder.class } )
public class BroadcastClientEndpoint {
}

下面是调用运行代码:

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

这是连接URL ws://localhost:8080/broadcast,随机挑选一些客户端名称(从UUID),每1秒的延迟产生10条信息,(只是为了确保我们有时间去接收他们都回来了)。

下面是服务器端的代码:

@ServerEndpoint( 
    value = "/broadcast", 
    encoders = { MessageEncoder.class }, 
    decoders = { MessageDecoder.class } 

public class BroadcastServerEndpoint {
    private static final Set< Session > sessions = 
        Collections.synchronizedSet( new HashSet< Session >() );

@OnOpen
    public void onOpen( final Session session ) {
        sessions.add( session );
    }

@OnClose
    public void onClose( final Session session ) {
        sessions.remove( session );
    }

@OnMessage
    public void onMessage( final Message message, final Session client ) 
            throws IOException, EncodeException {
        for( final Session session: sessions ) {
            session.getBasicRemote().sendObject( message );
        }
    }
}

为了使这个服务器端点能够运行,我们将其注册入Jetty服务器,Jetty9.能够在嵌入下运行:

public class ServerStarter  {
    public static void main( String[] args ) throws Exception {
        Server server = new Server( 8080 );

// Create the 'root' Spring application context
        final 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:,它是创建一个Websockets的容器,目前容器内什么也没有,我们没有注册进入我们的服务器端点。

Spring的AppConfig 能够帮助我们做到这点:

@Configuration
public class AppConfig  {
    @Inject private WebApplicationContext context;
    private ServerContainer container;

public class SpringServerEndpointConfigurator extends ServerEndpointConfig.Configurator {
        @Override
        public < T > T getEndpointInstance( Class< T > endpointClass ) 
                throws InstantiationException {
            return context.getAutowireCapableBeanFactory().createBean( endpointClass );   
        }
    }

@Bean
    public ServerEndpointConfig.Configurator configurator() {
        return new SpringServerEndpointConfigurator();
    }

@PostConstruct
    public 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 )  
            ) {
                @Override
                public Configurator getConfigurator() {
                    return configurator();
                }
            }
        );
    }  
}

容器通过调用构造函数将创建container,然后每一次新的客户端连接创建一个服务器端点的新实例。

我们检索的WebSockets容器的方法是Jetty专用规范:查询来自名为“javax.websocket.server.ServerContainer”上下文的属性。

最后运行:

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

输出结果部分:

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

该项目源码下载: GitHub

用Jetty 9.1运行Java WebSockets微服务的更多相关文章

  1. 如何使用 Java 构建微服务?

    [编者按]微服务背后的大理念是将大型.复杂且历时长久的应用在架构上设计为内聚的服务,这些服务能够随着时间的流逝而演化.本文主要介绍了利用 Java 生态系统构建微服务的多种方法,并分析了每种方法的利弊 ...

  2. java~springcloud微服务目录索引

    回到占占推荐博客索引 最近写了不过关于java,spring,微服务的相关文章,今天把它整理一下,方便大家学习与参考. java~springcloud微服务~目录索引 springcloud~服务注 ...

  3. Apache发布支持Java EE微服务的Meecrowave服务器

    Apache OpenWebBeans团队希望通过使服务器适应用户来消除复杂性.所以,该团队发布了Apache Meecrowave项目1.0版. Apache Meecrowave是一款小型服务器, ...

  4. kubernetes实践之运行aspnetcore webapi微服务

    1.预备工作 unbuntu 16.04 and above docker kubernetes 集群 2.使用vs2017创建一个web api应用程序,并打包镜像到本地. 3.推送本地镜像到doc ...

  5. kubernetes实战之运行aspnetcore webapi微服务 - kubernetes

    1.预备工作 unbuntu 16.04 or above docker for linux kubernetes for linux 集群环境 2.使用vs2017创建一个web api应用程序,并 ...

  6. Java面试——微服务

    1.什么是微服务?    就目前而言,对于微服务业界并没有一个统一的,标准的定义. 但通常而言,微服务架构是一种架构模式或者说是一种架构风格,它提倡将单一应用程序划分一组小的服务,每个服务运行在其独立 ...

  7. [Java复习] 微服务

    1. 怎么样定义一个微服务,或划分服务比较合理?业务导向的共性? 对应服务拆分,先设计高内聚低耦合的领域模型(DD),再实现相应的分布式系统是一种比较合理的方式. 微服务是手段,不是目的.目的是为了让 ...

  8. 推荐Java五大微服务器及其代码示例教程

    来源素文宅博客:http://blog.yoodb.com/yoodb/article/detail/1339 微服务越来越多地用于开发领域,因为开发人员致力于创建更大,更复杂的应用程序,这些应用程序 ...

  9. K8S学习笔记之filebeat采集K8S微服务java堆栈多行日志

    0x00 背景 K8S内运行Spring Cloud微服务,根据定制容器架构要求log文件不落地,log全部输出到std管道,由基于docker的filebeat去管道采集,然后发往Kafka或者ES ...

随机推荐

  1. Golang Vendor 包机制 及 注意事项

    现在的 Go 版本是 1.8,早在 1.5 时期,就有了 Vendor 包机制,详情可查看博文:“理解 Go 1.5 vendor”. 遇到的问题 个人在使用 Glide 管理 Vendor 包时(附 ...

  2. [.NET] [.net 脱壳工具]Sixxpack 最新脱壳机 通杀Sixxpack全版本by -=Msdn5 君临=

    [.net 脱壳工具]Sixxpack 最新脱壳机 通杀Sixxpack全版本by -=Msdn5 君临=- 识别方法: 如果无法调戏,请上传附件艾特我.............发帖不易啊..身处大西 ...

  3. delphi连接mysql不用添加DSN(mysql connector odbc 5.1版)

    一.下载安装mysql驱动http://mysql.com/downloads/connector/odbc/二.添加adoconnection,adoquery,使用以下连接字符串http://ww ...

  4. mysql 和 sqlite 区别 及 SQLite Expert Professional sqliteManager 区别

    mysql 和 sqlite 区别 SQLITE是单机数据库.功能简约,小型化,追求最大磁盘效率MYSQL是完善的服务器数据库.功能全面,综合化,追求最大并发效率 MYSQL.Sybase.Oracl ...

  5. Oracle APEX 5.1 with Ords 17 in Tomcat 9–Error tips: 请求无法映射到任何数据库。请确保请求 URL 正确, 并且已正确配置 URL 到数据库的映射

    一次意外关机引发的血案 1.重新开机打开 tomcat 9, 一切正常 2.打开 ords,异常报错: 404 Not Found 请求无法映射到任何数据库.请确保请求 URL 正确, 并且已正确配置 ...

  6. Struts2学习笔记——Struts2与Spring整合

      Struts2与Spring整合后,可以使用Spring的配置文件applicationContext.xml来描述依赖关系,在Struts2的配置文件struts.xml来使用Spring创建的 ...

  7. Activity间用Intent、Bundle、onActivityResult进行传值

    其实Activity间的传值就是通过Bundle,intent中也是自动生成了Bundle来传值,里面还有个onActivityResult()方法也可以传送数值. 如果一个Activity是由sta ...

  8. 《例说XBee无线模块开发》

    <例说XBee无线模块开发> 基本信息 原书名:The Hands-on XBee Lab Manual:Experiments that Teach you XBee Wireless ...

  9. [Hook] 免root,自己进程内,startActivity hook的几种姿势

    首先关于startActivity 我们平时会经常使用到 在activity内 直接startActivity(intent) 其实这里还有一种start方式 我们可能没怎么用过 getApplica ...

  10. Makefile:160: recipe for target 'all' failed (Ubuntu 16.06 + Opencv3.2)解决办法

    前言 之前一直用的opencv 好好的,今天安装了anaconda之后,python中的opencv不能用了,即便是拷贝cv2.so之后也是不能用,问题如下: 根本原因 安装anaconda之后,很多 ...