相似文章有:SpringBoot内置的jackson和lombok的使用、HttpMessageConverter。
首先,新建spring项目:demo4,且添加依赖:Spring Web和lombok。
spring-boot-starter-web>spring-boot-starter-json>jackson jar,依赖关系如下。
java目录下新建包com.example.boot,并在该包下新建类:bean.Person和bean.Pet,如下。
package com.example.boot.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Person {
private String userName;
private Integer age;
private Date birth;
private Pet pet;
}
package com.example.boot.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Pet {
private String name;
private Float weight;
}
com.example.boot下新建控制器类controller.Demo4Controller,如下。
package com.example.boot.controller;
import com.example.boot.bean.Person;
import com.example.boot.bean.Pet;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
@Controller
public class Demo4Controller {
@GetMapping("/person")
@ResponseBody
private Person save(){
Person person = new Person();
person.setAge(22);
person.setUserName("张三");
person.setBirth(new Date());
person.setPet(new Pet("小苹果",8.5f));
return person;
}
}
resources.static下新建静态页面index.html,如下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<a href="/person">测试</a>
</body>
</html>
最后,启动应用,点击测试,查看结果。
也就是,jackson jar+@ResponseBody,将Person类转换成了JSON,最终返回给了响应。
接下来探寻下原理,调试过程经过了以下部分源码。
- org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//...
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//...
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//...
}
- org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//...
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
//...
}
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//...
//参数解析器
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
//返回值处理器
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
//...
invocableMethod.invokeAndHandle(webRequest, mavContainer);
//...
}
返回值处理器有这么15种,如下。
- org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//...
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//...
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
//...
}
- org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//选择合适的返回值处理器
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
//返回值处理器对返回值进行处理
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
selectHandler
,遍历现有的返回值处理器,直到找到能够处理这种类型的返回值的处理器为止;
handleReturnValue
,如果找到合适的返回值处理器,则处理器调用handleReturnValue方法处理返回值。
- org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#selectHandler
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
进入HandlerMethodReturnValueHandler,该接口类有两个方法,如下。
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
支持处理Person类型的返回值处理器是RequestResponseBodyMethodProcessor。
- org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
- org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
//...
MediaType selectedMediaType = null;
acceptableTypes = getAcceptableMediaTypes(request);
//...
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
//...
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
//...
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
//...
}
}
else {
//...
}
return;
}
}
}
}
浏览器默认通过请求头(Accept)告诉服务器,自己能够接受什么样的内容类型。
服务端通过调用getAcceptableMediaTypes方法得知浏览器能够的内容类型,见acceptableTypes
,有8种,如下。
服务器根据自身处理能力,决定自己能够生产什么样的内容类型。
服务器端通过调用getProducibleMediaTypes方法,确定自己能够生产的内容类型,见producibleTypes
,有4种,如下。
现在,浏览器说,我能接受这8种内容类型;服务器说,我能生产这4种数据类型。二者协商下,嵌套for循环,得到mediaTypesToUse
,如下。
以上4种类型,服务器能生产,浏览器也能接受。
接下来就是选择消息转换器,HttpMessageConverter。
SpringMVC默认支持10种消息转换器,如下。
进入HttpMessageConverter,其定义如下,包含5个方法。
public interface HttpMessageConverter<T> {
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
return (canRead(clazz, null) || canWrite(clazz, null) ?
getSupportedMediaTypes() : Collections.emptyList());
}
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
遍历当前系统现有的所有的消息转化器,找到能够将Person类转换为JSON的转换器:MappingJackson2HttpMessageConverter
。
- org.springframework.http.converter.AbstractGenericHttpMessageConverter#write
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
//...
writeInternal(t, type, outputMessage);
outputMessage.getBody().flush();
}
- org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#writeInternal
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
//...
ObjectMapper objectMapper = selectObjectMapper(clazz, contentType);
//...
ObjectWriter objectWriter = (serializationView != null ?
objectMapper.writerWithView(serializationView) : objectMapper.writer());
//...
objectWriter.writeValue(generator, value);
writeSuffix(generator, object);
generator.flush();
//...
}
最后的最后,我们来了解下jackson jar、消息转换器、返回xml或json响应之间的关系。
先说结论:因为有jackson-databind、jackson-core jar包,所以消息转换器(能够将POJO转换为JSON或XML的消息转换器)才会被加入SpringMVC底层;SpringMVC底层有了这些消息转换器,才能将POJO转换为JSON或XML,并作为响应返回给浏览器。
现在来看源码,最好debug一下。
启动应用程序,自动配置类WebMvcAutoConfiguration会根据当前系统配置将现有的消息转换器加载到SpringMVC底层。其中,
只有当前系统包含com.fasterxml.jackson.databind.ObjectMapper
和com.fasterxml.jackson.core.JsonGenerator
,变量jackson2Present
才为true
;jackson2Present
为true
时,MappingJackson2HttpMessageConverter(能把POJO转换为JSON的消息转换器)才会加入到SpringMVC底层。
只有当前系统包含com.fasterxml.jackson.dataformat.xml.XmlMapper
,变量jackson2XmlPresent
才为true
;jackson2XmlPresent
为true
时,MappingJackson2XmlHttpMessageConverter(能把POJO转换为XML的消息转换器)才会加入到SpringMVC底层。
- WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#configureMessageConverters
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.messageConvertersProvider
.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
}
- HttpMessageConverters#getConverters和HttpMessageConverters#getDefaultConverters
public HttpMessageConverters(boolean addDefaultConverters, Collection<HttpMessageConverter<?>> converters) {
List<HttpMessageConverter<?>> combined = getCombinedConverters(converters,
addDefaultConverters ? getDefaultConverters() : Collections.emptyList());
//...
}
public List<HttpMessageConverter<?>> getConverters() {
return this.converters;
}
private List<HttpMessageConverter<?>> getDefaultConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport",
null)) {
converters.addAll(new WebMvcConfigurationSupport() {
public List<HttpMessageConverter<?>> defaultMessageConverters() {
return super.getMessageConverters();
}
}.defaultMessageConverters());
}
//...
}
- WebMvcConfigurationSupport#getMessageConverters
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<>();
configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters);
}
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
- WebMvcConfigurationSupport#addDefaultHttpMessageConverters
//WebMvcConfigurationSupport
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
private static final boolean jackson2Present;
private static final boolean jackson2XmlPresent;
static {
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
if (jackson2XmlPresent) {
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}
if (jackson2Present) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}