迟来总比没有好:SSE或服务器发送的事件现在已在JAX-RS中

服务器发送的事件 (或简称为SSE )是非常有用的协议,它允许服务器通过HTTP将数据推送到客户端。 这是我们的网络浏览器支持的年龄,但令人惊讶的是, JAX-RS规范在很长一段时间内都忽略了这一点。 尽管Jersey提供了适用于SSE媒体类型的扩展名,但该API尚未正式化,因此不能移植到其他JAX-RS实现中。

幸运的是, JAX-RS 2.1 (也称为JSR-370)通过将SSE支持(客户端和服务器端)作为正式规范的一部分,已经改变了这一点。 在今天的帖子中,我们将研究如何使用最近发布的出色的Apache CXF框架3.2.0版将SSE支持集成到现有的Java REST(ful) Web服务中。 实际上,除了自举之外,实际上没有CXF特定的东西,所有示例都应在实现JAX-RS 2.1规范的任何其他框架中工作。

事不宜迟,让我们开始吧。 由于这些天大量的Java项目都建立在出色的Spring Framework之上,因此我们的示例应用程序将使用Spring Boot和Apache CXF Spring Boot Integration使我们Swift起步。 老好伙伴Apache Maven也可以通过管理项目依赖项来帮助我们。

org.springframework.bootspring-boot-starter1.5.8.RELEASEorg.apache.cxfcxf-rt-frontend-jaxrs3.2.0org.apache.cxfcxf-spring-boot-starter-jaxrs3.2.0org.apache.cxfcxf-rt-rs-client3.2.0org.apache.cxfcxf-rt-rs-sse3.2.0

在后台, Apache CXF正在使用Atmosphere框架来实现SSE传输,因此这是我们必须包括的另一个依赖项。

org.atmosphereatmosphere-runtime2.4.14

有关依赖于Atmosphere框架的详细信息导致需要提供其他配置设置,即transportId ,以确保在运行时拾取支持SSE的传输。 相关详细信息可以添加到application.yml文件中:

cxf:servlet:init:transportId: http://cxf.apache.org/transports/http/sse

太好了,所以基础就在那里,继续前进。 我们将要构建的REST(ful) Web服务将在SSE流中公开虚构的CPU平均负载(为简单起见,随机生成)。 Stats类将构成我们的数据模型。

public class Stats {private long timestamp;private int load;public Stats() {}public Stats(long timestamp, int load) {this.timestamp = timestamp;this.load = load;}// Getters and setters are omitted...
}

说到流, Reactive Streams规范已进入Java 9 ,希望我们能看到Java社区加速采用反应式编程模型。 此外,在具有Reactive Streams支持的情况下,开发支持SSE的 REST(ful) Web服务将变得更加容易和直接。 为此,让我们将RxJava 2集成到示例应用程序中。

io.reactivex.rxjava2rxjava2.1.6

这是开始使用我们的StatsRestService类(典型的JAX-RS资源实现)的好时机。 JAX-RS 2.1中的关键SSE功能以Sse上下文对象为中心,可以像这样注入。

