@RequestBody作用是將http請求解析爲對應的對象。例如:
http請求的參數(application/json格式):
{
"accountId": 10,
"adGroupId": "12345678",
"campaignId": "12345678",
"dataType": 0,
"sign": "abcdefg",
"site": "us",
"timeStamp": 1453250,
"userId": 10
}
通過@RequestBody可以解析爲ProductSyncNegativeDto對象(如下代碼所示)
public CustResponse syncNegative(@RequestBody ProductSyncNegativeDto productSyncNegativeDto)
那@RequestBody註解是如何實現http請求報文轉對象的呢?
@ResponseBody作用是將返回的對象轉爲json字符串,例如我們返回一個CustResponse對象,那postman中的結果會是啥?
{
"code": 100,
"msg": "",
"details": [
10,
10,
"us",
"12345678",
"12345678",
0
]
}
我們可以發現,結果是一個json字符串,那@ResponseBody註解到底是如何將對象轉爲json字符串返回的呢?
接下來老師會帶童鞋們一些來揭祕,@RequestBody、@ResponseBody的底層實現原理。
一、概述
@Controller註解
在開始之前,我們先來介紹一下@Controller,做過ssm/ssh項目的同學肯定都接觸過springMVC,那必然會用到@Controller註解。Controller方法被封裝成ServletInvocableHandlerMethod類,並且由invokeAndHandle方法完成請求處理。
HttpMessageConverter
SpringMVC處理請求和響應時,支持多種類型的請求參數和返回類型,而此種功能的實現就需要對HTTP消息體和參數及返回值進行轉換,爲此SpringMVC提供了大量的轉換類,所有轉換類都實現了HttpMessageConverter接口。
public interface HttpMessageConverter<T> {
// 當前轉換器是否能將HTTP報文轉換爲對象類型
boolean canRead(Class<?> clazz, MediaType mediaType);
// 當前轉換器是否能將對象類型轉換爲HTTP報文
boolean canWrite(Class<?> clazz, MediaType mediaType);
// 轉換器能支持的HTTP媒體類型
List<MediaType> getSupportedMediaTypes();
// 轉換HTTP報文爲特定類型
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
// 將特定類型對象轉換爲HTTP報文
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
HttpMessageConverter接口定義了5個方法,用於將HTTP請求報文轉換爲java對象,以及將java對象轉換爲HTTP響應報文。
HandlerMethodArgumentResolver與HandlerMethodReturnValueHandler
對應到SpringMVC的Controller方法,read方法即是讀取HTTP請求轉換爲參數對象,write方法即是將返回值對象轉換爲HTTP響應報文。SpringMVC定義了兩個接口來操作這兩個過程:參數解析器HandlerMethodArgumentResolver和返回值處理器HandlerMethodReturnValueHandler。
// 參數解析器接口
public interface HandlerMethodArgumentResolver {
// 解析器是否支持方法參數
boolean supportsParameter(MethodParameter parameter);
// 解析HTTP報文中對應的方法參數
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
// 返回值處理器接口
public interface HandlerMethodReturnValueHandler {
// 處理器是否支持返回值類型
boolean supportsReturnType(MethodParameter returnType);
// 將返回值解析爲HTTP響應報文
void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
參數解析器和返回值處理器在底層處理時,都是通過HttpMessageConverter進行轉換。流程如下:
二、@RequestBody解析過程
所有的http請求都會進入ServletInvocableHandlerMethod類(繼承InvocableHandlerMethod,所有的參數解析器都會在在這裏面進行初始化)的invokeAndHandle方法中,我們來具體看看invokeAndHandle方法是幹什麼的。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 執行http請求
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
// 返回值處理
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}catch (Exception ex) {
if (logger.isTraceEnabled()){
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
我們可以看到invokeAndHandle方法都會進入invokeForRequest方法中,invokeForRequest方法就是實現@RequestBody註解的功能,將http請求報文解析爲我們設置的對象。我們進入該方法看看,裏面具體做了哪些事情。
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// http報文解析爲對象數組
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
//執行@PostMapping、@GetMapping等接口
return doInvoke(args);
}
我們可以看到invokeForRequest中主要做了兩件事情,一個是通過getMethodArgumentValues方法返回http解析後的對象數組,然後通過doInvoke方法執行接口的具體業務邏輯代碼。
我們接着進入getMethodArgumentValues方法,細看一下@RequestBody的具體解析過程。
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 獲取http請求參數
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
// 遍歷所有參數,挨個解析
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
// 參數解析器解析HTTP報文
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
其中this.resolvers.supportsParameter(parameter)用來判斷請求參數是否合法,this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory)方法最終實現@RequestBody解析操作。我們來看看this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory)中做了什麼。
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 獲取對應的解析器
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException(
"Unsupported parameter type [" + parameter.getParameterType().getName() + "]." +
" supportsParameter should be called first.");
}
// 通過HandlerMethodArgumentResolver 解析器解析http報文
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
getArgumentResolver方法來獲取對應的HandlerMethodArgumentResolver參數解析器,參數解析器最終通過RequestResponseBodyMethodProcessor類來具體執行解析過程,我們接着來看看RequestResponseBodyMethodProcessor中resolveArgument方法又是怎樣的一個處理過程。
不同的resolvers(HandlerMethodArgumentResolver接口)會對應不同的參數解析器,例如public String testDemo(String name),解析器就會變成ServletRequestMethodArgumentResolver,如果是@RequestBody,參數解析器就是RequestResponseBodyMethodProcessor
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
// 通過HttpMessageConverter來解析http報文爲Object對象
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
readWithMessageConverters方法中,HttpMessageConverter(接口對應實現類)的read方法實現了http報文解析,我們來看看最終http參數解析部分的代碼。
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
....
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
// 判斷轉換器是否支持參數類型
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
// read方法執行HTTP報文到參數的轉換
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
....
}
代碼部分省略了,關鍵部分即是遍歷所有的HttpMessageConverter,然後通過canRead方法判斷解析器是否支持,最後執行AbstractJackson2HttpMessageConverter對象(HttpMessageConverter實現類)的read方法完成最後的參數解析。
AbstractJackson2HttpMessageConverter對象的read方法,核心是利用了jackson工具,將http報文的json字符串轉換爲object對象並返回。
三、@ResponseBody返回值序列化過程
執行完doInvoke邏輯代碼之後,通過ServletInvocableHandlerMethod對象的invokeAndHandle方法,利用返回值處理器對返回值進行序列化輸出。
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
returnValueHandlers爲HandlerMethodReturnValueHandlerComposite對象,該對象實現了HandlerMethodReturnValueHandler接口,我們接着來看看handleReturnValue方法的具體實現。
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 選擇合適的HandlerMethodReturnValueHandler返回值處理器
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方法會提供合適的HandlerMethodReturnValueHandler,用來處理返回值。
我們看到的HandlerMethodReturnValueHandler處理器最終也是由RequestResponseBodyMethodProcessor實現的,我們具體來看看handleReturnValue方法。
handler(HandlerMethodReturnValueHandler)接口會根據不同類型選擇不同的返回值處理器,例如頁面跳轉類型的處理器就是ViewNameMethodReturnValueHandler。
@Override
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);
// 調用HttpMessageConverter執行
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
....
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
// 判斷是否支持返回值類型,返回值類型很有可能不同,如String,Map,List等
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 {
// 執行返回值轉換
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
....
}
我們看到最終還是由HttpMessageConverter(AbstractGenericHttpMessageConverter實現類)的write方法來進行對象的序列化輸出。
大家都知道@ResponseBody需要通過io流來讀取,也就HttpMessageConverter最終的write會寫入到io輸出流中,上面的createOutputMessage(webRequest)方法就是創建一個輸出流,我們來具體看看它的實現。
protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
Assert.state(response != null, "No HttpServletResponse");
return new ServletServerHttpResponse(response);
}
public class ServletServerHttpResponse implements ServerHttpResponse {
private final HttpServletResponse servletResponse;
private final HttpHeaders headers;
private boolean headersWritten = false;
private boolean bodyUsed = false;
/**
* Construct a new instance of the ServletServerHttpResponse based on the given {@link HttpServletResponse}.
* @param servletResponse the servlet response
*/
public ServletServerHttpResponse(HttpServletResponse servletResponse) {
Assert.notNull(servletResponse, "HttpServletResponse must not be null");
this.servletResponse = servletResponse;
this.headers = new ServletResponseHttpHeaders();
}
}
public interface ServletResponse {
String getCharacterEncoding();
String getContentType();
ServletOutputStream getOutputStream() throws IOException;
PrintWriter getWriter() throws IOException;
void setCharacterEncoding(String var1);
void setContentLength(int var1);
void setContentLengthLong(long var1);
void setContentType(String var1);
void setBufferSize(int var1);
int getBufferSize();
void flushBuffer() throws IOException;
void resetBuffer();
boolean isCommitted();
void reset();
void setLocale(Locale var1);
Locale getLocale();
}
createOutputMessage方法中創建了ServletServerHttpResponse ,然後通過 ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage)方法寫入到輸出流中。write方法的核心也是通過Jackson工具將對象解析爲json字符串。我們最後來看看write的核心處理方法writeInternal。
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
MediaType contentType = outputMessage.getHeaders().getContentType();
JsonEncoding encoding = getJsonEncoding(contentType);
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
try {
writePrefix(generator, object);
Object value = object;
Class<?> serializationView = null;
FilterProvider filters = null;
JavaType javaType = null;
if (object instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) object;
value = container.getValue();
serializationView = container.getSerializationView();
filters = container.getFilters();
}
if (type != null && TypeUtils.isAssignable(type, value.getClass())) {
javaType = getJavaType(type, null);
}
ObjectWriter objectWriter = (serializationView != null ?
this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && javaType.isContainerType()) {
objectWriter = objectWriter.forType(javaType);
}
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}
objectWriter.writeValue(generator, value);
writeSuffix(generator, object);
generator.flush();
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
}
}
@Override
public OutputStream getBody() throws IOException {
this.bodyUsed = true;
writeHeaders();
return this.servletResponse.getOutputStream();
}
objectWriter.writeValue(generator, value)方法中將value對象通過serialize序列化方法,將對象轉爲json字符串,然後設置到io流中。我們最後看看Jackson最終的序列化是怎麼樣的?
@Override
public final void serialize(Object bean, JsonGenerator gen, SerializerProvider provider)
throws IOException
{
if (_objectIdWriter != null) {
gen.setCurrentValue(bean); // [databind#631]
_serializeWithObjectId(bean, gen, provider, true);
return;
}
// 設置json的開始符號("{")
gen.writeStartObject(bean);
if (_propertyFilterId != null) {
// 循環將對象設置爲json字符串 serializeFieldsFiltered(bean, gen, provider);
} else {
serializeFields(bean, gen, provider);
}
// 設置json的結束符號("}")
gen.writeEndObject();
}
在serialize方法中通過JsonGenerator將要返回的對象轉爲json格式的字符串。
四、springMVC初始化
至此我們就基本走完了一個HTTP請求和響應的過程。現在你可能有個疑惑,SpringMVC我們都是開箱即用,這些參數解析器和返回值處理器在哪裏定義的呢?在覈心的HandlerAdapter實現類RequestMappingHandlerAdapter的初始化方法中定義的。
而在RequestMappingHandlerAdapter構造時,也同時初始化了衆多的HttpMessageConverter,以支持多樣的轉換需求。
WebMvcConfigurationSupport.java
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setWriteAcceptCharset(false);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringConverter);
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new SourceHttpMessageConverter<Source>());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
messageConverters.add(new AtomFeedHttpMessageConverter());
messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build();
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(objectMapper));
}
else if (jaxb2Present) {
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build();
messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));
}
else if (gsonPresent) {
messageConverters.add(new GsonHttpMessageConverter());
}
}
五、相關依賴
大家可能會發現springboot項目都沒有jackson相關的依賴,那爲什麼可以進行jackson的序列化呢,那是因爲在spring-boot-starter-web依賴中其實已經包含了jackson相關的依賴。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-cbor</artifactId>
<version>2.9.8</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-smile</artifactId>
<version>2.9.8</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.8</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
六、總結
看似簡簡單單的@RequestBody和@ResponseBody兩個註解,其實內部做了大量的準備工作。現在童鞋們明白這整個過程的實現原理吧。
想要更多幹貨、技術猛料的孩子,快點拿起手機掃碼關注我,我在這裏等你哦~
林老師帶你學編程:https://wolzq.com