В предыдущем своём посте я коротко рассказал про реализацию веб-сокетов в Java EE и в качестве примера привёл простое приложение, которое представляет собой эхо-сервис: клиент отправляет серверу текстовое сообщение и получает его обратно. Однако передача текстовых данных - лишь основа для тех возможностей, которые предоставляет реализация веб-сокетов в Java EE. Одной из таких возможностей является передача объектов в виде JSON-строки.
Отправка объектов
Для отправки объектов существует метод sendObject в субинтерфейсах Async и Basic интерфейса RemoteEndpoint. В качестве аргумента он принимает объект, для которого существут класс-энкодер, реализующий один из субинтерфейсов Encoder (Text, TextStream, Binary и BinaryStream). В качестве примеров в данном посте будет использован Encoder.Text. Для каждого класса, объекты которого будут передаваться через веб-сокеты, необходимо написать класс-энкодер. Для всех четырёх интерфейсов обязательны к реализации три метода: init и destroy для выполнения необходимых действий до и после обработки объекта, а так же encode, в котором и происходит преобразование объекта в JSON-строку. На стороне клиента теперь остаётся вызвать JSON.parse для преобразования полученного сообщения в объект.
public class ChatMessageEncoder implements Encoder.Text<ChatMessage> {
public static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static final String MESSAGE_CLASS_NAME = "org.acruxsource.sandbox.websocket.domain.ChatMessage";
@Override
public void init(EndpointConfig config) {
}
@Override
public void destroy() {
}
@Override
public String encode(ChatMessage object) throws EncodeException {
DateFormat dateFormat = new SimpleDateFormat(DATE_PATTERN);
return Json.createObjectBuilder()
.add("_class", MESSAGE_CLASS_NAME)
.add("message", Json.createObjectBuilder()
.add("sender", object.getSender())
.add("message", object.getMessage())
.add("sendDate", dateFormat.format(object.getSendDate())))
.build()
.toString();
}
}
После того как класс-энкодер написан, его необходимо указать в свойстве encoders аннотации @ServerEndpoint у нашего эндпоинта. Если он не будет там указан, то эндпоинт не будет знать, каким образом нужно преобразовать объект в строку, и выкинет ошибку. Так как посредством одного эндпоинта можно отправлять объекты разных классов, нужно написать необходимое количество классов-энкодеров и указать их в эндпоинте.
@ServerEndpoint(
value = "/chat",
encoders = {
ChatMessageEncoder.class
}
)
public class EncodedSandboxEndpoint {
//
}
Получение объектов
В случае с получением объектов через веб-сокеты алгоритм аналогичен: пишем класс-декодер, реализующий один из четырёх субинтерфейсов Decoder, указываем его в свойстве decoders аннотации @ServerEndpoint, и вместо строки в методе @OnMessage принимаем объект нужного класса. Единственное серьёзное отличие - декодер используется только один, т.е. эндпоинт может получать сообщения только одного класса, не смотря на то, что в decoders можно указать любое количество классов-декодеров. Если нужна возможность принимать объекты разных классов всё же нужна, то решается данная проблема использованием абстрактного класса, либо интерфейса и написанием класса-декодера для них. В классе-декодере обязательны для реализации четыре метода: init, destroy, decode, преобразующий строку в объект нужного типа, и willDecode, определяющий, может ли данная строка быть преобразована при помощи данного декодера.
public class MessageTextDecoder implements Decoder.Text<Message> {
public static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
@Override
public Message decode(String s) throws DecodeException {
System.out.println(s);
Message message;
try (JsonReader messageReader = Json.createReader(new StringReader(s))) {
JsonObject messageObject = messageReader.readObject();
switch (messageObject.getString("_class")) {
case ChatMessageEncoder.MESSAGE_CLASS_NAME:
message = decodeChatMessage(messageObject.getJsonObject("message"));
break;
case PrivateChatMessageEncoder.MESSAGE_CLASS_NAME:
message = decodeUserChatMessage(messageObject.getJsonObject("message"));
break;
default:
throw new IllegalArgumentException("Unknown message type: " + messageReader.readObject().getString("class"));
}
}
return message;
}
private ChatMessage decodeChatMessage(JsonObject messageObject) {
ChatMessage chatMessage;
DateFormat dateFormat = new SimpleDateFormat(DATE_PATTERN);
chatMessage = new ChatMessage();
chatMessage.setSender(messageObject.getString("sender"));
chatMessage.setMessage(messageObject.getString("message"));
try {
chatMessage.setSendDate(dateFormat.parse(messageObject.getString("sendDate")));
} catch (ParseException exception) {
exception.printStackTrace();
}
return chatMessage;
}
private PrivateChatMessage decodeUserChatMessage(JsonObject messageObject) {
PrivateChatMessage chatMessage;
DateFormat dateFormat = new SimpleDateFormat(DATE_PATTERN);
chatMessage = new PrivateChatMessage();
chatMessage.setSender(messageObject.getString("sender"));
chatMessage.setReceiver(messageObject.getString("receiver"));
chatMessage.setMessage(messageObject.getString("message"));
try {
chatMessage.setSendDate(dateFormat.parse(messageObject.getString("sendDate")));
} catch (ParseException exception) {
exception.printStackTrace();
}
return chatMessage;
}
@Override
public boolean willDecode(String s) {
if (s != null && !s.isEmpty()) {
try (JsonReader messageReader = Json.createReader(new StringReader(s))) {
String className = messageReader.readObject().getString("_class");
if (ChatMessageEncoder.MESSAGE_CLASS_NAME.equals(className) || PrivateChatMessageEncoder.MESSAGE_CLASS_NAME.equals(className)) {
return true;
}
}
}
return false;
}
@Override
public void init(EndpointConfig config) {}
@Override
public void destroy() {}
}
В моих примерах корневой JSON-объект несёт информацию о классе передаваемого объекта в поле _class, а так же сам объект в поле message. Таким образом достаточно просто разбирать типы передаваемых объектов как на стороне сервера, так и на стороне клиента.
Использование TextStream вместо Text
При использовании TextStream вместо Text различия в коде будут минимальны. В классе-энкодере метод encode вторым аргументом принимает объект Writer и ничего не возвращает. Ответ отправляется при помощи методов объекта Writer.
public class PrivateChatMessageEncoder implements Encoder.TextStream<PrivateChatMessage> {
public static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static final String MESSAGE_CLASS_NAME = "org.acruxsource.sandbox.websocket.domain.PrivateChatMessage";
@Override
public void init(EndpointConfig config) {}
@Override
public void destroy() {}
@Override
public void encode(PrivateChatMessage object, Writer writer) throws EncodeException, IOException {
DateFormat dateFormat = new SimpleDateFormat(DATE_PATTERN);
String jsonString = Json.createObjectBuilder()
.add("_class", MESSAGE_CLASS_NAME)
.add("message", Json.createObjectBuilder()
.add("sender", object.getSender())
.add("receiver", object.getReceiver())
.add("message", object.getMessage())
.add("sendDate", dateFormat.format(object.getSendDate())))
.build()
.toString();
writer.write(jsonString);
}
}
В классе-декодере метод decode принимает в качестве единственного аргумента не JSON-строку, а объект Reader, из которого нужно вычитать передаваемую строку. Плюс к этому у Decoder.TextStream нет метода willDecode, так как в момент вызова метода OnMessage сообщение передано не полностью.
public class MessageTextStreamDecoder implements Decoder.TextStream<Message> {
public static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
@Override
public Message decode(Reader r) throws DecodeException {
Message message;
try (JsonReader messageReader = Json.createReader(r)) {
JsonObject messageObject = messageReader.readObject();
switch (messageObject.getString("_class")) {
case ChatMessageEncoder.MESSAGE_CLASS_NAME:
message = decodeChatMessage(messageObject.getJsonObject("message"));
break;
case PrivateChatMessageEncoder.MESSAGE_CLASS_NAME:
message = decodeUserChatMessage(messageObject.getJsonObject("message"));
break;
default:
throw new IllegalArgumentException("Unknown message type: " + messageReader.readObject().getString("class"));
}
}
return message;
}
// код опущен
}
Комментариев нет:
Отправить комментарий