JSR 356 WebSockets是即将发布的JEE 7版本中令人兴奋的新功能之一,并且在其参考实现中包括Server-和Client API。 这使其非常适合在客户端与JavaFX集成。 JacpFX是JavaFX之上的RCP框架,它使用基于消息的方法与组件进行交互。 这种基于消息的方法使集成WebSocket-ClientEndpoints以及将传入消息传递到JavaFX / JacpFX应用程序变得容易。
下面的文章将向您展示如何在GlassFish 4上创建一个简单的Websocket-SeverEndpoint以及如何从JacpFX客户端联系该端点。 示例场景非常简单:服务器端点可以创建与Twitter的连接,它从客户端获取查询消息,并将Twitter结果广播到所有已连接的用户
ClientEndpoints。 该示例将基于GlassFish b85和JacpFX 1.2。 (maven)示例项目可以在这里下载
第1部分:创建服务器端点
示例项目是一个简单的JEE 7 maven项目。 该Web项目包含一个充当WebSocket-ServerEndpoint的POJO。 它接收查询请求,并将结果广播到所有连接的客户端。 第二个POJO是一个无状态Bean,它接收查询消息,执行twitter搜索并将结果返回到Server-Endpoint。 为了避免处理文本或二进制消息,我们创建了两个POJO,它们充当WebSocket消息的编码器/解码器。 让我们从TwitterRepositoryBean开始,这是一个简单的无状态bean:
@Stateless(mappedName = "TwitterRepositoryBean")public class TwitterRepositoryBean {public TwitterResult getTwitterDataByQuery(Query query) {return parser.fromJson(getFeedData(query.getQuery()), TwitterResult.class);}private String getFeedData(String input) {final String searchURL = "http://search.twitter.com/search.json?q=" + input + "&rpp=5&include_entities=true" +"&with_twitter_user_id=true&result_type=mixed";final URL twitter = new URL(searchURL);…return "";}}
接下来,我们创建一个WebSocket-ServerEndpoint。 创建端点的最简单方法是编写一个POJO并使用@ServerEndpoint(“ path”)对其进行注释。 端点可以具有以下生命周期注释:@ OnOpen,@ OnClose,@ OnError和一个或多个@OnMessage。
请注意,每种本机消息类型只允许使用一个@OnMessage。 这些是文本,二进制和傍。 这是什么意思? 您可以有两种这样的方法: @OnMessage login(Login lg);
和@OnMessage message(Message m)
。 但是一个必须以文本形式传输,另一个必须以二进制形式传输,否则部署时会出现异常。
ServerEndpoint将如下所示:
@ServerEndpoint(value = "/twitter")public class TwitterEndpoint {@Injectprivate TwitterRepositoryBean twitterRepo;@OnMessagepublic void handleChatMessage(Query query, Session session) {TwitterResult result = twitterRepo.getTwitterDataByQuery(query);broadcastMessage(result,session);}private void broadcastMessage(TwitterResult result, Session session) {for (Session s : session.getOpenSessions()) {s.getBasicRemote().sendObject(result);}}}
现在我们创建了ServerEndpoint,问题是如何使用Query
和TwitterResult
类的类型进行处理,因为本机消息格式为二进制和文本。 解决方案是:消息“编码器/解码器”。 因此,我们需要一个将二进制消息转换为Query
解码器,以及一个将TwitterResult
编码为二进制的编码器。
public class QueryDecoder implements Decoder.Binary<Query> {public Query decode(ByteBuffer byteBuffer) throws DecodeException {return (Query) SerializationUtils.deserialize(byteBuffer.array());}public boolean willDecode(ByteBuffer byteBuffer) {Object message = SerializationUtils.deserialize(byteBuffer.array());if (message == null) return false;return message instanceof Query;}}
和编码器:
public class TwitterResultEncoder implements Encoder.Binary<TwitterResult> {public ByteBuffer encode(TwitterResult message) throws EncodeException {return ByteBuffer.wrap(SerializationUtils.serialize(message));}}
为了使编码器/解码器可用于端点,我们扩展@ServerEndpoint注释,如下所示:
@ServerEndpoint(value = "/twitter", decoders = {QueryDecoder.class},encoders = {TwitterResultEncoder.class})
因此,现在我们有一个完整的ServerEndpoint示例,您可以将其部署在任何符合JEE 7的应用服务器上。
第2部分:创建JacpFX客户端和ClientEndpoint
从JacpFX开始的最简单方法是使用提供的Maven原型。 它生成一个示例客户端,其中包括JacpFX的每个有趣方面。 因此,您可以立即使用工作台,两个透视图(fxml和JavaFX),两个UI组件和两个非UI组件。 因此,我们只需使用它并通过WebSocket-Endpoints对其进行扩展。
如果您有> Java7u6和maven,则只需键入:
mvn archetype:generate -DarchetypeGroupId=org.jacp -DarchetypeArtifactId=JacpFX-quickstart-archetype -DarchetypeVersion=1.2 -DarchetypeRepository=http://developer.ahcp.de/nexus/content/repositories/jacp
创建一个JacpFX项目。
要获取WebSocket-Client API的依赖关系,您需要在pom.xml中添加以下依赖关系:
<dependency><groupId>org.glassfish.tyrus</groupId><artifactId>tyrus-client</artifactId><version>1.0-rc1</version><scope>compile</scope></dependency><dependency><groupId>org.glassfish.tyrus</groupId><artifactId>tyrus-container-grizzly</artifactId><version>1.0-rc1</version><scope>compile</scope></dependency>
并添加以下存储库:
<repository><id>java.net-promoted</id><url>https://maven.java.net/content/groups/promoted/</url></repository>
现在的基本思想是,我们使用创建的有状态组件并将其用作WebSocket- ClientEndpoint。 每次收到新的TwitterReult
,我们都会将其传递到JacpFX消息总线,并将其委托给一个UI组件,该组件将结果呈现在表中。 另一方面,我们将TextField
的输入委托给有状态组件,该组件将查询请求发送到ServerEndpoint。 因此,如下更改有状态组件:
id003
类级别的注释@ClientEndpoint
将此组件标记为WebSocket-Endpoint,而@CallbackComponent
包含此无状态组件的JacpFX元数据。 @OnStart init(..)
方法包含用于连接到WebSocket-ServerEndpoint并将有状态组件的实例作为ClientEndpoint传递的代码。 @OnStart是JacpFX生命周期注释,将在激活组件时执行。 当组件从UI收到Query
消息时,将执行“handleAction”
方法。 在这里,我们称为“sendQuery”
并使用WebSocket-Session将Query
对象发送到服务器端点。
当服务器执行Query
并从Twitter接收结果时,他TwitterResult
广播到所有连接的客户端,因此@OnMessage onTwitterMessage(…)
方法将在客户端执行。 在这里,我们调用组件的actionListener并将结果传递给ID为“ id001”的组件,以呈现结果。 像之前的ServerEndpoint一样,我们需要一个“编码器/解码器”来处理消息类型“ Query
和TwitterResult
”。 因此,以相同的方式创建编码器/解码器,然后像这样在ClientEndpoint上注册它们:
@ClientEndpoint(decoders = {TwitterResultDecoder.class}, encoders = {QueryEncoder.class})
第3部分:更改示例客户端以显示Query-和TableView
最后一步是将示例JacpFX客户端中的UI组件更改为具有查询视图和表视图。 您基本上可以自由地使用输入字段和表来创建一个组件以呈现结果。 更好的方法是为此创建单独的组件。 在开始之前,您可以删除PerspectiveTwo.java
, ComponentFXMLRight.java
和ComponentFXMLBottom.java
。 还要删除resources/main.xml
的引用,并将componentTop
的引用添加到PerspectiveOne:
<bean id="perspectiveOne" class="org.jacp.client.perspectives.PerspectiveOne"><property name="subcomponents"><list><ref bean="componentLeft" /><ref bean="componentTop" /><ref bean="statefulCallback" /><ref bean="statelessCallback" /></list></property></bean>
第一个JacpFX透视图(perspectiveOne)通过FXML声明其UI,并充当两个组件的容器。 它已经是一个SplitPane
,我们只需更改代码即可垂直拆分视图。 因此,打开resources/fxml/perspectiveOne.fxml
并在<SplitPane>
元素上添加属性orientation="VERTICAL"
,并将dividerPositions
更改为0.30。 现在打开PerspectiveOne.java并将组件的注册目标名称更改为:
perspectiveLayout.registerTargetLayoutComponent("QueryView",this.gridPaneLeft);perspectiveLayout.registerTargetLayoutComponent("TableView",this.gridPaneRight);
我们要做的是为我们的组件设置新的目标布局。 组件现在可以注册到这些目标布局之一,然后将在其中进行渲染。 这种注册机制使您可以在透视图中定义任何复杂的UI结构,并为组件定义渲染点。 现在,我们更改要在“QueryView”
目标中呈现的ComponentTop
。 为此,我们只需像这样更改@Component的defaultExecutionTarget属性的值即可:
@Component(defaultExecutionTarget = "QueryView", id = "id006", name = "componentTop", active = true, resourceBundleLocation = "bundles.languageBundle", localeID = "en_US")
这个组件已经包含一个TextField
和一个Button
,因此我们只需要更改Button的EventHandler即可传递TextFiled
的值。
private EventHandler<Event> getEventHandler() {return new EventHandler<Event>() {@Overridepublic void handle(final Event arg0) {getActionListener("id01.id003",textField.getText()).performAction(arg0);}};}
getActionListener(“id01.id003”…
只是在透视图“ id01”中定义了带有“ id003”的组件(有状态组件),作为此消息的目标。接下来,我们将ComponentLeft.java
更改为TableView
。我们将默认的executionTarget
更改为“TableView”
还更改了createUI()
方法以显示TableView
并更新了postHandleAction
来处理TwitterResults
并将它们传递给表,最终的解决方案如下所示:
@Component(defaultExecutionTarget = " TableView ", id = "id001", name = "componentLeft", active = true, resourceBundleLocation = "bundles.languageBundle", localeID = "en_US")public class ComponentLeft extends AFXComponent {private AnchorPane pane;private ObservableList<Tweet> tweets = FXCollections.observableArrayList();@Override/*** The handleAction method always runs outside the main application thread. You can create new nodes, execute long running tasks but you are not allowed to manipulate existing nodes here.*/public Node handleAction(final IAction<Event, Object> action) {// runs in worker threadif (action.getLastMessage().equals(MessageUtil.INIT)) {return this.createUI();}return null;}@Override/*** The postHandleAction method runs always in the main application thread.*/public Node postHandleAction(final Node arg0,final IAction<Event, Object> action) {// runs in FX application threadif (action.getLastMessage().equals(MessageUtil.INIT)) {this.pane = (AnchorPane) arg0;} else if (action.getLastMessage() instanceof TwitterResult) {tweets.clear();TwitterResult result = (TwitterResult) action.getLastMessage();if (!result.getResults().isEmpty()) {tweets.addAll(result.getResults());Collections.sort(tweets);}}return this.pane;}/*** create the UI on first call** @return*/private Node createUI() {final AnchorPane anchor = AnchorPaneBuilder.create().styleClass("roundedAnchorPaneFX").build();TableView table = new TableView();table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);table.getColumns().addAll(createColumns());table.setItems(tweets);AnchorPane.setTopAnchor(table, 25.0);AnchorPane.setRightAnchor(table, 25.0);AnchorPane.setLeftAnchor(table, 25.0);anchor.getChildren().addAll(table);GridPane.setHgrow(anchor, Priority.ALWAYS);GridPane.setVgrow(anchor, Priority.ALWAYS);return anchor;}private List<TableColumn> createColumns() {…return Arrays.asList(imageView, nameView, messageView);}
JacpFX UI组件具有定义的生命周期:每条消息首先通过handleAction(..)
传递,然后通过postHandleAction(…)
方法postHandleAction(…)
。 handleAction(..)
始终在工作线程中执行,因此您可以执行任何复杂且耗时的计算,而不会阻塞UI。 在这里,您可以自由创建新的UI节点并返回它们。 但是,您不能更改任何现有节点,因为您不在JavaFX Application Thread上。 然后,返回的Node将被传递到在应用程序线程上运行的postHandleAction(…)
。 您可以在此处看到完整的生命周期:
在postHandleAction(…)
我们检查TwitterResult
消息,并将Twitter条目添加到Table
。 现在我们完成了,您可以在GlassFish 4实例上部署ServerEndpoint并运行该应用程序。 如果启动许多实例,则TwitterResult
将传递给所有连接的客户端。 您可以在项目Wiki的此处找到JacpFX的完整文档。 有关JSR 356的更多信息,请访问项目页面和Arun Gupta的博客。
资源资源
- https://blogs.oracle.com/arungupta/
- https://java.net/projects/tyrus
- https://code.google.com/p/jacp/wiki/文档
- http://www.javacodegeeks.com/2012/03/building-rich-clients-with-jacpfx-and.html
参考: 通过我们的W4G合作伙伴 Andy Moncsek 将JacpFX客户端与JSR 356 WebSocket 一起使用 。
翻译自: https://www.javacodegeeks.com/2013/04/using-jacpfx-clients-with-jsr-356-websockets.html