如何解决以websocket消息的形式广播HATEOAS对象,其内容类型为application / json
我需要在extends RepresentationModel
上同时作为Websocket的广播消息来呈现下面的类(在RESTController
的其他几个类中)。
@Getter
@JsonPropertyOrder(alphabetic=true)
@Setter
public class MessageWrapper extends RepresentationModel<MessageWrapper> {
private Object message;
private String simpleName;
public MessageWrapper(Object message) {
this.message = message;
this.simpleName = message.getClass().getSimpleName();
}
}
我使用以下代码将其呈现到响应主体:
@RequestMapping(
headers = {
"Content-Type=application/json","X-Requested-With=XMLHttpRequest"
},method = RequestMethod.POST,produces = "application/hal+json",value = "/thread/{threadId}/message")
public HttpEntity<MessageWrapper> post(...) {
MessageWrapper wrapper = ...
HttpEntity<MessageWrapper> entity =
new ResponseEntity<HateoasWrapper>(wrapper,HttpStatus.OK);
return entity;
}
它会产生以下截断的响应:
HTTP/1.1 200
...
Content-Type: application/hal+json
{
"_links": {
"previous": {
"href": "/thread/1044/message/86"
},"self": {
"href": "/thread/1044/message/87"
}
},"message": {
...
},"simpleName":"..."
}
请注意响应的Content-Type
和上方的_links
对象属性。我为此Content-Type
创建了一个JavaScript处理程序,并且_links
属性很关键。现在,稍后某个时间发生在一个单独的事件上,我需要将完全相同的JSON字符串广播到websocket。所以我这样做了:
@Autowired
private SimpMessagingTemplate messagingTemplate;
public void broadcastToUser(StompPrincipal principal,MessageWrapper wrapper) {
SimpMessageHeaderAccessor simpHeaderAccessor =
SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
simpHeaderAccessor.setUser(principal);
simpHeaderAccessor.setLeaveMutable(true);
messagingTemplate.convertAndSendToUser(
principal.getUsername(),"/broadcast",wrapper,simpHeaderAccessor.getMessageHeaders());
}
它会产生以下消息:
a["MESSAGE
destination:/user/broadcast
content-type:application/json
subscription:sub-0
message-id:tluhntae-0
content-length:267
{
"links": [
{
"rel": "previous","href": "/thread/1044/message/86"
},{
"rel": "self","href": "/thread/1044/message/87"
}
],"message": {...},"simpleName": "..."
}
"]
请注意,它具有links
属性而不是_links
。还要注意,它没有像上面那样拥有一个对象值,而是具有一个对象数组作为值。我想对上述RESTController
响应使用相同的javascript回调,但是不能使用,因为links
属性具有不同的结构。我可以在javascript端将其编组,但我宁愿尝试将其配置为按我期望的方式进行渲染。我读到这是因为我将Websocket消息呈现为application/json
而不是application/hal+json
,这就是为什么它使用links
而不是_links
的原因。因此,在广播消息之前,我将消息的Content-Type
设置为HAL:
MimeType applicationJson;
if(RepresentationModel.class.isAssignableFrom(body.getClass())) {
applicationJson = new MimeType("application","hal+json");
} else {
applicationJson = new MimeType("application","json");
}
simpHeaderAccessor.setContentType(applicationJson);
尽管它会引发以下错误:
2020-10-26 23:40:40.799 ERROR 6200 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.messaging.converter.MessageConversionException: Unable to convert payload with type='MessageWrapper',contentType='application/hal+json',converter=[CompositeMessageConverter[converters=[org.springframework.messaging.converter.StringMessageConverter@66389c40,org.springframework.messaging.converter.ByteArrayMessageConverter@724493b5,org.springframework.messaging.converter.MappingJackson2MessageConverter@1228bacc]]]] with root cause
org.springframework.messaging.converter.MessageConversionException: Unable to convert payload with type='MessageWrapper',org.springframework.messaging.converter.MappingJackson2MessageConverter@1228bacc]]]
at org.springframework.messaging.core.AbstractMessageSendingTemplate.doConvert(AbstractMessageSendingTemplate.java:187) ~[spring-messaging-5.3.0-M1.jar:5.3.0-M1]
at org.springframework.messaging.core.AbstractMessageSendingTemplate.convertAndSend(AbstractMessageSendingTemplate.java:150) ~[spring-messaging-5.3.0-M1.jar:5.3.0-M1]
at org.springframework.messaging.simp.SimpMessagingTemplate.convertAndSendToUser(SimpMessagingTemplate.java:230) ~[spring-messaging-5.3.0-M1.jar:5.3.0-M1]
at org.springframework.messaging.simp.SimpMessagingTemplate.convertAndSendToUser(SimpMessagingTemplate.java:211) ~[spring-messaging-5.3.0-M1.jar:5.3.0-M1]
at dev.gideon.chatapp.service.BroadcastService.broadcastToUser(BroadcastService.java:54) ~[classes/:na]
...
我真的需要将websocket消息的Content-Type
设置为HAL才能按照我期望的方式整理。反正有实现吗?我问这个问题是因为除了我自己,我不想做单独的javascript代码来处理带有数组值的links
属性和带有对象值的_links
属性的json对象,我也相信将其与HAL Content-Type
一起广播确实是适当的,这样,除了我自己的客户端以外的其他客户端也将这样对待它,而不仅仅是普通的JSON广播消息。
解决方法
MessageConverter
Content-Type
的网络套接字广播消息没有默认的application/hal+json
。我需要为自定义MIME类型实现自己的MessageConverter
。我创建了一个extends AbstractMessageConverter
的转换器:
public class HalMessageConverter extends AbstractMessageConverter {
public HalMessageConverter() {
MimeType hal = new MimeType("application","hal+json");
this.addSupportedMimeTypes(hal);
}
@Override
protected boolean supports(Class<?> clazz) {
return RepresentationModel.class.isAssignableFrom(clazz);
}
@Override
protected Object convertToInternal(
Object payload,MessageHeaders headers,Object conversionHint) {
TypeConstrainedMappingJackson2HttpMessageConverter converter =
new TypeConstrainedMappingJackson2HttpMessageConverter(RepresentationModel.class);
Module module = new Jackson2HalModule();
LinkRelationProvider provider = new DefaultLinkRelationProvider();
Jackson2HalModule.HalHandlerInstantiator instantiator =
new Jackson2HalModule.HalHandlerInstantiator(
provider,CurieProvider.NONE,MessageResolver.DEFAULTS_ONLY);
ObjectMapper mapper = converter.getObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.registerModule(module);
mapper.setHandlerInstantiator(instantiator);
try {
String payloadJson = mapper.writeValueAsString(payload);
byte[] bytes = payloadJson.getBytes();
return bytes;
} catch(JsonProcessingException exception) {
return null;
}
}
}
我需要创建一个自定义类,因为方法addSupportedMimeTypes
是protected
。 convertToInternalMethod
保存转换逻辑。 Jackson2HalModule
类是我一直以来一直在寻找的类。此类是当发送links
的{{1}}的{{1}}时_links
成为Response
的原因。
创建此自定义Content-Type
后,我将其添加到应用程序的默认转换器中:
application/hal+json
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。