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>