javascript
将JacpFX客户端与JSR 356 WebSockets一起使用
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
總結
以上是生活随笔為你收集整理的将JacpFX客户端与JSR 356 WebSockets一起使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 黄金ETF是T0吗?
- 下一篇: EC2上的ElasticSearch不到