Java で WebSocket を実装してみる。
クライアント側は JavaScript で実装。
#環境
##AP サーバー
Tomcat 8.0.15
##ブラウザ
- IE11
- Google Chrome 38
- Firefox 33
#HelloWorld
##build.gradle
apply plugin: 'war'
war.baseName = 'websocket'
repositories {
mavenCentral()
}
dependencies {
providedCompile 'javax.websocket:javax.websocket-api:1.1'
}
##フォルダ構成
|-build.gradle
`-src/main/
|-java/sample/websocket/
| `-SampleWebSocket.java
`-webapp/
|-index.html
`-script.js
##サーバー側実装
package sample.websocket;
import javax.websocket.OnMessage;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/echo")
public class SampleWebSocket {
@OnMessage
public String echo(String message) {
System.out.println(message);
return message;
}
}
##クライアント側実装
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Sample</title>
<script src="jquery.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<span id="message"></span>
</body>
</html>
$(function() {
var url = 'ws://localhost:8080/websocket/echo';
var ws = new WebSocket(url);
ws.onmessage = function(receive) {
$('#message').text(receive.data);
};
ws.onopen = function() {
ws.send('Hello WebSocket');
}
});
##動作確認
war ファイルを作成して tomcat にデプロイし、ブラウザで http://localhost:8080/websocket/
にアクセス。
Hello WebSocket
##説明
###サーバー側
@ServerEndpoint("/echo")
public class SampleWebSocket {
@OnMessage
public String echo(String message) {
System.out.println(message);
return message;
}
}
-
@ServerEndpoiint
でアノテートしたクラスが、サーバー側の WebSocket のエンドポイントになる。-
value
属性でパスを定義する。
-
-
@OnMessage
でメソッドをアノテートすると、クライアントからメッセージを受信したときに、そのメソッドが実行される。- 引数に受信したメッセージを受け取れる。
- 戻り値が、そのままクライアントに返信される。
###クライアント側
$(function() {
var url = 'ws://localhost:8080/websocket/echo';
var ws = new WebSocket(url);
ws.onmessage = function(receive) {
$('#message').text(receive.data);
};
ws.onopen = function() {
ws.send('Hello WebSocket');
}
});
-
WebSocket
のインスタンスを生成するとサーバーへの接続を開始する。 - 接続が完了すると、
onopen
に渡した関数がコールバックされる。 -
send()
メソッドでサーバーにメッセージを送信する。 - サーバーからメッセージが送られると、
onmessage
に設定した関数がコールバックされる。- 引数には MessageEvent オブジェクト が渡される。
#サーバーからクライアントにメッセージを送信する
サーバーからクライアントにメッセージを送る方法は、大きく2つある。
1つは Hello World のところの実装のように、 @OnMessage
でアノテートしたメソッドの戻り値を使う方法。
もう1つは、 RemoteEndpoint
を使う方法。
package sample.websocket;
import java.io.IOException;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/echo")
public class SampleWebSocket {
@OnMessage
public void echo(String message, Session session) throws IOException {
session.getBasicRemote().sendText(message);
}
}
-
javax.websocket.Session
を引数に追加して、getBasicRemote()
メソッドでRemoteEndpoint
のインスタンスを取得し、sendText()
メソッドでクライアントにメッセージを送信する。
##非同期でメッセージを送信する
getBasicRemote()
で取得できる RemoteEndpoint
は、メッセージの送信が完了するまで処理を待機する。
終了を待ちたくない場合は、 getAsyncRemote()
で RemoteEndpoint
を取得する。
package sample.websocket;
import java.io.IOException;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/echo")
public class SampleWebSocket {
@OnMessage
public void echo(String message, Session session) throws IOException {
session.getAsyncRemote().sendText(message);
}
}
#接続中の全てのクライアントにメッセージを送信する
##実装
package sample.websocket;
import java.io.IOException;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/broadcast")
public class SampleWebSocket {
@OnMessage
public void broadcast(String message, Session session) throws IOException {
session.getOpenSessions().forEach(s -> {
s.getAsyncRemote().sendText(message);
});
}
}
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Sample</title>
<script src="jquery.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<input type="text" id="message" />
<button id="send">Send</button>
<hr>
<span id="receive"></span>
</body>
</html>
$(function() {
$('#send').attr('disabled', true);
var ws = new WebSocket('ws://localhost:8080/websocket/broadcast');
ws.onopen = function() {
$('#send').attr('disabled', false);
};
ws.onmessage = function(receive) {
$('#receive').text(receive.data);
};
$('#send').on('click', function() {
var sendMessage = $('#message').val();
ws.send(sendMessage);
});
});
##動作確認
IE, Chrome, Firefox でページを開いて、 IE でテキストを入力、ボタンをクリックする。
すると、 Chrome, Firefox に IE で入力した文字が出力される。
##説明
-
Session.getOpenSessions()
で、現在接続中の全てのセッションを取得できる。
##問題点
この実装方法で、ブロードキャストは問題なく実装できる。
しかし、この実装には無駄が多い。
クライアントに送信するデータは全て同じ内容だが、送信するための形式にデータを変換する処理が毎回行われている。
同じデータは使いまわせるようにすると効率が良いか、現状 WebSocket の API にはこれを実現するものが定義されていない。
リファレンス実装である Tyrus を使っていれば、以下のように実装することでこの無駄を回避できる。
@OnMessage
public void onMessage(Session s, String m) {
((TyrusSession) s).broadcast(m);
}
今後のバージョンアップで改善されるかもしれない。
参考
#イベントハンドリング
##実装
package sample.websocket;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/event")
public class SampleWebSocket {
@OnOpen
public void onOpen(Session session) {
System.out.println("open : " + session.getId());
}
@OnClose
public void onClose(Session session) {
System.out.println("close : " + session.getId());
}
@OnError
public void onError(Session session, Throwable cause) {
System.out.println("error : " + session.getId() + ", " + cause.getMessage());
}
}
new WebSocket('ws://localhost:8080/websocket/event');
##動作確認
ブラウザで http://localhost:8080/websocket/
を表示。
open : 0
F5 で画面を再描画。
close : 0
open : 1
ブラウザを閉じる。
error : 1, java.util.concurrent.ExecutionException: java.io.IOException: 確立された接続がホスト コンピューターのソウトウェアによって中止されました。
close : 1
##説明
-
@OnOpen
で接続開始時、@OnClose
で接続終了時、@OnError
で通信エラー時の処理をハンドリングできる。 - WebSocket のセッションと HttpServletSession は完全に別物。
- ブラウザが再描画されれば JavaScript も実行され直すので、前の接続(セッション)は切断される。
#パスパラメータを使用する
##実装
package sample.websocket;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/pathparam/{string}/{number}")
public class SampleWebSocket {
@OnOpen
public void open(@PathParam("string") String string, @PathParam("number") int number) {
System.out.printf("OnOpen : string=%s, number=%d%n", string, number);
}
@OnMessage
public void message(String message, @PathParam("string") String string, @PathParam("number") int number) {
System.out.printf("Message : message=%s, string=%s, number=%d%n", message, string, number);
}
@OnClose
public void close(@PathParam("string") String string, @PathParam("number") int number) {
System.out.printf("Close : string=%s, number=%d%n", string, number);
}
}
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Sample</title>
<script src="jquery.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<button id="hoge">hoge</button>
<button id="fuga">fuga</button>
</body>
</html>
$(function() {
$('#hoge').on('click', function() {
var ws = new WebSocket('ws://localhost:8080/websocket/pathparam/hoge/11');
ws.onopen = function() {
ws.send('hoge message');
ws.close();
};
});
$('#fuga').on('click', function() {
var ws = new WebSocket('ws://localhost:8080/websocket/pathparam/fuga/22');
ws.onopen = function() {
ws.send('fuga message');
ws.close();
};
});
});
##動作確認
ブラウザでページを開いて、「hoge」ボタンをクリック。
OnOpen : string=hoge, number=11
Message : message=hoge message, string=hoge, number=11
Close : string=hoge, number=11
「fuga」ボタンをクリック。
OnOpen : string=fuga, number=22
Message : message=fuga message, string=fuga, number=22
Close : string=fuga, number=22
##説明
-
@ServerEndpoint
のvalue
属性に指定するパスに、{パラメータ名}
という形式でパスパラメータを定義することができる。 - パスパラメータは、
@OnOpen
,@OnMessage
,@OnClose
,@OnError
などの各メソッドで引数として受け取ることができる。 - メソッド引数には、
@PathParam("パラメータ名")
という形で受け取るパラメータを指定する。 - 基本は
String
だが、int
などのプリミティブ型で受け取ることも可能。
#接続を閉じる
##サーバー側から閉じる
###実装
package sample.websocket;
import java.io.IOException;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/close")
public class SampleWebSocket {
@OnMessage
public void close(String message, Session session) throws IOException {
session.close();
}
}
var ws = new WebSocket('ws://localhost:8080/websocket/close');
ws.onopen = function() {
ws.send(null);
}
ws.onclose = function(closeEvent) {
console.log('code = ' + closeEvent.code + ', reason = ' + closeEvent.reason);
};
###動作確認
code = 1000, reason =
##ブラウザ側から閉じる
###実装
var ws = new WebSocket('ws://localhost:8080/websocket/close');
ws.onopen = function() {
ws.close();
}
package sample.websocket;
import java.io.IOException;
import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/close")
public class SampleWebSocket {
@OnClose
public void onClose(Session session, CloseReason reason) throws IOException {
System.out.println("code = " + reason.getCloseCode().getCode() + ", reason = " + reason.getReasonPhrase());
}
}
###動作確認
code = 1000, reason = null
##説明
- サーバーから切断する場合は、
Session#close()
メソッドを使用する。 - クライアントから切断する場合は、
WebSocket#close()
メソッドを使用する。
#ServerEndpoint のインスタンスはセッションごとに生成される
##実装
package sample.websocket;
import javax.websocket.OnMessage;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/instance")
public class SampleWebSocket {
@OnMessage
public void message(String message) {
System.out.println(this.hashCode());
}
}
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Sample</title>
<script src="jquery.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<button id="send">Send</button>
</body>
</html>
$(function() {
var ws = new WebSocket('ws://localhost:8080/websocket/instance');
ws.onopen = function() {
$('#send').on('click', function() {
ws.send('message');
});
}
});
##動作確認
ブラウザ(Chrome)を開き、「Send」ボタンを何回かクリック。
1962593639
1962593639
1962593639
1962593639
新しいタブでページを開き、そちらで「Send」ボタンを何回かクリック。
2050833800
2050833800
2050833800
2050833800
F5で画面を再描画してから、「Send」ボタンを何回かクリック。
1334674039
1334674039
1334674039
##説明
ServerEndpoint のインスタンスは、セッションごと(コネクションごと)に作成される。
1つのセッションの中では、常に同じ ServerEndpoint のインスタンスが利用される。
また、 ServerEndpoint のインスタンスは常に1つのスレッドとだけ関連付けられる。
つまり、セッションごとの情報を保持しておきたい場合は、 ServerEndpoint のインスタンスフィールドに保持しておけば良い。
Note:
As opposed to servlets, WebSocket endpoints are instantiated multiple times.
(略)
Each instance is associated with one and only one connection. This facilitates keeping user state for each connection and makes development easier, because there is only one thread executing the code of an endpoint instance at any given time.
#サーバーから自発的にメッセージを送信する
ここまでの実装方法だと、サーバー側から自発的にクライアントにメッセージを送ることができない。
サーバー側から自発的にメッセージを送信するには、 ServerEndpoint の static フィールドに Session を保持しておき、 static メソッドを介して Session にアクセスする。
##実装
package sample.websocket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.websocket.OnClose;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/broadcast")
public class SampleWebSocket {
private static final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
static {
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleWithFixedDelay(SampleWebSocket::broadcast, 5, 5, TimeUnit.SECONDS);
}
@OnOpen
public void connect(Session session) {
sessions.add(session);
}
@OnClose
public void remove(Session session) {
sessions.remove(session);
}
public static void broadcast() {
Date now = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
sessions.forEach(session -> {
session.getAsyncRemote().sendText("Broadcast : " + formatter.format(now));
});
}
}
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Sample</title>
<script src="jquery.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<span id="message"></span>
</body>
</html>
$(function() {
var ws = new WebSocket('ws://localhost:8080/websocket/broadcast');
ws.onmessage = function(e) {
$('#message').text(e.data);
};
});
##動作確認
ブラウザをいくつか立ち上げて、 http://lcoalhost:8080/broadcast
を開く。
最初のアクセスから5秒ほど経過すると、各ブラウザに現在時刻が出力される。
以後、5秒ごとに時刻の表示が更新される。
##説明
-
@OnOpen
のときにSession
を static 宣言したConcurrentLinkedQueue
に格納している。 - また、
@OnClose
のときにSession
をキューから削除している。
自作の static フィールドに保存して管理するというのが、なんだか大丈夫なのかなと不安になるけど、 公式のチュートリアル がこうなっているのだから、きっと大丈夫なのだろう。
ちなみに、 JavaEE 環境なら CDI の Event を使ってもうちょっとスマート?に実装する方法が Stack Overflow で紹介されている。
java - How to get an existing websocket instance - Stack Overflow
#メッセージを Java オブジェクトにマッピングするための仕組みを利用する
##デコーダー(メッセージ→Javaオブジェクト)
###実装
package sample.websocket;
public class Hoge {
public int id;
public String name;
@Override
public String toString() {
return "Hoge [id=" + id + ", name=" + name + "]";
}
}
package sample.websocket;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;
public class HogeDecoder implements Decoder.Text<Hoge> {
@Override
public void init(EndpointConfig config) {
System.out.println("init");
}
@Override
public boolean willDecode(String s) {
System.out.println("willDecode");
return true;
}
@Override
public Hoge decode(String s) throws DecodeException {
System.out.println("decode");
String[] tokens = s.split(":");
Hoge hoge = new Hoge();
hoge.id = Integer.parseInt(tokens[0]);
hoge.name = tokens[1];
return hoge;
}
@Override
public void destroy() {
System.out.println("destroy");
}
}
package sample.websocket;
import javax.websocket.OnMessage;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint(value="/decode", decoders=HogeDecoder.class)
public class SampleWebSocket {
@OnMessage
public void message(Hoge hoge) {
System.out.println(hoge);
}
}
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Sample</title>
<script src="jquery.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<button id="send">Send</button>
<button id="close">Close</button>
</body>
</html>
$(function() {
var ws = new WebSocket('ws://localhost:8080/websocket/decode');
ws.onopen = function(e) {
$('#send').on('click', function() {
ws.send('12:Hoge');
});
$('#close').on('click', function() {
ws.close();
});
};
});
###動作確認
Web ブラウザでページを表示。
init
「Send」ボタンをクリック。
willDecode
decode
Hoge [id=12, name=Hoge]
もう一回クリック。
willDecode
decode
Hoge [id=12, name=Hoge]
「Close」ボタンをクリック。
destroy
###説明
-
Decoder
を実装したクラスを作成し、@ServerEndpoint
のdecoders
に指定する。 -
Decoder
にはテキストを変換するText<T>
や、バイナリデータを変換するBinary<T>
というサブインターフェースが用意されており、実際はこれらを実装したクラスを作成する。 - デコーダーが指定されていると、クライアントからのメッセージは一旦このデコーダーに渡される。
- デコーダーでは、以下のメソッドを実装する。
メソッド | 説明 |
---|---|
init | 初期化処理。セッションが確立されたときに1度だけ実行される。 |
willDecode | メッセージをデコードするかどうかを判定する。falseを返した場合、 decode() メソッドは実行されない。 |
decode | メッセージを Java オブジェクトに変換するデコード処理を実装する。 |
destroy | セッションが破棄されるときに1度だけ実行される。 |
-
@OnMessage
でアノテートした ServerEndpoint のメソッドには、デコーダーが作成した Java オブジェクトが引数に渡される。
##エンコーダー(Javaオブジェクト→メッセージ)
###実装
package sample.websocket;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
public class HogeEncoder implements Encoder.Text<Hoge>{
@Override
public void init(EndpointConfig config) {
System.out.println("init");
}
@Override
public String encode(Hoge hoge) throws EncodeException {
System.out.println("encode");
return hoge.id + ":" + hoge.name;
}
@Override
public void destroy() {
System.out.println("destroy");
}
}
package sample.websocket;
import javax.websocket.OnMessage;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint(value="/encode", encoders=HogeEncoder.class)
public class SampleWebSocket {
@OnMessage
public Hoge message(String message) {
System.out.println("message");
Hoge hoge = new Hoge();
hoge.id = 20;
hoge.name = "HOGE";
return hoge;
}
}
$(function() {
var ws = new WebSocket('ws://localhost:8080/websocket/encode');
ws.onopen = function() {
$('#send').on('click', function() {
ws.send(null);
});
$('#close').on('click', function() {
ws.close();
});
};
ws.onmessage = function(e) {
console.log(e.data);
};
});
###動作確認
ブラウザでページを表示。
init
「Send」ボタンをクリック。
message
encode
20:HOGE
「Close」ボタンをクリック。
destroy
###説明
- デコーダーの逆。変換の向きが逆になったこと以外、特に大きな違いはない。
#おまけ
Encoder と Decoder は、そのままだとパラメータで使う型の数だけ作成しないといけないため、ちょっとイケてない。
ということで、 json と任意の型とを変換する Encoder と Decoder を実装してみた。
##build.gradle
dependencies {
compile 'com.fasterxml.jackson.core:jackson-databind:2.4.3'
providedCompile 'javax.websocket:javax.websocket-api:1.1'
}
json の変換には Jackson を利用する。
##実装
package sample.websocket;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonConverter implements Encoder.Text<Object>, Decoder.Text<Object> {
public static final String ENDPOINT_CLASS = "json.endpoint.class";
private ObjectMapper mapper = new ObjectMapper();
private Map<String, Object> userProperties;
private Class<?> messageClass;
@Override
public void init(EndpointConfig config) {
this.userProperties = config.getUserProperties();
}
@Override
public Object decode(String s) throws DecodeException {
try {
Class<?> messageClass = this.getMessageClass();
return this.mapper.readValue(s, messageClass);
} catch (IOException e) {
throw new DecodeException(s, "failed to decode message.", e);
}
}
private Class<?> getMessageClass() {
if (this.messageClass == null) {
this.initMessageClass();
}
return this.messageClass;
}
private void initMessageClass() {
Class<?> endpointClass = (Class<?>)userProperties.get(ENDPOINT_CLASS);
Method onMessageMethod = this.findOnMessageMethod(endpointClass);
Parameter messageParameter = this.findMessageParameter(onMessageMethod);
this.messageClass = messageParameter.getType();
}
private Method findOnMessageMethod(Class<?> endpointClass) {
return this.findFromArray(endpointClass.getMethods(), this::isOnMessageMethod, "method annotated by @OnMessage is not found.");
}
private boolean isOnMessageMethod(Method method) {
return method.isAnnotationPresent(OnMessage.class);
}
private Parameter findMessageParameter(Method onMessageMethod) {
return this.findFromArray(onMessageMethod.getParameters(), this::isMessageParameter, "message parameter is not found.");
}
private boolean isMessageParameter(Parameter parameter) {
return this.isNotSession(parameter) && this.isNotPathParam(parameter);
}
private boolean isNotSession(Parameter parameter) {
return !Session.class.isAssignableFrom(parameter.getType());
}
private boolean isNotPathParam(Parameter parameter) {
return !parameter.isAnnotationPresent(PathParam.class);
}
private <T> T findFromArray(T[] array, Predicate<T> condition, String exceptionMessage) {
return Stream.of(array)
.filter(condition)
.findFirst()
.orElseThrow(() -> new RuntimeException(exceptionMessage));
}
@Override
public String encode(Object object) throws EncodeException {
try {
return this.mapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new EncodeException(object, "failed to encode message.", e);
}
}
@Override
public boolean willDecode(String s) {
return true;
}
@Override public void destroy() {/*no use*/}
}
package sample.websocket;
import javax.websocket.EndpointConfig;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint(value="/json", encoders=JsonConverter.class, decoders=JsonConverter.class)
public class SampleWebSocket {
@OnOpen
public void open(EndpointConfig config) {
config.getUserProperties().put(JsonConverter.ENDPOINT_CLASS, SampleWebSocket.class);
}
@OnMessage
public Hoge message(Hoge message, Session session) {
System.out.println("message = " + message);
Hoge hoge = new Hoge();
hoge.id = 33;
hoge.name = "fuga";
return hoge;
}
}
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Sample</title>
<script src="jquery.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<button id="send">Send</button>
<button id="close">Close</button>
</body>
</html>
$(function() {
var ws = new WebSocket('ws://localhost:8080/websocket/json');
ws.onopen = function() {
$('#send').on('click', function() {
var message = {id: 15, name: "Hoge"};
ws.send(JSON.stringify(message));
});
$('#close').on('click', function() {
ws.close();
});
};
ws.onmessage = function(e) {
console.log(e.data);
};
});
##動作確認
ブラウザでページを表示して、「Send」ボタンをクリック。
message = Hoge [id=15, name=Hoge]
{"id":33,"name":"fuga"}
##説明
-
EndpointConfig#getUserProperties()
で取得できる Map (UserProperties
)を介して、 ServerEndpoint の型の情報をやり取りしている。 -
SampleWebSocket
のopen()
メソッド内で、 ServerEndpoint の型をUserProperties
に設定する。 -
JsonConverter
側は、init()
メソッドでUserProperties
を取得してインスタンス変数に保存しておく。-
SampleWebSocket
のopen()
メソッドよりJsonConverter
のinit()
メソッドが先に呼ばれる。 - このため、この時点では
UserProperties
に型情報は保存されていない。
-
- そして、
decode()
メソッドが呼ばれたときに変換後の型情報をリフレクションで取得し、 Jackson でデコードしている。
これで JsonConverter
は使い回ししやすくなるはず。
#参考