SpringMVC源码分析(数据转换和格式化)

@author:QYX

本系列共30章,争取大家读完后,都能自己手写实现一下属于自己的SpringMVC框架

因为写于很久之前,所以版本可能较老,请见谅

 

 

当一个请求到达DispatcherServlet的时候,需要找到对应的HandlerMapping,然后根据HandlerMapping去找对应的HandlerAdapter执行处理器,处理器在要调用控制器之前,需要先获取HTTP发送过来的信息,然后将其转变为控制器的各种不同类型参数,这就是各类注解能够得到丰富类型参数的原因。它首先用HTTP的消息转换器(HTTPMessageConverter)对消息转换,但是这是一个比较原始的转换,他是类型和文件类型比较简易的转换,他还需要进一步转换才能转换为POJO或者其他丰富的参数类型。

当处理器处理完了这些参数的转换,它就会进行验证,当控制器完成了对应的逻辑,返回结果后,处理器如果可以找到对应处理结果类型的HTTPMessageConverter的实现类,它就会调用对应的HTTPMessageConverter的实现类方法。

对于SpringMVC,在XML配置了<mvc:annotation-driven>,或者Java配置的注解上加入@EnableWebMvc的时候,SpringIOC容器会自定义生成一个关于转换器和格式化器的类实例-------FormattingConverterFactoryBean,这样就可以从Spring IoC容器中获取这个对象了,它的产品主要就是DefaultFormattingConversionService类对象。

它的顶层接口-------ConversionService接口,它还实现了转换器的注册器(ConverterRegister)和格式化器(FormatterRegitry)两个接口,也就是说可以在它那注册转换器或者格式化器了。

在运行控制器之间,他就会把这些转换器把HTTP的数据转换为对应的类型,用以填充控制器的参数,这就是为什么在控制器保持一定规则下就能够得到参数的原因。

在Java类型转换之前,在SpringMVC中,为了应对了HTTP请求,它还定义了HttpMessageConverter,它是一个总体的接口,通过它可以读入HTTP的请求内容, 也就是说,在读取HTTP请求的参数和内容的时候会先用HttpMessageConverter读出,做一次简单转换为Java类型,主要是字符串(String),然后就可以使用各类转换器进行转换了,在逻辑业务处理完成后,还可以通过它把数据转换为响应给用户的内容

对于转换器而言,Spring分为两大类,一种是Converter接口所定义的,另外一种GenericConverter,它们都可以使用注册机注册,它们都是来自于Spring Core项目,而非Spring MVC项目,他的作用范围是Java内部各种类型之间的转换

HttpMessageConverter实例

 

 

 

HttpMessageConverter是定义从HTTP接口请求信息和应答给用户的

public interface HttpMessageConverter<T> {  
  
    /** 
     * Indicates whether the given class can be read by this converter. 
     * @param clazz the class to test for readability 
     * @param mediaType the media type to read, can be {@code null} if not specified. 
     * Typically the value of a {@code Content-Type} header. 
     * @return {@code true} if readable; {@code false} otherwise 
     */  
    boolean canRead(Class<?> clazz, MediaType mediaType);  
  
    /** 
     * Indicates whether the given class can be written by this converter. 
     * @param clazz the class to test for writability 
     * @param mediaType the media type to write, can be {@code null} if not specified. 
     * Typically the value of an {@code Accept} header. 
     * @return {@code true} if writable; {@code false} otherwise 
     */  
    boolean canWrite(Class<?> clazz, MediaType mediaType);  
  
    /** 
     * Return the list of {@link MediaType} objects supported by this converter. 
     * @return the list of supported media types 
     */  
    List<MediaType> getSupportedMediaTypes();  
  
    /** 
     * Read an object of the given type form the given input message, and returns it. 
     * @param clazz the type of object to return. This type must have previously been passed to the 
     * {@link #canRead canRead} method of this interface, which must have returned {@code true}. 
     * @param inputMessage the HTTP input message to read from 
     * @return the converted object 
     * @throws IOException in case of I/O errors 
     * @throws HttpMessageNotReadableException in case of conversion errors 
     */  
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)  
            throws IOException, HttpMessageNotReadableException;  
  
    /** 
     * Write an given object to the given output message. 
     * @param t the object to write to the output message. The type of this object must have previously been 
     * passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}. 
     * @param contentType the content type to use when writing. May be {@code null} to indicate that the 
     * default content type of the converter must be used. If not {@code null}, this media type must have 
     * previously been passed to the {@link #canWrite canWrite} method of this interface, which must have 
     * returned {@code true}. 
     * @param outputMessage the message to write to 
     * @throws IOException in case of I/O errors 
     * @throws HttpMessageNotWritableException in case of conversion errors 
     */  
    void write(T t, MediaType contentType, HttpOutputMessage outputMessage)  
            throws IOException, HttpMessageNotWritableException;  
  
}  

源码分析:

canRead:判断类型是否可读,clazz是类别,而对于mediaType是Http类型

canWrite:判断类型是否可写,clazz是类别,而对于mediaType是Http类型

getSupportedMediaTypes:对于mediaType是HTTP的类型

read:读取数据类型,进行转换,clazz是类,而inputMessage是HTTP请求消息

write:消息写,contentType是HTTP类型,outputMessage是HTTP的应答消息

拿我们经常使用的MappingJacksonHttpMessageConverter举例,他的配置如下:

MappingJacksonHttpMessageConverter 用于将对象转换为 JSON,反之亦然。此内置转换程序使用 Jackson 的 ObjectMapper 将 JSON 映射到 JavaBean,因此您必须将下列 Jackson JAR 文件添加到类路径。 
org.codehaus.jackson.jar 
org.codehaus.jackson.mapper.jar

他的测试调用很简单,可以使用@ResponseBody返回Json格式的数据

@ResponseBody 注释用于将返回对象(Employee 或 EmployeeList)变为响应的正文内容,将使用 MappingJacksonHttpMessageConverter 将其映射到 JSON。

使用 HttpMessageConverter 和 @ResponseBody,您可以实现多个具象,而无需包含 Spring 的视图技术 — 这是使用 ContentNegotiatingViewResolver 所不具有的一个优势。

Spring MVC的@ResponseBody 的作用是把返回值直接写到HTTP response body里。 使用AnnotationMethodHandlerAdapter的handleResponseBody方法, AnnotationMethodHandlerAdapter使用request header中”Accept”的值和messageConverter支持的MediaType进行匹配,然后会用”Accept”的第一个值写入 response的”Content-Type”。

AnnotationMethodHandlerAdapter将会初始化7个转换器,可以通过调用AnnotationMethodHandlerAdapter的getMessageConverts()方法来获取转换器的一个集合

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
   <property name="messageConverters">
       <list>
           <ref bean="jsonConverter" />
           <ref bean="marshallingConverter" />
           <ref bean="atomConverter" />
       </list>
   </property>
</bean>

<bean id="jsonConverter"   class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
   <property name="supportedMediaTypes" value="application/json" />
</bean>

 

posted @ 2020-02-15 23:20  计算机的探索者  阅读(181)  评论(0编辑  收藏  举报