Archive for 12月, 2012
Java EE 7 WebSocket Client Sample Application with JavaFX
At the previous entry, I wrote how to WebSocket application on Server Side. However JSR 356 Java API for WebSocket is providing Client API also. So in this entry, I will explain the WebSocket Client API with JavaFX.
The image of this application is like following video. There is one button on the bottom of application. If you click the button, the application connect to WebSocket Server and receive the messages and show the message on the TableView.
Preparation :
In order to compile and run the application, you need following libraries.
* grizzly-framework-2.2.19.jar
* grizzly-http-2.2.19.jar
* grizzly-http-server-2.2.19.jar
* grizzly-websocket-2.2.19.jar
* tyrus-servlet-1.0-b08.jar
You can download the Grizzly related libraries from following site.
http://grizzly.java.net/nonav/docs/docbkx2.2/html/dependencies.html
* Grizzly is a popular NetWork Server framework which is wrote by Java NIO. And originally the grizzly was created for the server engine for GlassFish. It has high scalability and good performance. If you use the grizzly libraries, you don’t need to write the low level socket programing by Java.
In this client application, I will use the Grizzly to connect the WebSocket Server with less code.
Also, you need to get the jar file as tyrus-servlet (reference implementation of Java API for WebSocket) and it include the javax.net.websocket package. Please get the libraries from following site ?
http://repo1.maven.org/maven2/org/glassfish/tyrus/tyrus-servlet/1.0-b08/
Then I will start to create a NetBeans Project by selecting JavaFX FXML Application.
In this Project, I will specified the project name as “JavaFX-WebSocket”. After that in order to be able to use the downloaded libraries in the project,I configured and added the libraries to the project.
If the project had created successfully , following 3 classes will be created by NetBeans.
”JavaFXWebSocket.java”, ”Sample.fxml”, ”SampleController.java”.
I will try to update the above 3 classes as follows. At first , I will customize the view(FXML). If you already configured the SceneBuilder, you can show the SceneBuilder screen on your desktop after you click the Sample.fxml file on NetBeans Project.
This Sample Application is very simple. So I only change the size of the Window and added two component as Label and TableView. After drag and drop these component, I inserted the ID as “table” for TableView. And there is TableColumn inside of TableView. So I specified the ID as “column” for TableColumn.
If you change the screen, you may get the FXML code like follows.
<?xml version="1.0" encoding="UTF-8"?> <?import java.lang.*?> <?import java.util.*?> <?import javafx.scene.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="453.0" xmlns:fx="http://javafx.com/fxml" fx:controller="javafx.websocket.SampleController"> <children> <Button fx:id="button" layoutX="326.0" layoutY="365.0" onAction="#handleButtonAction" text="Start TimeLine" /> <Label fx:id="label" layoutX="126.0" layoutY="120.0" minHeight="16.0" minWidth="69.0" /> <Label layoutX="14.0" layoutY="14.0" prefWidth="292.0" text="WebScoket Twitter TimeLine Client Smaple" underline="true" /> <TableView fx:id="table" layoutX="14.0" layoutY="45.0" prefHeight="311.0" prefWidth="425.0"> <columns> <TableColumn id="" maxWidth="445.0" prefWidth="445.0" text="Message List from Twitter" fx:id="column" /> </columns> </TableView> </children> </AnchorPane>
After customize the View, I will start to implement the Controller of JavaFX. For JavaFXWebSocket.java , there is no need to modify in this application. So I will use the NetBeans created code.
package javafx.websocket; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class JavaFXWebSocket extends Application { @Override public void start(Stage stage) throws Exception { Parent root = FXMLLoader.load(getClass().getResource("Sample.fxml")); Scene scene = new Scene(root); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); } }
I implemented the SampleController.java as follows. At first , I inserted the “@FXML TableView table” and “@FXML TableColumn column”. The field name is the same id of FXML(fx:id=”table”, fx:id=”column” ). And I implements the action of push the button on handleButtonAction method. Twitter Streaming is long running process. So I need to implement the check program as multi thread. At JavaFX ,javafx.concurrent.Service, javafx.concurrent.Task classes is prepared to implement the Task. So I had used the Service class to do it.
package javafx.websocket; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ResourceBundle; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javafx.concurrent.Service; import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; import javax.websocket.ClientEndpointConfiguration; import javax.websocket.DefaultClientConfiguration; import javax.websocket.DeploymentException; import org.glassfish.tyrus.client.ClientManager; public class SampleController implements Initializable { @FXML private TableView table; @FXML private TableColumn<RowData,String> column; @FXML private void handleButtonAction(ActionEvent event) { TwitterCheckService thread = new TwitterCheckService(table); thread.start(); } @Override public void initialize(URL url, ResourceBundle rb) { table.setEditable(true); column.setResizable(true); column.setCellValueFactory(new PropertyValueFactory<RowData, String>("message")); } class TwitterCheckService extends Service { private TableView table; private CountDownLatch messageLatch = null; public TwitterCheckService(TableView table) { this.table = table; } @Override protected Task createTask() { Task<Void> task = new Task<Void>() { @Override protected Void call() throws Exception { messageLatch = new CountDownLatch(1); try { URI clientURI = new URI("ws://localhost:8080/TwitterTimeLine/twitter"); // ClientContainer cliContainer = ContainerProvider.getClientContainer(); ClientManager cliContainer = org.glassfish.tyrus.client.ClientManager.createClient(); ClientEndpointConfiguration clientConfig = new DefaultClientConfiguration(); cliContainer.connectToServer(new TwitterClient(table), clientURI); messageLatch.await(1, TimeUnit.SECONDS); } catch (DeploymentException | URISyntaxException | InterruptedException ex) { Logger.getLogger(SampleController.class.getName()).log(Level.SEVERE, null, ex); } return null; } }; return task; } } }
In the above class, there is initialize() method to initialize the class. In the method, I wrote column.setCellValueFactory(new PropertyValueFactory(“message”)) . This is the value configuration of the Table column. In fact, the field of “message” in RowData class will be showed on every column in the Table. I specified the javafx.scene.text.Text type instead of String type, because it was difficult to manage the size of String in the Column of TableView. So instead of String, I used the Text and Text#setWrappingWidth could manage the size of viewable String.
package javafx.websocket; import javafx.scene.text.Text; public class RowData { private Text message; public RowData(Text message) { this.message = message; message.setWrappingWidth(400); } public Text getMessage() { return message; } public void setMessage(Text message) { this.message = message; } }
I will pick up and explain the important point of WebSocket Client in SampleController class as follows. I wrote the comment in the code as “// ClientContainer cliContainer = ContainerProvider.getClientContainer();” In fact the above code is recommended as standard. And you should write the following property on the system property.
“webocket.clientcontainer.classname= actual class name ”
However in this programing , I faced the error. Thus I wrote the following in order to get the instance of ClientContainer. “ClientManager cliContainer = org.glassfish.tyrus.client.ClientManager.createClient();”
URI clientURI = new URI("ws://localhost:8080/TwitterTimeLine/twitter"); // ClientContainer cliContainer = ContainerProvider.getClientContainer(); ClientManager cliContainer = org.glassfish.tyrus.client.ClientManager.createClient(); ClientEndpointConfiguration clientConfig = new DefaultClientConfiguration(); cliContainer.connectToServer(new TwitterClient(table), clientURI); messageLatch.await(20, TimeUnit.SECONDS);
I specified the “TwitterClient(table)” inside of cliContainer.connectToServer(new TwitterClient(table), clientURI) line. It is a WebSocket Client code which equal to the class of adding the @WebSocketEndpoint annotation at the server side. Instead of the “@WebSocketEndpoint” annotation, we must specify the “@WebSocketClient” annotation at the client side code. Following is the client side code of WebSocket client.
package javafx.websocket; import javafx.collections.ObservableList; import javafx.scene.control.TableView; import javafx.scene.text.Text; import javax.websocket.Session; import javax.websocket.WebSocketClient; import javax.websocket.WebSocketClose; import javax.websocket.WebSocketMessage; import javax.websocket.WebSocketOpen; @WebSocketClient public class TwitterClient { TableView table; ObservableList<RowData> list; public TwitterClient(TableView table) { this.table = table; } @WebSocketOpen public void onOpen(Session session) { System.out.println("Connection had opened."); } @WebSocketMessage public void onMessage(String message) { if (message.length() == 0) { return; } // In order to adjst the size of String in Table, I used Text. Text text = new Text(message); list = table.getItems(); list.add(0,new RowData(text)); } @WebSocketClose public void closeConnection(Session session) { System.out.println("Connection had closed."); } }
Actually the code is very similar to the server side code. Instead of the @WebSocetEndpoint, I specified the @WebSocketClient annotation. And inside of the class, we can implements the method which is added @WebSocketOpen, @WebSocketMessage and @WebSocketClose annotation. In this program, client receive the message from the server. And there is no need to send the data to server. So I implemented the @WebSocketMessage public void onMessage(String message) method. In this method, the client receive the message from server and wrapping the String to Text object in order to adjust the length of the viewable String (Text text = new Text(message)). Finally the message is inserted into the first line of the Table.
This WebSocket client code is very easy to implement. So you can write the WebSocket program very easily not only server side but also Java Application like JavaFX.
Java EE 7 WebSocket Server-Side Programing with Twitter4J
In this entry, I will explain the new feature of Java EE 7. In Java EE 7, Java API for WebSocket (JSR-356) will be included.
I explained these technology at Japanese Java conference as follows. At that time, I showed the Twitter Timeline demo by using the WebSocket. So I will explain following how to create WebSocket application as standard technology of Java.
Now Java EE expert group is developing and evaluating the specification of JSR-356 Java API for WebSocket. So it may be possible to change the coding style when the Java EE 7 had released officially release at next year (We are planning to release the Java EE 7 at 2013 Spring). So please note this point ?
This program was created for GlassFish 4.0 b58 environment at November. You can download the promoted build of GlassFish v4.0 from following site. So please download it before the coding ?
http://dlc.sun.com.edgesuite.net/glassfish/4.0/promoted/
Latest version of the GlassFish was “glassfish-4.0-b67.zip”. (2012/12/20)
It seems that already the API had changed during this one month. Arun Gupta already mentioned about it on following entry.
WebSocket Samples in GlassFish 4 build 66 – javax.websocket.* package: TOTD #190
So if you get the latest version of the GlassFish, please refer to the above entry too?
And you need to get the “Twitter4J” libraries from following site before coding. Twitter4J. Twitter4J is very very useful libraries to create Twitter service by Java. Twitter4J is created by Japanese guyes whose name is Yusuke Yamamoto-san.
Actually I got two libraries as follows.
* twitter4j-core-2.2.6.jar
* twitter4j-stream-2.2.6.jar
Perhaps as you may know, WebSocket is able to communicate by Full duplex and bi-directional between Client and Server. And it is used the upgrade protocol of HTTP protocol as WebSocket protocol.
Once WebSocket connection had established, it is possible to write the program depends on the WebSocket lifecycle.
* Open (@WebSocketOpen)
* Receiving the Message (@WebSocketMessage )
* Error (@WebSocketError)
* Close (@WebSocketClose)
I will show the sample code of getting the Twitter Timeline as follows. At first, you need to specify the @WebSocketEndpoint annotation to the Java class. And you need to specify the context root in the argument of the annotation. For example, if you specify following,
@WebSocketEndpoint(value = “/twitter”) // since build 61
the client can connect to the server by using following
“ws://WEBSOCKET-SERVER/APP_NAME/twitter”.
And I implemented two method on the class as initOpen, closeWebSocket. And also I specified two annotation as @WebSocketOpen, @WebSocketClose.
* @WebSocketOpen initOpen : if the client access to the server, it inserted the Session to the Set(peers). Session has the information of RemoteEndpoint(client).
@ @WebSocketClose closeWebSocket : if the connection had cut, it delete the Session from the Set(peers). Session has the information of RemoteEndpoint(client).
package jp.co.oracle; import java.io.IOException; import javax.net.websocket.Session; import javax.net.websocket.annotations.WebSocketClose; import javax.net.websocket.annotations.WebSocketEndpoint; import javax.net.websocket.annotations.WebSocketMessage; import javax.net.websocket.annotations.WebSocketOpen; @WebSocketEndpoint(path = "/twitter") // build 58 // @WebSocketEndpoint(value = "/twitter") build 61 public class TwitterWebSocket { @WebSocketOpen public void initOpen(Session session) { TwitterClientSingleton.peers.add(session); } @WebSocketClose public void closeWebSocket(Session session) { TwitterClientSingleton.peers.remove(session); } }
Next, I created Twitter monitoring program by using Singleton EJB with Twitter4j lib. In this program, if the EJB received the new message from Twitter, the EJB automatically send the message to all of connected WebSocket Client. In the EJB, I had used the Streaming API of Twitter4J. And also I specified two annotation to the class as @Startup and @Singleton. Thus, the EJB will be automatically initialized and started the service by EJB Container when the application is started.
Once EJB had loaded, initTwitterStream() method will be called by container because @PostConstruct annotation is specified to the method. In the method, it initialized Twitter4J API and it is using the Twitter Stream API. And in this example, I specify the search keyword as “java”.
In the Twitter4J, StatusAdapter is provided and onStatus() method can receive the message when the above filter had matched. So I wrote the code of publishing the message to all of connected RemoteEndpoint in this method. Of course if you would like to write JSON, you can do it.
(* NOTE: If you would like to receive the message from client, you can write the method with @WebSocketMessage annotation.)
package jp.co.oracle; import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Future; import javax.annotation.PostConstruct; import javax.ejb.Singleton; import javax.ejb.Startup; import javax.net.websocket.SendHandler; import javax.net.websocket.SendResult; import javax.net.websocket.Session; import twitter4j.FilterQuery; import twitter4j.Status; import twitter4j.StatusAdapter; import twitter4j.TwitterStream; import twitter4j.TwitterStreamFactory; import twitter4j.User; @Startup @Singleton public class TwitterClientSingleton extends StatusAdapter { private static TwitterStream twStream = null; public static Set<Session> peers = null; // Session の Set (The information of the RemodeEndpoint is includedn in Session object) static { peers = Collections.synchronizedSet(new HashSet()); } @PostConstruct public void initTwitterStream() { //Initialize the Twitter Stream of Twitter4J twStream = TwitterStreamFactory.getSingleton(); FilterQuery filter = new FilterQuery(); filter.track(new String[]{"java"}); twStream.addListener(this); twStream.filter(filter); } @Override public void onStatus(Status status) { // when the filter had matched this code will be called User user = status.getUser(); if (status.getUser().getLang().equals("ja")) { String resStr = "@" + user.getScreenName() + " : " + status.getText(); try { // I send the message to all of connected client as sequencial for (Session peer : peers) { peer.getRemote().sendString(resStr); } } catch (IOException ioe) { ioe.printStackTrace(); } } } }
Finally, I will write the View. This View is very very simple. So you can write simply HTML or JSF or other. However in this example, I will write as JSF faceless. In the program, if this client receive the Twitter message from Server, onMessage() of JavaScript is called and show the message on the first line of the HTML Table.
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html"> <h:head> <title>Twitter TimeLine Sample</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>Twitter TimeLine Sample</title> <style type="text/css"> table,td,th { width: 700px; border-collapse: collapse; border: 1px black solid; } </style> <script language="javascript" type="text/javascript"> var wsUri = "ws://localhost:8080/TwitterTimeLine/twitter"; var websocket = new WebSocket(wsUri); websocket.onopen = function(evt) { onOpen(evt) }; websocket.onmessage = function(evt) { onMessage(evt) }; websocket.onerror = function(evt) { onError(evt) }; var numberOfMessage; function init() { numberOfMessage = 0; } function onOpen(evt) { ; } function onMessage(evt) { writeToScreen(evt.data); numberOfMessage++; } function onError(evt) { writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data); } function writeToScreen(messages) { var table = document.getElementById("TBL"); var row = table.insertRow(0); var cell1 = row.insertCell(0); var textNode = document.createTextNode(messages); var z = numberOfMessage%2; if(z==1){ cell1.style.backgroundColor="#ADD8E6" ; } cell1.appendChild(textNode); } window.addEventListener("load", init, false); </script> </h:head> <h:body> <h2>Twitter Time Line <BR/>WebSocket Sample Application!!</h2> <TABLE BORDER="1" ID="TBL"> </TABLE> </h:body> </html>
In order to run the Application, You need to get the consumer key and access Token from Twitter. http://twitter.com/oauth_clients/new After created the consumer key and access token, you need to write the following properties on twitter4j.properties file. And please placed the properties file to WEB-INF/classes ?
# To change this template, choose Tools | Templates # and open the template in the editor. debug=false oauth.consumerKey=********************* oauth.consumerSecret=**************************************** oauth.accessToken=********-**************************************** oauth.accessTokenSecret=****** |
And also you need the following libraries to compile and run.
* Twitter4J: twitter4j-core-2.2.6.jar
* Twitter4J: twitter4j-stream-2.2.6.jar
* WebSocket: tyrus-servlet.jar (GlassFish-INSTALL/glassfish/modules)
* EJB : (GlassFish-INSTALL/glassfish/modules)
* CDI : (GlassFish-INSTALL/glassfish/modules)
I will show the directory structure of NetBeans project as follows.
Finally :
Perhaps you can understand that the Java API for WebSocket API is very very easy to use. And also if you use the Singleton EJB , it is very useful to monitor the backend service.
For example, please consider the DataBase monitoring instead of Twitter. If client request the DB update information to the Application Server, the Application Server will send the query to DB server for every individual request until now. However if you create the DB monitoring application like this, the load of DB may be extremery decrease.
Following is the demo video of this sample application.
WebSocket is the key technology included in the Java EE 7. And it is very easy to implement. If you are interested in the WebSocket on Java Platfor, please try to use the NetBeans and GlassFish v4.
Concurrency Update (jsr166e)のご紹介
この記事はJava Advent Calendar 2012 の 18 日目の記事です。
昨日は @torutk さんによる「Javaで地図表示ーGeoToolsを使って」でした。
明日は @Akira Koyasu さんです。
Java に関して何を記載しようかと悩んでいたのですが、やはり Java SE 8 のネタが良いかと思い、Java SE 8 に含まれる予定の「Concurrency Update (jsr166e)」について、かんたんにご紹介したいと思います。
Java SE 8 に含まれる予定の機能一覧は下記に記載されております。
JEP (JDK Enhancement-Proposal)
上記をご覧頂くと様々な機能拡張が施される事をご確認頂けますが、並行処理についても下記の JEP として更新される予定のようですので、少しだけご紹介したいと思います。
JEP : 155 Concurrency Updates (jsr166e)
Java SE に含まれる並行処理用の API は Java SE 5 に JSR 166: Concurrency Utilities として、java.util.concurrent パッケージで提供されました。その後、Java SE 6, 7 でもこれらのライブラリはアップデートされ、直近では Java SE 7 で jsr-166y として Fork/Join フレームワークが追加された事は記憶に新しいかと思います。
さて、Java SE 8 では jsr-166 はさらに進化して jsr-166e として機能拡張が施されようとしています。ちなみに「jsr-166e」の「e」は「Eight」つまり「8」を意味しております。jsr-166e で提供される API の一覧は下記をご参照ください。
JSR-166e の主な拡張ポイント:
● ConcurrentHashMap に対するキャッシュ指向の機能拡張
● スケーラブルで更新可能な変数
● ForkJoinPool に対する改良
これらを簡単にご紹介します。
「ConcurrentHashMap」について
ConcurrentHashMap は Java SE 5 に導入されて以降、様々な所で使われてきました。しかし ConcurrentHashMap は必要以上にメモリを消費するという問題もあり、Java SE 8 に導入予定の ConcurrentHashMap は少ないメモリで効率的に利用できるようです。またシーケンシャル処理も、並行処理もサポートし大量のコンテンツを扱う場合に有効に働きます。
ConcurrentHashMapV8 の API リファレンス
ConcurrentHashMapV8 のソースコード
「スケーラブルで更新可能な変数」
Adder(加算用の機能) について
Adder として追加される、DoubleAdder, LongAdder は Java SE 8 では java.util.concurrent.atomic パッケージ内に含まれる予定です。ここで追加される Adder (加算用の機能)はとても高性能な並行処理対応のプリミティブの加算用の機能を提供します。それぞれサンプルのコードが下記より入手できます。
DoubleAdder
LongAdder
LongAdderTable
ソースコードはコチラから 参照できます。
DoubleAdderDemo のサンプル
LongAdderDemo のサンプル
上記の、LongAdderDemo を私の環境で実行した所、複数のスレッドで AtomicLong で 10,000,000 まで加算していく場合と、LongAdder を使用して加算していく場合で下記の違いがありました。この結果を見てもわかる通り、並列で加算を行う場合、AtomicLong よりも、LongAdder を使用した方が高速に動作する事がお分かりいただけるかと思います。
Doug Lea によって書かれた LongAdder のコード:
/* * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ */ import java.util.concurrent.Phaser; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicLong; import jsr166e.LongAdder; public class LongAdderDemo { static final int INCS_PER_THREAD = 10000000; static final int NCPU = Runtime.getRuntime().availableProcessors(); static final ExecutorService pool = Executors.newCachedThreadPool(); public static void main(String[] args) { System.out.println("Warmup..."); int half = NCPU > 1 ? NCPU / 2 : 1; casTest(half, 1000); adderTest(half, 1000); for (int reps = 0; reps < 2; ++reps) { System.out.println("Running..."); for (int i = 1; i <= NCPU * 2; i <<= 1) { casTest(i, INCS_PER_THREAD); adderTest(i, INCS_PER_THREAD); } } pool.shutdown(); } static void casTest(int nthreads, int incs) { System.out.print("AtomicLong "); Phaser phaser = new Phaser(nthreads + 1); AtomicLong a = new AtomicLong(); for (int i = 0; i < nthreads; ++i) pool.execute(new CasTask(a, phaser, incs)); report(nthreads, incs, timeTasks(phaser), a.get()); } static void adderTest(int nthreads, int incs) { System.out.print("LongAdder "); Phaser phaser = new Phaser(nthreads + 1); LongAdder a = new LongAdder(); for (int i = 0; i < nthreads; ++i) pool.execute(new AdderTask(a, phaser, incs)); report(nthreads, incs, timeTasks(phaser), a.sum()); } static void report(int nthreads, int incs, long time, long sum) { long total = (long)nthreads * incs; if (sum != total) throw new Error(sum + " != " + total); double secs = (double)time / (1000L * 1000 * 1000); long rate = total * (1000L) / time; System.out.printf("threads:%3d Time: %7.3fsec Incs per microsec: %4d\n", nthreads, secs, rate); } static long timeTasks(Phaser phaser) { phaser.arriveAndAwaitAdvance(); long start = System.nanoTime(); phaser.arriveAndAwaitAdvance(); phaser.arriveAndAwaitAdvance(); return System.nanoTime() - start; } static final class AdderTask implements Runnable { final LongAdder adder; final Phaser phaser; final int incs; volatile long result; AdderTask(LongAdder adder, Phaser phaser, int incs) { this.adder = adder; this.phaser = phaser; this.incs = incs; } public void run() { phaser.arriveAndAwaitAdvance(); phaser.arriveAndAwaitAdvance(); LongAdder a = adder; for (int i = 0; i < incs; ++i) a.increment(); result = a.sum(); phaser.arrive(); } } static final class CasTask implements Runnable { final AtomicLong adder; final Phaser phaser; final int incs; volatile long result; CasTask(AtomicLong adder, Phaser phaser, int incs) { this.adder = adder; this.phaser = phaser; this.incs = incs; } public void run() { phaser.arriveAndAwaitAdvance(); phaser.arriveAndAwaitAdvance(); AtomicLong a = adder; for (int i = 0; i < incs; ++i) a.getAndIncrement(); result = a.get(); phaser.arrive(); } } }
MaxUpdater
また、Adder (加算器)と同様に、並行処理の中でハイ・パフォーマンスに最大値を取得する API も追加されます。
DoubleMaxUpdater
LongMaxUpdater
ForkJoinPool について
新機能の追加の他、パフォーマンスが大幅に改善されています。これによって、今まで扱う場所が限定されていた ForkJoinPool が、開発場面の色々な場面で利用できるようになるようです。
最後に、
Java SE 8 では ConcurrentHashMap 等の並行処理機能も拡張・追加されていますので、ご興味ある方は是非チェックしてみてください。
Java EE 7 WebSocket Client Sample Application with JavaFX
この記事はJavaEE Advent Calendar 2012の14日目の記事です。
昨日は @noriand さんによる「Spring on Glassfish」でした。
明日は@kokuzawaさんです。
2013 年 5 月 13 日追記:本ソースコードは、WebSocket の仕様が完全に FIX する前に記載したコードのため、既に記載している内容のコードでは動かなくなっています。新しい API の詳細は、javax.websocketパッケージ、javax.websocket.serverをご参照ください。 |
さて、今年は何を書こうかととてもなやんでいた所、昨日の深夜12時過ぎに、
@skrb さんから、「@yoshioterada 寺田さん、JavaFX Advent Calednarに登録していないようですけど…. GlassFish と JavaFX を絡めて、両方のAdvent Calendar に登録するのでもいいですよww」
とおっしゃって頂き(敷居があがり (^_^;) )、以前のセミナーで説明した Java の WebSocket のクライアント側の実装をご紹介しようという事で書いてみます。
※ また、本プログラムは、Java EE 7 に含まれる予定の WebSocket API を利用している事から、現時点での実装方法のサンプルとなります。Java SEE 7 の正式リリース時には API 等が変更される可能性も多いにありますので、どうぞその点はご注意ください。
サーバ側の実装コードは、先日、WebSocket Twitter タイムライン・取得サンプルのエントリで記載しました。このエントリを記載した後にも既に GlassFish v4 の b66 では API が若干変わっていますので、あくまでの参考としてご参照ください。
今回は、このサーバ側の実装をそのまま利用して、クライアント側のプログラムを JavaFX (Java のアプリケーション)として動作させるというのが今回のブログの趣旨となります。
作成する JavaFX の WebSocket アプリの動作は下記のようなイメージです。この JavaFX アプリケーションは、ボタンを押下すると WebSocket のサーバに接続しサーバから送信されるメッセージを受信し TextArea 内に表示するという内容です。
準備:
本プログラムを動作させるためには、下記のライブラリが必要になります。
* grizzly-framework-2.2.19.jar
* grizzly-http-2.2.19.jar
* grizzly-http-server-2.2.19.jar
* grizzly-websocket-2.2.19.jar
* tyrus-servlet-1.0-b08.jar
* javax.net.websocket-1.0-b08.jar
Grizzly 関連の jar ファイルはコチラから御入手ください。
※ Grizzly とは GlassFish のサーバ・エンジンを司る汎用サーバ・フレームワークで Grizzly のライブラリを利用する事で自身で低レベルのソケットプログラムを記載する事なく、ネットワーク通信のプログラムを記載する事が可能になります。今回クライアント側からサーバ側に対してネットワーク接続を行うために、この Grizzly のライブラリを内部的に利用します。
また、tyrus と javax.net.websocket の jar につきましては、https://maven.java.net/ からここで説明した内容と同様の方法でそれぞれのライブラリを検索して頂き、御入手ください。
それでは、NetBeans を使用して JavaFX のアプリケーションを作成しましょう。
まず、NetBeans から新規プロジェクトを作成します。
ここでは、プロジェクト名として「JavaFX-WebSocket」という名前で作成します。
次に、準備の所で入手した全ライブラリを本プロジェクトのライブラリ内に配置し利用できるようにします。
ここでは、”JavaFXWebSocket.java”、”Sample.fxml”、”SampleController.java”の3つのファイルが作成されていますので、これらを編集してプログラムを作っていきます。
まず、JavaFX SceneBuilder を使用してデザインを作成します。今回はとても簡単にするために、画面サイズを調整し、Label, TableView を貼付けます。また、TableView に対して ID として “table” を設定します。また、TableView 内の TableColumn に対して column
の ID を設定します。
できあがった、Sample.fxml は下記の通りです。
<?xml version="1.0" encoding="UTF-8"?> <?import java.lang.*?> <?import java.util.*?> <?import javafx.scene.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="453.0" xmlns:fx="http://javafx.com/fxml" fx:controller="javafx.websocket.SampleController"> <children> <Button fx:id="button" layoutX="326.0" layoutY="365.0" onAction="#handleButtonAction" text="Start TimeLine" /> <Label fx:id="label" layoutX="126.0" layoutY="120.0" minHeight="16.0" minWidth="69.0" /> <Label layoutX="14.0" layoutY="14.0" prefWidth="292.0" text="WebScoket Twitter TimeLine Client Smaple" underline="true" /> <TableView fx:id="table" layoutX="14.0" layoutY="45.0" prefHeight="311.0" prefWidth="425.0"> <columns> <TableColumn id="" maxWidth="445.0" prefWidth="445.0" text="メッセージ一覧" fx:id="column" /> </columns> </TableView> </children> </AnchorPane>
次にコードを実装していきます。まず、JavaFXWebSocket.java についてはデフォルトのまま一切編集を加えずにそのまま利用します。
package javafx.websocket; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class JavaFXWebSocket extends Application { @Override public void start(Stage stage) throws Exception { Parent root = FXMLLoader.load(getClass().getResource("Sample.fxml")); Scene scene = new Scene(root); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); } }
次に、SampleController.java を編集します。@FXML TableView table は、Sample.fxml に追加した TableView と一対一で対応します。次に、ボタンが押下された際の処理を実装しますが、Button が押下された時の処理は別スレッドで実装します。別スレッドで実装しないと、処理が終了するまで処理が専有してしまうため他に一切処理ができなくなります。ボタンが押下された際の処理は、javafx.concurrent.Service を継承したクラスで実装します。(※ JavaFX では非同期処理を javafx.concurrent.Service, javafx.concurrent.Task 等を利用して実装します。)
package javafx.websocket; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ResourceBundle; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javafx.concurrent.Service; import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; import javax.websocket.ClientEndpointConfiguration; import javax.websocket.DefaultClientConfiguration; import javax.websocket.DeploymentException; import org.glassfish.tyrus.client.ClientManager; public class SampleController implements Initializable { @FXML private TableView table; @FXML private TableColumn<RowData,String> column; @FXML private void handleButtonAction(ActionEvent event) { TwitterCheckService thread = new TwitterCheckService(table); thread.start(); } @Override public void initialize(URL url, ResourceBundle rb) { table.setEditable(true); column.setResizable(true); column.setCellValueFactory(new PropertyValueFactory<RowData, String>("message")); } class TwitterCheckService extends Service { private TableView table; private CountDownLatch messageLatch = null; public TwitterCheckService(TableView table) { this.table = table; } @Override protected Task createTask() { Task<Void> task = new Task<Void>() { @Override protected Void call() throws Exception { messageLatch = new CountDownLatch(1); try { URI clientURI = new URI("ws://localhost:8080/TwitterTimeLine/twitter"); // ClientContainer cliContainer = ContainerProvider.getClientContainer(); ClientManager cliContainer = org.glassfish.tyrus.client.ClientManager.createClient(); ClientEndpointConfiguration clientConfig = new DefaultClientConfiguration(); cliContainer.connectToServer(new TwitterClient(table), clientURI); messageLatch.await(1, TimeUnit.SECONDS); } catch (DeploymentException | URISyntaxException | InterruptedException ex) { Logger.getLogger(SampleController.class.getName()).log(Level.SEVERE, null, ex); } return null; } }; return task; } } }
上記のクラスにおいて、初期化の initialize メソッド中に、column.setCellValueFactory(new PropertyValueFactory(“message”));
を記載しており、ここで Table の各行で表示する内容を設定しています。実際に各行で表示される内容は RowData の message に設定される内容を表示します。message は 単なる String 型ではなく、javafx.scene.text.Text 型としていますが、これは Table の各セルで文字列の改行が自動的になされないため、Text でラッピングし Text#setWrappingWidth で文字列の長さを調整しています。
package javafx.websocket; import javafx.scene.text.Text; public class RowData { private Text message; public RowData(Text message) { this.message = message; message.setWrappingWidth(400); } public Text getMessage() { return message; } public void setMessage(Text message) { this.message = message; } }
また、SampleController クラスの中で、WebSocket のクライアント側の実装として重要な部分を下記に抜粋します。まず、コメントしている側のコードなのですが、本来 WebSocket のクライアントの実装として推奨されるのはコメントしてある側のコードです。ClientContainer を ContainerProvider.getClientContainer() から取得するというのが正しい実装で、実際に利用するクラスは、システム・プロパティに
“webocket.clientcontainer.classname=実際のクラス名” という記載を行う事でプロパティから読み込みます。しかし、今回エラーが発生してし取得する事ができなかったため、ClientContainer の実装(ClientManager)を直接、org.glassfish.tyrus.client.ClientManager.createClient() から取得しています。
URI clientURI = new URI("ws://localhost:8080/TwitterTimeLine/twitter"); // ClientContainer cliContainer = ContainerProvider.getClientContainer(); ClientManager cliContainer = org.glassfish.tyrus.client.ClientManager.createClient(); ClientEndpointConfiguration clientConfig = new DefaultClientConfiguration(); cliContainer.connectToServer(new TwitterClient(table), clientURI); messageLatch.await(20, TimeUnit.SECONDS);
また、cliContainer.connectToServer(new TwitterClient(table), clientURI) で記載している TwitterClient(table) は、WebSocket のサーバ側の実装では @WebSocketEndpoint のアノテーションが付加されたクラスと同じ働きをするクライアント側の実装を行っているクラスと認識していただければと思います。下記に TwitterClient クラスの実装を記載します。
package javafx.websocket; import javafx.collections.ObservableList; import javafx.scene.control.TableView; import javafx.scene.text.Text; import javax.websocket.Session; import javax.websocket.WebSocketClient; import javax.websocket.WebSocketClose; import javax.websocket.WebSocketMessage; import javax.websocket.WebSocketOpen; @WebSocketClient public class TwitterClient { TableView table; ObservableList<RowData> list; public TwitterClient(TableView table) { this.table = table; } @WebSocketOpen public void onOpen(Session session) { System.out.println("Connection had opened."); } @WebSocketMessage public void onMessage(String message) { if (message.length() == 0) { return; } // Table 内で文字列を改行するために String を Text で Wrap Text text = new Text(message); list = table.getItems(); list.add(0,new RowData(text)); } @WebSocketClose public void closeConnection(Session session) { System.out.println("Connection had closed."); } }
サーバ側で WebSocket のコードを実装する場合は、@WebSocketEndpoint アノテーションを付加していました、クライアント側はその代わりに、@WebSocketClient アノテーションを付加したクラスを実装します。実際に記載する内容は、@WebSocketEndpoint の内容と同等で、@WebSocketOpen, @WebSocketMessage, @WebSocketClose のアノテーションを付加したメソッドをそれぞれ実装します。このプログラムでは、サーバ側からメッセージが配信されてき、サーバ側に対してメッセージを送信する必要はありませんので、実際に
は @WebSocketMessage を付加した public void onMessage(String message) メソッドの部分が重要になります。ここでは、サーバ側からメッセージが配信された場合、受信 した文字列を Text text = new Text(message) でラッピングし、現在 Table に設定されている Item を抜き出した後、Table の先頭行に RowData を追加しています。
この程度のプログラムであれば、特に難しい事を考えなくても簡単に、クライアント側の WebSocket プログラムを作成する事ができるかと思います。
最後に、
今年は Java EE の単独で Advent Calendar が作成された事に対してとても嬉しく思うと共に、作成してくださった、@megascus さん本当にありがとうございました。
Java EE 7 は、来年の春頃にリリースされますが、WebSocket の他にもとても便利になった API などが多数提供される予定です。いち早く、Java EE 7 の各機能を試してみたい方は GlassFish v4 の開発ビルドをダウンロードしていただく事で試していただく事もできます。是非今から Java EE 7 にお備えください。