@Service
@Path("/api/stats")
public class StatsRestService {@Context public void setSse(Sse sse) {// Access Sse context here}

在Sse上下文之外,我们可以访问两个非常有用的抽象:例如SseBroadcaster和OutboundSseEvent.Builder :

private SseBroadcaster broadcaster;
private Builder builder;@Context 
public void setSse(Sse sse) {this.broadcaster = sse.newBroadcaster();this.builder = sse.newEventBuilder();
}

您可能已经猜到了, OutboundSseEvent.Builder构造了OutboundSseEvent类的实例,这些实例可以通过电线发送,而SseBroadcaster则向所有连接的客户端广播相同的SSE流。 话虽如此,我们可以生成OutboundSseEvent的流并将其分发给感兴趣的每个人:

private static void subscribe(final SseBroadcaster broadcaster, final Builder builder) {Flowable.interval(1, TimeUnit.SECONDS).zipWith(eventsStream(builder), (id, bldr) -> createSseEvent(bldr, id)).subscribeOn(Schedulers.single()).subscribe(broadcaster::broadcast);
}private static Flowable<OutboundSseEvent.Builder> eventsStream(final Builder builder) {return Flowable.generate(emitter -> emitter.onNext(builder.name("stats")));
}

如果您不熟悉RxJava 2 ,请不用担心,这就是这里发生的情况。 eventsStream方法为stats类型的SSE事件返回有效无限的OutboundSseEvent.Builder实例流。 订阅方法稍微复杂一些。 我们首先创建一个流,该流每秒发出序号,例如fe 0,1,2,3,4,5,6,… ,依此类推。 稍后,我们将此流与eventsStream方法返回的流合并,实质上将两个流合并为一个流,该流每秒发出一个元组(number, OutboundSseEvent.Builder ) 。 公平地说,该元组对我们不是很有用,因此我们将其转换为OutboundSseEvent类的实例,将数字视为SSE事件标识符:

private static final Random RANDOM = new Random();private static OutboundSseEvent createSseEvent(OutboundSseEvent.Builder builder, long id) {return builder.id(Long.toString(id)).data(Stats.class, new Stats(new Date().getTime(), RANDOM.nextInt(100))).mediaType(MediaType.APPLICATION_JSON_TYPE).build();
}

OutboundSseEvent可以使用常规MessageBodyWriter解析策略在data属性中携带将相对于指定的mediaType进行序列化的任何有效负载。 一旦获得OutboundSseEvent实例,便使用SseBroadcaster :: broadcast方法将其发送出去。 请注意,我们使用subscribeOn运算符将控制流移交给了另一个线程,这通常是您一直在做的事情。

很好,希望现在清除了流部分,但是我们如何才能真正订阅SseBroadcaster发出的SSE事件? 这比您想像的要容易:

@GET
@Path("broadcast")
@Produces(MediaType.SERVER_SENT_EVENTS)
public void broadcast(@Context SseEventSink sink) {broadcaster.register(sink);
}

我们都准备好了。 这里最重要的是正在生成的内容类型,应将其设置为MediaType.SERVER_SENT_EVENTS 。 在这种情况下, SseEventSink的上下文实例变得可用,并且可以向SseBroadcaster实例注册。

要查看我们的JAX-RS资源,我们需要使用例如JAXRSServerFactoryBean引导服务器实例,并在此过程中配置所有必需的提供程序。 请注意,我们也在显式指定要使用的传输,在这种情况下为SseHttpTransportFactory.TRANSPORT_ID

@Configuration
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {@Beanpublic Server rsServer(Bus bus, StatsRestService service) {JAXRSServerFactoryBean endpoint = new JAXRSServerFactoryBean();endpoint.setBus(bus);endpoint.setAddress("/");endpoint.setServiceBean(service);endpoint.setTransportId(SseHttpTransportFactory.TRANSPORT_ID);endpoint.setProvider(new JacksonJsonProvider());return endpoint.create();}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/static/**").addResourceLocations("classpath:/web-ui/"); }
}

要结束循环,我们只需要为Spring Boot应用程序提供运行器即可:

@SpringBootApplication
public class SseServerStarter {    public static void main(String[] args) {SpringApplication.run(SseServerStarter.class, args);}
}

现在,如果我们运行该应用程序并使用多个Web浏览器或同一浏览器中的不同选项卡导航到http:// localhost:8080 / static / broadcast.html ,我们将观察到所有事件内部绘制的相同事件流:

不错,广播当然是一个有效的用例,但是在每次端点调用时返回一个独立的SSE流又如何呢? 简单,只需使用SseEventSink方法(例如sendclose )即可直接操作SSE流。

@GET
@Path("sse")
@Produces(MediaType.SERVER_SENT_EVENTS)
public void stats(@Context SseEventSink sink) {Flowable.interval(1, TimeUnit.SECONDS).zipWith(eventsStream(builder), (id, bldr) -> createSseEvent(bldr, id)).subscribeOn(Schedulers.single()).subscribe(sink::send, ex -> {}, sink::close);
}

这次,如果我们运行该应用程序并使用多个Web浏览器或同一浏览器中的不同选项卡导航到http:// localhost:8080 / static / index.html ,我们将观察到完全不同的图表:

出色的服务器端API确实非常简洁且易于使用。 但是在客户端方面,我们可以使用Java应用程序中的SSE流吗? 答案是肯定的。 JAX-RS 2.1还概述了客户端API,其核心是SseEventSource 。

final WebTarget target = ClientBuilder.newClient().register(JacksonJsonProvider.class).target("http://localhost:8080/services/api/stats/sse");try (final SseEventSource eventSource =SseEventSource.target(target).reconnectingEvery(5, TimeUnit.SECONDS).build()) {eventSource.register(event -> {final Stats stats = event.readData(Stats.class, MediaType.APPLICATION_JSON_TYPE);System.out.println("name: " + event.getName());System.out.println("id: " + event.getId());System.out.println("comment: " + event.getComment());System.out.println("data: " + stats.getLoad() + ", " + stats.getTimestamp());System.out.println("---------------");});eventSource.open();// Just consume SSE events for 10 secondsThread.sleep(10000); 
}

如果运行此代码段(假设服务器也已启动并且正在运行),我们将在控制台中看到类似的内容(您可能还记得,数据是随机生成的)。

name: stats
id: 0
comment: null
data: 82, 1509376080027
---------------
name: stats
id: 1
comment: null
data: 68, 1509376081033
---------------
name: stats
id: 2
comment: null
data: 12, 1509376082028
---------------
name: stats
id: 3
comment: null
data: 5, 1509376083028
---------------...

如我们所见,服务器端的OutboundSseEvent变为客户端的InboundSseEvent 。 客户端可以使用通常的MessageBodyReader解析策略,通过指定预期的媒体类型来消耗数据属性中可以反序列化的任何有效负载。

单篇文章中压缩了很多内容。 而且,关于SSE和JAX-RS 2.1的更多信息我们在这里没有涉及,例如使用HttpHeaders.LAST_EVENT_ID_HEADER或配置重新连接延迟。 如果有兴趣学习的话,这些可能是即将发布的帖子的重要话题。

总而言之, JAX-RS对SSE的支持是我们许多人期待已久的事情。 最后,它在那里,请尝试一下!

完整的项目资源可在Github上找到 。

翻译自: https://www.javacodegeeks.com/2017/10/better-late-never-sse-server-sent-events-now-jax-rs.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/348433.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java 面向对象继承的思想_Java面向对象思想

Java类的定义&#xff1a;成员变量会默认初始化&#xff0c;局部变量不会默认初始化。如何在内存中区分类和对象&#xff1a;♦类是静态的概念&#xff0c;代码区♦对象是new出来的&#xff0c;位于堆内存&#xff0c;类的每一个成员变量在不同的对象中都有不同的值(除了静态变…

java 垃圾回收手动回收_Java垃圾回收(4)

java 垃圾回收手动回收G1&#xff1a;垃圾优先 G1收集器是热点JVM中要实现的最新收集器。 自Java 7 Update 4以来&#xff0c;它一直是受支持的收集器。OracleGC团队也公开表示&#xff0c;他们对低暂停GC的希望是完全实现的G1。 这篇文章来自我之前的垃圾收集博客文章&#xf…

使用正确的垃圾收集器将Java内存使用量降至最低

大小对于软件至关重要。 很明显&#xff0c;与大型整体方法相比&#xff0c;在微服务体系结构中使用小型组件具有更多优势。 最新的Java版本的Jigsaw可帮助分解旧应用程序或从头开始构建新的云原生应用程序。 这种方法减少了磁盘空间&#xff0c;构建时间和启动时间。 但是&am…

java 鼠标画多个圆形_点击鼠标不同的建(左、中、右)画一个不同颜色的圆

[java]代码库import java.awt.Color;import java.awt.Graphics;import java.awt.event.MouseEvent;import java.awt.event.MouseListener;import javax.swing.JFrame;public class T1 extends JFrame{int x;int y;Color c;public T1(){this.setSize(600,400);this.setDefaultCl…

通过粘性仙人掌基元进行延迟加载和缓存

您显然知道什么是延迟加载 &#xff0c;对吗&#xff1f; 而且您无疑知道缓存 。 据我所知&#xff0c;Java中没有一种优雅的方法来实现它们中的任何一个。 这是我在Cactoos原语的帮助下为自己找到的。 Matteo Garrone的《 Reality&#xff08;2012&#xff09;》 假设我们需…

java语言编程基础_java语言编程基础

java语言基本要素高级语言如c、c#、java等都有一些共同性的东西&#xff1a;关键字、标识符、运算符、注释、数据类型、常量和变量、语句、函数、数组。高级语言在这些要素上大同小异。Java关键字&#xff1a;一些有特定含义&#xff0c;有专门用途的字符串(keyword)。Java中关…

junit mockito_JUnit和Mockito合作

junit mockito这次&#xff0c;我想对测试框架Mockito进行概述。 毫无疑问&#xff0c;这是用于测试Java代码的最受欢迎的工具之一。 我已经对Mockito的竞争对手EasyMock进行了概述。 这篇文章将基于有关EasyMock的示例应用程序。 我的意思是代表咖啡机功能的类。 使用Mockito…

debian10树莓派4安装mysql_树莓派4上如何安装 Raspbian Buster

随着 Raspberry Pi 4 主板的问世&#xff0c;了解如何使用Raspbian Buster设置您的操作系统。你的新Pi 4到了邮箱&#xff0c;你已经设置了办公桌&#xff0c;您已准备好安装操作系统。对于初学者来说&#xff0c;Raspbian为Pi制造商提供了出色的桌面体验。最重要的是&#xff…

借助财务客户评估解决方案在云中构建AppDev

现代JBoss BRMS时代最古老的业务逻辑演示是2012年6月发布的“ 客户评估”示例 。 那时&#xff0c;JBoss BRMS包含规则&#xff0c;事件和流程。 该项目提供了一个完整安装和配置的环境&#xff0c;用于展示该项目和所有可用的BPM组件。 它包括一个带有单元测试的JBoss Develo…

jvm 性能_JVM性能魔术

jvm 性能HotSpot是我们众所周知和喜爱的JVM&#xff0c;是Java和Scala汁流淌的大脑。 多年来&#xff0c;许多工程师对其进行了改进和调整&#xff0c;并且每次迭代时&#xff0c;其代码执行的速度和效率都接近于本机编译代码。 JIT&#xff08;“及时”&#xff09;编译器是…

Hollowjars,部署扫描程序以及Wildfly群体为何很棒

在上一篇文章中&#xff0c;我描述了如何使用OpenLiberty和maven作为独立服务器或maven构建的一部分来启动服务器&#xff0c;以及如何创建fatjar包。 在这篇文章中&#xff0c;我正在研究如何使用Wildfly群。 我仍在尝试使MicroProfile在Wildfly full上运行&#xff0c;因此&…

Java调用跟踪系统_Tracer:在分布式系统中的调用跟踪和日志相关

Tracer: Distributed system tracingTracer noun, /ˈtɹeɪsɚ/: A round of ammunition that contains a flammable substance that produces a visible trail when fired in the dark.Tracer is a library that manages custom trace identifiers and carries them through …

java测试netty_《Netty官方文档》基准测试

原文链接 译者&#xff1a;lijunshuNetty有一个模块叫’netty-microbench’&#xff0c;我们可以用他来执行一系列的微型基准测试。Netty-microbench是基于OpenJDK JMH构件的(HotSpot的推荐基准测试方案)。当你开始netty基准测试时&#xff0c;你不需要额外的依赖。运行基准测…

java描边_shape描边设置是否显示四周描边

android:width"1pt"/>android:topRightRadius"30pt"android:bottomRightRadius"30pt" />效果图如下&#xff1a;这里设置了左边描边不显示设置虚线&#xff1a;android:shape"line">android:dashGap"3pt"android:d…

java 垃圾回收手动回收_Java垃圾回收(2)

java 垃圾回收手动回收并行清理 今天&#xff0c;我们介绍了并行GC的工作原理。 具体来说&#xff0c;这是在Eden上运行并行Scavenge收集器&#xff0c;在Tenured一代上运行Parallel Mark and Sweep收集器的组合。 您可以通过传递-XX&#xff1a; UseParallelOldGC来获得此选项…

OpenHub框架进行的异步通信

在本系列的前一部分中&#xff0c;我们介绍了OpenHub框架 。 这部分显示了框架最强大的功能之一- 异步消息传递模型 。 当源系统无法等待目标系统的响应时&#xff0c;将使用系统之间的异步通信。 有以下几个原因&#xff1a; 源系统必须尽可能地响应 &#xff0c;并且不受外…

工厂设计模式和策略设计模式_设计模式:策略

工厂设计模式和策略设计模式这次我想谈谈策略设计模式 。 通过这种方式&#xff0c;我开始撰写有关行为设计模式的文章。 这种模式表示对象之间的某些交互模式&#xff0c;以使代码更灵活且组织得更好。此方法的最本质点是对象之间的松散耦合。 当您的应用程序中有多个实现目的…

如何将不带web.xml的Spring应用程序部署到Tomcat

介绍 由于Servlet 3规范不再需要web.xml来配置Web应用程序&#xff0c;因此已通过使用注释代替。 在本文中&#xff0c;我们将研究如何在不使用web.xml情况下将简单的基于Spring的应用程序部署到Tomcat 8.5。*。 创建一个空的应用程序 使用以下命令使用maven webapp原型创建一…

Python和Java结合的项目实战_[项目实战] Python高级教程项目实战篇 Python和Java结合的项目实战 视频教程 [...

资源介绍课程简介:xa0xa0Python高级教程项目实战篇 Python和Java结合的项目实战 视频教程 教学视频----------------------课程目录Python项目实战篇[初级项目&#xff1a;图片社交电商导购漂流瓶]项目实现功能&#xff1a; 用户注册&#xff0c;登录&#xff0c;登出图片的多种…

设计模式 建造者模式_设计模式:建造者

设计模式 建造者模式有时需要在应用程序中创建一个复杂的对象。 一种解决方案是Factory模式&#xff0c;另一种是Builder设计模式。 在某些情况下&#xff0c;您甚至可以结合使用这两种模式。 但是在本文中&#xff0c;我想研究一下Builder设计模式 。 我需要说的第一件事是创造…