FastJsonHttpMessageConverter
它和Gson和fastjson类似,只不过它内部引擎用的是Ali的FastJson库
// Fastjson for Spring MVC Converter. Compatible Spring MVC version 3.2+ // @since 1.2.10 public class FastJsonHttpMessageConverter extends AbstractHttpMessageConverter<Object> implements GenericHttpMessageConverter<Object> { public FastJsonHttpMessageConverter() { super(MediaType.ALL); } // 永远返回true,表示它想支持所有的类型,所有的MediaType,现在这算一个小Bug @Override protected boolean supports(Class<?> clazz) { return true; } // 它竟然对泛型Type都没有任何的实现,这也是一个小bug // 包括读写的时候 对泛型类型都没有做很好的处理~~~ public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) { return super.canRead(contextClass, mediaType); } public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) { return super.canWrite(clazz, mediaType); } // 这是处理读的方法,主要依赖于JSON.parseObject这个方法解析成一个object private Object readType(Type type, HttpInputMessage inputMessage) { try { InputStream in = inputMessage.getBody(); return JSON.parseObject(in, fastJsonConfig.getCharset(), type, fastJsonConfig.getParserConfig(), fastJsonConfig.getParseProcess(), JSON.DEFAULT_PARSER_FEATURE, fastJsonConfig.getFeatures()); } catch (JSONException ex) { throw new HttpMessageNotReadableException("JSON parse error: " + ex.getMessage(), ex); } catch (IOException ex) { throw new HttpMessageNotReadableException("I/O error while reading input message", ex); } } }
总体来说,如果你是FastJson的死忠粉,你可以替换掉默认的Jackson的实现方式。但是由于FastJson在效率在对标Jackson并没有多少优势,所以绝大多数情况下,我并不建议修改Spring MVC处理json的默认行为
ResourceRegionHttpMessageConverter
和org.springframework.core.io.support.ResourceRegion有关,它只能写为一个ResourceRegion或者一个它的List
只能写不能读,读方法都会抛异常~
// 这个类很简单,就是对Resource的一个包装 所以它和`application/octet-stream`也是有关的 // @since 4.3 public class ResourceRegion { private final Resource resource; private final long position; private final long count; ... }
若你报错说ResourceRegionHttpMessageConverter类找不到,请检查你的Spring版本。因此此类@since 4.3
自定义消息转换器PropertiesHttpMessageConverter处理Properties类型数据
自定义的主要目的是加深对消息转换器的理解。此处我们仍然是通过继承AbstractHttpMessageConverter方式来扩展:
public class PropertiesHttpMessageConverter extends AbstractHttpMessageConverter<User> { // 用于仅仅只处理我自己自定义的指定的MediaType private static final MediaType DEFAULT_MEDIATYPE = MediaType.valueOf("application/properties"); public PropertiesHttpMessageConverter() { super(DEFAULT_MEDIATYPE); setDefaultCharset(StandardCharsets.UTF_8); } // 要求入参、返回值必须是User类型我才处理 @Override protected boolean supports(Class<?> clazz) { return clazz.isAssignableFrom(User.class); } @Override protected User readInternal(Class<? extends User> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { InputStream is = inputMessage.getBody(); Properties props = new Properties(); props.load(is); // user的三个属性 String id = props.getProperty("id"); String name = props.getProperty("name"); String age = props.getProperty("age"); return new User(Integer.valueOf(id), name, Integer.valueOf(age)); } @Override protected void writeInternal(User user, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { OutputStream os = outputMessage.getBody(); Properties properties = new Properties(); // 属性判空此处我就不做了~~~ properties.setProperty("id", user.getId().toString()); properties.setProperty("name", user.getName()); properties.setProperty("age", user.getAge().toString()); properties.store(os, "user comments"); } }
其实发现,处理代码并不多。需要注意的是:此处我们只处理我们自定义的
:application/properties-user
这一种MediaType
即可,职责范围放到最小。
接下来就是要注册进Spring MVC
里:
@Configuration @EnableWebMvc public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { // 因为此转换器职责已经足够单一,所以放在首位是木有问题的~ converters.add(0, new PropertiesHttpMessageConverter()); // 若放在末尾,将可能不会生效~~~~(比如如果Fastjson转换器 处理所有的类型的话,所以放在首位最为保险) //converters.add(0, new PropertiesHttpMessageConverter()); } }
这里需要注意的是,为了避免意外,一定要注意自定义消息转换器的注册顺序问题。至于为什么,在参考阅读的博文里已经详细解释了~~~
编写Handler处理器如下:
@ResponseBody @RequestMapping(value = "/test/properties", method = RequestMethod.POST) public User upload(@RequestBody User user) { System.out.println(user); return user; }
下面可以用postman模拟访问了,就能看到如下效果
这样就大功告成了,我们自定义的消息处理器,只处理我们我们指定的MediaType、指定的Class类型,可以帮助我们实现某些个性化逻辑
Spring MVC默认注册哪些HttpMessageConverter?
说明:此处情况完全以Spring MVC版本讲解,和Spring Boot无关。
Spring 版本号为:5.1.6.RELEASE
不开启该注解:@EnableWebMvc
开启该注解:@EnableWebMvc
可以看到@EnableWebMvc注解的“威力”还是蛮大的,一下子让Spring MVC变强不少,所以一般情况下,我是建议开启它的。
当然如果是在Spring Boot环境下使用Spring MVC,到时候会再具体问题具体分析~~~
在纯Spring环境下,我是无理由建议标注@EnableWebMvc上此注解的
而且从上面可以看出,若我们classpath下有Jackson的包,那装配的就是MappingJackson2HttpMessageConverter,若没有jackson包有gson包,那装配的就是gson转换器。
小细节
- 如果一个Controller类里面所有方法的返回值都需要经过消息转换器,那么可以在类上面加上@ResponseBody注解或者将@Controller注解修改为@RestController注解,这样做就相当于在每个方法都加上了@ResponseBody注解了(言外之意别的方式都是不会经历消息转换器的)
- @ResponseBody和@RequestBody都可以处理Map类型的对象。如果不确定参数的具体字段,可以用Map接收。@ReqeustBody同样适用。(List也是木有问题的)
- 方法上的和类上的@ResponseBody都可以被继承
- 默认的xml转换器Jaxb2RootElementHttpMessageConverter需要类上有@XmlRootElement注解才能被转换(虽然很少使用但此处还是指出)
@Override public boolean canWrite(Class<?> clazz, MediaType mediaType) { return (AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) != null && canWrite(mediaType)); }
- 返回值类型可声明为基类的类型,不影响转换(比如我们返回值是Object都是木有关系的)。但参数的类型必需为特定的类型(最好不要用接口类型,当然有的时候也是可以的比如Map/List/Resource等等)。这是显而易见的
最后
请求和响应都有对应的body,而这个body就是需要关注的主要数据。
请求体与请求的查询参数或者表单参数是不同的:
请求体的表述一般就是一段字符串(当然也可能是二进制),而查询参数可以看作url的一部分,这两个是位于请求报文的不同地方
表单参数可以按照一定格式放在请求体中,也可以放在url上作为查询参数。
响应体则是浏览器渲染页面的依据,对于一个普通html页面得响应,响应体就是这个html页面的源代码。
请求体和响应体都是需要配合Content-Type头部使用的,这个头部主要用于说明body中得字符串是什么格式的,比如:text,json,xml等。
- 对于请求报文,只有通过此头部,服务器才能知道怎么解析请求体中的字符串
- 对于响应报文,浏览器通过此头部才知道应该怎么渲染响应结果,是直接打印字符串还是根据代码渲染为一个网页
还有一个与body有关的头部是Accept,这个头部标识了客户端期望得到什么格式的响应体。服务器可根据此字段选择合适的结果表述。
对于HttpServletRequest和HttpServletResponse,可以分别调用getInputStream和getOutputStream来直接获取body,但是获取到的仅仅只是一段字符串。
而对于Java来说,处理一个对象肯定比处理一个字符串要方便得多,也好理解得多。
所以根据Content-Type头部,将body字符串转换为java对象是常有的事。反过来,根据Accept头部,将java对象转换客户端期望格式的字符串也是必不可少的工作。
因此本文讲述的消息转换器HttpMessageConverter就是专门来实现请求体/响应体到Java对象之间的转换的,具有非常重要的意义