RestTemplate调用时如何处理produces中的多种响应媒体类型
我通过rest template调用某API,该API会根据id返回图片byte[],当出现HTTP NOT FOUND(对应id不存在)时,返回JSON格式的错误对象。因特殊原因未使用全局异常处理器,直接在当前端点处理异常。成功场景下可正常获取图片,但当API返回JSON错误响应时,会抛出如下异常:
org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class org.json.simple.JSONObject] with preset Content-Type 'null']
以下是使用rest template调用API的代码:
@GetMapping(value="api/image/{id}" , produces = { MediaType.IMAGE_JPEG_VALUE, MediaType.APPLICATION_JSON_VALUE }) public @ResponseBody ResponseEntity<Object> api_image_customer(@PathVariable(name = "id")String id){ String accessToken = getCrmAccessToken(); RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE); headers.add("Authorization", "Bearer "+accessToken); ResponseEntity<byte[]> response = null; HttpEntity formEntity = new HttpEntity(null, headers); try { response = restTemplate.exchange("http://172.18.5.19:8082/api/image/"+id, HttpMethod.GET, formEntity, byte[].class); if(response!=null && response.getStatusCode().is2xxSuccessful()) { byte[] imageBytes = response.getBody(); return new ResponseEntity<>(imageBytes, HttpStatus.OK); } else if(response!=null && !response.getStatusCode().is2xxSuccessful()){ return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } else { return new ResponseEntity<>("ERROR", HttpStatus.INTERNAL_SERVER_ERROR); } } catch(HttpClientErrorException ex) { String msg = ex.getResponseBodyAsString(); System.out.println(msg+" !!!!"); // this is getting printed as json string. JSONParser parser =new JSONParser(); JSONObject obj = null; try { obj = (JSONObject)parser.parse(msg); }catch(Exception ex_) { ex_.printStackTrace(); return new ResponseEntity<>("error converting json", HttpStatus.INTERNAL_SERVER_ERROR); } return new ResponseEntity<>(obj, HttpStatus.INTERNAL_SERVER_ERROR); } }
我原本以为@GetMapping的produces属性配置多种MediaType即可处理图片和JSON两种响应,但并未生效。请问是否有办法在此处管理两种响应类型,比如动态修改响应的content-type?
问题根源在于返回JSON错误时未指定正确的Content-Type,且Spring默认消息转换器对org.json.simple.JSONObject支持不足。可以通过以下方式修复:
1. 动态设置响应Content-Type
在返回不同类型结果时,手动为响应头指定对应的媒体类型:
- 返回图片时,设置
MediaType.IMAGE_JPEG - 返回JSON错误时,设置
MediaType.APPLICATION_JSON
修改核心返回逻辑:
// 成功返回图片 if(response!=null && response.getStatusCode().is2xxSuccessful()) { byte[] imageBytes = response.getBody(); HttpHeaders imageHeaders = new HttpHeaders(); imageHeaders.setContentType(MediaType.IMAGE_JPEG); return new ResponseEntity<>(imageBytes, imageHeaders, HttpStatus.OK); }
2. 替换JSONObject为Spring兼容类型
避免使用org.json.simple.JSONObject,改用Spring默认支持的Map或自定义错误DTO类,减少序列化异常:
catch(HttpClientErrorException ex) { String msg = ex.getResponseBodyAsString(); HttpHeaders jsonHeaders = new HttpHeaders(); jsonHeaders.setContentType(MediaType.APPLICATION_JSON); try { // 用Jackson将JSON字符串转为Map ObjectMapper objectMapper = new ObjectMapper(); Map<String, Object> errorMap = objectMapper.readValue(msg, Map.class); return new ResponseEntity<>(errorMap, jsonHeaders, HttpStatus.INTERNAL_SERVER_ERROR); } catch (Exception ex_) { ex_.printStackTrace(); // 转换失败直接返回原始JSON字符串 return new ResponseEntity<>(msg, jsonHeaders, HttpStatus.INTERNAL_SERVER_ERROR); } }
3. 修复非2xx响应的处理逻辑
原代码直接返回response对象会导致序列化问题,需解析响应体后再返回:
else if(response!=null && !response.getStatusCode().is2xxSuccessful()){ String errorBody = new String(response.getBody(), StandardCharsets.UTF_8); HttpHeaders jsonHeaders = new HttpHeaders(); jsonHeaders.setContentType(MediaType.APPLICATION_JSON); return new ResponseEntity<>(errorBody, jsonHeaders, HttpStatus.INTERNAL_SERVER_ERROR); }
最终核心代码片段
@GetMapping(value="api/image/{id}" , produces = { MediaType.IMAGE_JPEG_VALUE, MediaType.APPLICATION_JSON_VALUE }) public @ResponseBody ResponseEntity<Object> api_image_customer(@PathVariable(name = "id")String id){ String accessToken = getCrmAccessToken(); RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE); headers.add("Authorization", "Bearer "+accessToken); ResponseEntity<byte[]> response = null; HttpEntity formEntity = new HttpEntity(null, headers); try { response = restTemplate.exchange("http://172.18.5.19:8082/api/image/"+id, HttpMethod.GET, formEntity, byte[].class); if(response!=null && response.getStatusCode().is2xxSuccessful()) { byte[] imageBytes = response.getBody(); HttpHeaders imageHeaders = new HttpHeaders(); imageHeaders.setContentType(MediaType.IMAGE_JPEG); return new ResponseEntity<>(imageBytes, imageHeaders, HttpStatus.OK); } else if(response!=null && !response.getStatusCode().is2xxSuccessful()){ String errorBody = new String(response.getBody(), StandardCharsets.UTF_8); HttpHeaders jsonHeaders = new HttpHeaders(); jsonHeaders.setContentType(MediaType.APPLICATION_JSON); return new ResponseEntity<>(errorBody, jsonHeaders, HttpStatus.INTERNAL_SERVER_ERROR); } else { HttpHeaders jsonHeaders = new HttpHeaders(); jsonHeaders.setContentType(MediaType.APPLICATION_JSON); return new ResponseEntity<>("{\"msg\":\"ERROR\"}", jsonHeaders, HttpStatus.INTERNAL_SERVER_ERROR); } } catch(HttpClientErrorException ex) { String msg = ex.getResponseBodyAsString(); HttpHeaders jsonHeaders = new HttpHeaders(); jsonHeaders.setContentType(MediaType.APPLICATION_JSON); try { ObjectMapper objectMapper = new ObjectMapper(); Map<String, Object> errorMap = objectMapper.readValue(msg, Map.class); return new ResponseEntity<>(errorMap, jsonHeaders, HttpStatus.INTERNAL_SERVER_ERROR); } catch (Exception ex_) { ex_.printStackTrace(); return new ResponseEntity<>(msg, jsonHeaders, HttpStatus.INTERNAL_SERVER_ERROR); } } }
通过上述修改,即可根据响应场景动态设置正确的Content-Type,同时解决消息转换器的异常问题。
内容的提问来源于stack exchange,提问作者JPG

