用Jetty 9.1运行Java WebSockets微服务
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微服务的更多相关文章
- 如何使用 Java 构建微服务?
[编者按]微服务背后的大理念是将大型.复杂且历时长久的应用在架构上设计为内聚的服务,这些服务能够随着时间的流逝而演化.本文主要介绍了利用 Java 生态系统构建微服务的多种方法,并分析了每种方法的利弊 ...
- java~springcloud微服务目录索引
回到占占推荐博客索引 最近写了不过关于java,spring,微服务的相关文章,今天把它整理一下,方便大家学习与参考. java~springcloud微服务~目录索引 springcloud~服务注 ...
- Apache发布支持Java EE微服务的Meecrowave服务器
Apache OpenWebBeans团队希望通过使服务器适应用户来消除复杂性.所以,该团队发布了Apache Meecrowave项目1.0版. Apache Meecrowave是一款小型服务器, ...
- kubernetes实践之运行aspnetcore webapi微服务
1.预备工作 unbuntu 16.04 and above docker kubernetes 集群 2.使用vs2017创建一个web api应用程序,并打包镜像到本地. 3.推送本地镜像到doc ...
- kubernetes实战之运行aspnetcore webapi微服务 - kubernetes
1.预备工作 unbuntu 16.04 or above docker for linux kubernetes for linux 集群环境 2.使用vs2017创建一个web api应用程序,并 ...
- Java面试——微服务
1.什么是微服务? 就目前而言,对于微服务业界并没有一个统一的,标准的定义. 但通常而言,微服务架构是一种架构模式或者说是一种架构风格,它提倡将单一应用程序划分一组小的服务,每个服务运行在其独立 ...
- [Java复习] 微服务
1. 怎么样定义一个微服务,或划分服务比较合理?业务导向的共性? 对应服务拆分,先设计高内聚低耦合的领域模型(DD),再实现相应的分布式系统是一种比较合理的方式. 微服务是手段,不是目的.目的是为了让 ...
- 推荐Java五大微服务器及其代码示例教程
来源素文宅博客:http://blog.yoodb.com/yoodb/article/detail/1339 微服务越来越多地用于开发领域,因为开发人员致力于创建更大,更复杂的应用程序,这些应用程序 ...
- K8S学习笔记之filebeat采集K8S微服务java堆栈多行日志
0x00 背景 K8S内运行Spring Cloud微服务,根据定制容器架构要求log文件不落地,log全部输出到std管道,由基于docker的filebeat去管道采集,然后发往Kafka或者ES ...
随机推荐
- Golang 特性简介
by sheepbao 主要大概介绍go语言的历史和特性,简单的入门. 来历 很久以前,有一个IT公司,这公司有个传统,允许员工拥有20%自由时间来开发实验性项目.在2007的某一天,公司的几个大牛, ...
- JavaScript进阶系列05,事件的执行时机, 使用addEventListener为元素同时注册多个事件,事件参数
本篇体验JavaScript事件的基本面,包括: ■ 事件必须在页面元素加载之后起效■ 点击事件的一个简单例子■ 为元素注册多个点击事件■ 获取事件参数 ■ 跨浏览器事件处理 □ 事件必须在页面元素加 ...
- 【java】ThreadLocal线程变量的实现原理和使用场景
一.ThreadLocal线程变量的实现原理 1.ThreadLocal核心方法有这个几个 get().set(value).remove() 2.实现原理 ThreadLocal在每个线程都会创建一 ...
- netty 自定义通讯协议
Netty中,通讯的双方建立连接后,会把数据按照ByteBuf的方式进行传输,例如http协议中,就是通过HttpRequestDecoder对ByteBuf数据流进行处理,转换成http的对象.基于 ...
- IOS的唯一标识符问题(转)
引用地址 http://www.zhihu.com/question/22599526/answer/21938878 网上搜了下IOS手机标志的种类,直接引用过来. UDID [[UIDevice ...
- App.config和Web.config配置文件的配置节点的解析
前言 在http://www.cnblogs.com/aehyok/p/3558661.html这篇博文中,大致对配置文件有了初步的了解,并且在文中有提到过<appSettings>和&l ...
- 2016 移动应用质量大数据报告--转自腾讯Bugly
2016年,在“互联网+”战略的推动下,移动互联网与越来越多传统行业的结合更加紧密,用户使用移动互联网的工作场景.生活场景.消费场景都在悄然发生着改变, 移动互联网产品在智能硬件.医疗.汽车.旅游.教 ...
- Eclipse 在线汉化的和修改字体大小、颜色的方法
一.在线汉化 先进入 http://www.eclipse.org/babel/downloads.php 找到自己对应版本的网址,然后复制下来. 然后,进入eclipse.点工具栏上的Help - ...
- 选中TreeView的某节点,并加背景颜色
一:按钮事件,遍历所有节点 private void button2_Click(object sender, EventArgs e) { foreach (TreeNode n in TreeVi ...
- [转]mysqldump备份还原和mysqldump导入导出语句大全详解
FROM : http://www.cnblogs.com/zeroone/archive/2010/05/11/1732834.html mysqldump备份还原和mysqldump导入导出语句大 ...