В предыдущем своём посте я коротко рассказал про реализацию веб-сокетов в 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; } // код опущен }
Комментариев нет:
Отправить комментарий