【小家Spring】Spring MVC容器的web九大组件之---HandlerAdapter源码详解---HttpMessageConverter 消息转换器详解(下)

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: 【小家Spring】Spring MVC容器的web九大组件之---HandlerAdapter源码详解---HttpMessageConverter 消息转换器详解(下)

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模拟访问了,就能看到如下效果


image.png


这样就大功告成了,我们自定义的消息处理器,只处理我们我们指定的MediaType、指定的Class类型,可以帮助我们实现某些个性化逻辑


Spring MVC默认注册哪些HttpMessageConverter?



说明:此处情况完全以Spring MVC版本讲解,和Spring Boot无关。


Spring 版本号为:5.1.6.RELEASE


不开启该注解:@EnableWebMvc


image.png

开启该注解:@EnableWebMvc


image.png


可以看到@EnableWebMvc注解的“威力”还是蛮大的,一下子让Spring MVC变强不少,所以一般情况下,我是建议开启它的。


当然如果是在Spring Boot环境下使用Spring MVC,到时候会再具体问题具体分析~~~

在纯Spring环境下,我是无理由建议标注@EnableWebMvc上此注解的

而且从上面可以看出,若我们classpath下有Jackson的包,那装配的就是MappingJackson2HttpMessageConverter,若没有jackson包有gson包,那装配的就是gson转换器。

小细节


  1. 如果一个Controller类里面所有方法的返回值都需要经过消息转换器,那么可以在类上面加上@ResponseBody注解或者将@Controller注解修改为@RestController注解,这样做就相当于在每个方法都加上了@ResponseBody注解了(言外之意别的方式都是不会经历消息转换器的)
  2. @ResponseBody和@RequestBody都可以处理Map类型的对象。如果不确定参数的具体字段,可以用Map接收。@ReqeustBody同样适用。(List也是木有问题的)
  3. 方法上的和类上的@ResponseBody都可以被继承
  4. 默认的xml转换器Jaxb2RootElementHttpMessageConverter需要类上有@XmlRootElement注解才能被转换(虽然很少使用但此处还是指出)

 

@Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return (AnnotationUtils.findAnnotation(clazz, XmlRootElement.class) != null && canWrite(mediaType));
    }
  1. 返回值类型可声明为基类的类型,不影响转换(比如我们返回值是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对象之间的转换的,具有非常重要的意义


相关文章
|
30天前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
1月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
1月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
54 2
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
115 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
1月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
65 0
|
2月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
1月前
|
Java 应用服务中间件 Apache
浅谈Tomcat和其他WEB容器的区别
Tomcat是一款轻量级的免费开源Web应用服务器,常用于中小型系统及并发访问量适中的场景,尤其适合开发和调试JSP程序。它不仅能处理HTML页面,还充当Servlet和JSP容器。相比之下,物理服务器是指具备处理器、硬盘等硬件设施的服务器,如云服务器,其设计目标是在处理能力、稳定性和安全性等方面提供高标准服务。简言之,Tomcat专注于运行Java应用,而物理服务器则提供基础计算资源。
|
7天前
|
Kubernetes Cloud Native Docker
云原生时代的容器化实践:Docker和Kubernetes入门
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性和效率的关键。本篇文章将引导读者了解如何利用Docker进行容器化打包及部署,以及Kubernetes集群管理的基础操作,帮助初学者快速入门云原生的世界。通过实际案例分析,我们将深入探讨这些技术在现代IT架构中的应用与影响。
29 2
|
17天前
|
Kubernetes 监控 开发者
掌握容器化:Docker与Kubernetes的最佳实践
【10月更文挑战第26天】本文深入探讨了Docker和Kubernetes的最佳实践,涵盖Dockerfile优化、数据卷管理、网络配置、Pod设计、服务发现与负载均衡、声明式更新等内容。同时介绍了容器化现有应用、自动化部署、监控与日志等开发技巧,以及Docker Compose和Helm等实用工具。旨在帮助开发者提高开发效率和系统稳定性,构建现代、高效、可扩展的应用。
|
13天前
|
关系型数据库 MySQL API