SpringMVC注解之@ResponseBody注解原理
一、介紹
- @ResponseBody 注解的作用是將方法的返回值通過(guò)適當(dāng)?shù)霓D(zhuǎn)換器轉(zhuǎn)換為指定的格式之后,寫入到 response 對(duì)象的 body 區(qū),通常用來(lái)返回 JSON、XML 數(shù)據(jù)。
- 使用了 @ResponseBody 注解標(biāo)記的方法不再做視圖解析
二、作用范圍
- 標(biāo)記在方法上
- 標(biāo)記在類上
通過(guò) @RestController 注解實(shí)現(xiàn),此時(shí)所有的方法都將會(huì)被添加 @ResponseBody 注解
三、源碼分析
具體為何調(diào)用了以下方法可以看我的另一篇文章。SpringMVC 執(zhí)行流程解析
ServletInvocableHandlerMethod # invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
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;
}
}
該方法中調(diào)用了 handleReturnValue() 方法去處理返回值。SpringMVC 中使用 RequestResponseBodyMethodProcessor 類來(lái)處理 @ResponseBody 標(biāo)記的方法
RequestResponseBodyMethodProcessor # handleReturnValue
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// 設(shè)置請(qǐng)求已經(jīng)被完全處理了,則后面不再做視圖解析
// 后面我還會(huì)提到的
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
該方法中通過(guò) mavContainer.setRequestHandled(true); 設(shè)置請(qǐng)求已經(jīng)被完全處理了,則后面不再做視圖解析。然后調(diào)用了 writeWithMessageConverters() 方法。
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// 存儲(chǔ)響應(yīng)體的信息
Object body;
// 返回值類型
Class<?> valueType;
// 目標(biāo)類型
Type targetType;
// 返回值類型是否是 CharSequence
// 是則將 返回值類型和目標(biāo)類型設(shè)置為 String.class
if (value instanceof CharSequence) {
body = value.toString();
valueType = String.class;
targetType = String.class;
}
// 不是 CharSequence 類型,一般是我們的自定義類
else {
body = value;
valueType = getReturnValueType(body, returnType);
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
// 返回值類型是否是實(shí)現(xiàn)了 Resource 接口的資源
// 這里我就不分析了
if (isResourceType(value, returnType)) {
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource) value;
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
body = HttpRange.toResourceRegions(httpRanges, resource);
valueType = body.getClass();
targetType = RESOURCE_REGION_LIST_TYPE;
}
catch (IllegalArgumentException ex) {
outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
}
}
}
// 選中的媒體類型
MediaType selectedMediaType = null;
MediaType contentType = outputMessage.getHeaders().getContentType();
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
if (isContentTypePreset) {
if (logger.isDebugEnabled()) {
logger.debug("Found 'Content-Type:" + contentType + "' in response");
}
selectedMediaType = contentType;
}
else {
HttpServletRequest request = inputMessage.getServletRequest();
// 可接受的媒體類型
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
// 可產(chǎn)生的媒體類型
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
// 將要被使用的媒體類型
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
if (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
for (MediaType mediaType : mediaTypesToUse) {
// 該媒體類型是否是具體的
// 也就是不包含類似于 * 這樣的通配符
if (mediaType.isConcrete()) {
// 選中要使用的媒體類型
selectedMediaType = mediaType;
break;
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Using '" + selectedMediaType + "', given " +
acceptableTypes + " and supported " + producibleTypes);
}
}
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) {
// 使用類型轉(zhuǎn)換器將請(qǐng)求寫入到 response body 中
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;
}
}
}
if (body != null) {
Set<MediaType> producibleMediaTypes =
(Set<MediaType>) inputMessage.getServletRequest()
.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
throw new HttpMessageNotWritableException(
"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
}
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
該方法中通過(guò)調(diào)用 getAcceptableMediaTypes() 方法獲取到 acceptableTypes,getProducibleMediaTypes() 方法獲取到 producibleTypes,然后調(diào)用 isCompatibleWith() 方法比較 acceptableTypes 和 producibleTypes,獲取到兩者都兼容的類型。最后通過(guò)調(diào)用 isConcrete() 獲取到一個(gè)具體使用的媒體類型。
AbstractMessageConverterMethodProcessor # getProducibleMediaTypes
protected List<MediaType> getProducibleMediaTypes(
HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
Set<MediaType> mediaTypes =
(Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<>(mediaTypes);
}
else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<>();
// 遍歷類型轉(zhuǎn)化器,獲取支持的媒體類型
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter && targetType != null) {
if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
else if (converter.canWrite(valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}
else {
return Collections.singletonList(MediaType.ALL);
}
}
該方法中通過(guò)遍歷類型轉(zhuǎn)換器,根據(jù)類型轉(zhuǎn)換器獲取到支持的媒體類型。常見(jiàn)的類型轉(zhuǎn)化器有 StringHttpMessageConverter 支持轉(zhuǎn)換為 String 類型,MappingJackson2HttpMessageConverter 支持轉(zhuǎn)換為 json 類型,MappingJackson2XmlHttpMessageConverter 支持轉(zhuǎn)換為 XML 類型。
以轉(zhuǎn)換為 JSON 數(shù)據(jù)為例。我們最終選擇的媒體類型就是 “application/json” ,然后調(diào)用 AbstractGenericHttpMessageConverter # write 方法將數(shù)據(jù)寫入到 response body 中。
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();
// 添加響應(yīng)頭
// 設(shè)置 Content-Type 為 application/json
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}));
}
else {
// 寫入數(shù)據(jù)到 response body 中
writeInternal(t, type, outputMessage);
outputMessage.getBody().flush();
}
}
該方法中設(shè)置了響應(yīng)頭的 Content-Type 為 application/json,然后調(diào)用 writeInternal() 方法寫數(shù)據(jù)
AbstractJackson2HttpMessageConverter # writeInternal
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// 獲取媒體類型為 application/json
MediaType contentType = outputMessage.getHeaders().getContentType();
// 獲取 JSON 數(shù)據(jù)的編碼為 UTF-8
JsonEncoding encoding = getJsonEncoding(contentType);
// 獲取到 HttpServletResponse 的輸出流對(duì)象
// 用于將數(shù)據(jù)寫入到 response body 中
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
// 生成 JSON 數(shù)據(jù)的類
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputStream, 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())) {
// 獲取 java 類型,一般是我們自定義的類
javaType = getJavaType(type, null);
}
// 用于操作可序列化對(duì)象的類
// 我們自定義的類一定要實(shí)現(xiàn) Serializable 接口,并設(shè)置 get/set 方法
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);
}
// 寫入數(shù)據(jù)
objectWriter.writeValue(generator, value);
writeSuffix(generator, object);
generator.flush();
generator.close();
}
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);
}
}
該方法中通過(guò)調(diào)用 outputMessage.getBody() 方法獲取到了 HttpServletResponse 的輸出流對(duì)象,用于將數(shù)據(jù)輸出到 response body 中。并設(shè)置了 JSON 數(shù)據(jù)的編碼格式為 UTF-8,然后通過(guò) ObjectWriter 對(duì)象操作我們自定義的可序列化的對(duì)象,將該對(duì)象轉(zhuǎn)換為 JSON 格式輸出到 response body 中。
AbstractJackson2HttpMessageConverter # getJsonEncoding
protected JsonEncoding getJsonEncoding(@Nullable MediaType contentType) {
if (contentType != null && contentType.getCharset() != null) {
Charset charset = contentType.getCharset();
JsonEncoding encoding = ENCODINGS.get(charset.name());
if (encoding != null) {
return encoding;
}
}
return JsonEncoding.UTF8;
}
設(shè)置 JSON 數(shù)據(jù)的編碼格式為 UTF-8
ServletServerHttpResponse # getBody
public OutputStream getBody() throws IOException {
this.bodyUsed = true;
writeHeaders();
return this.servletResponse.getOutputStream();
}
獲取 HttpServletResponse 的輸出流
到這里我們已經(jīng)實(shí)現(xiàn)了將數(shù)據(jù)轉(zhuǎn)化為 JSON 格式輸出到 response body 中了。
還記得前面提到的 mavContainer.setRequestHandled(true) 這個(gè)方法嗎,前面我說(shuō)了調(diào)用了這個(gè)方法后,就不再做視圖解析了,我們這里再具體分析一下。
RequestMappingHandlerAdapter # invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
...
invocableMethod.invokeAndHandle(webRequest, mavContainer);
...
return getModelAndView(mavContainer, modelFactory, webRequest);
...
}
可以看到 getModelAndView() 方法是在 invokeAndHandle() 方法之后調(diào)用了,也就是在調(diào)用 getModelAndView() 方法前,我們已經(jīng)調(diào)用了 mavContainer.setRequestHandled(true) 方法了。getModelAndView() 方法就是做視圖解析的,我們來(lái)看一下該方法。
RequestMappingHandlerAdapter # getModelAndView
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
// 是否已經(jīng)完全處理了,若為 true,則直接返回 null
// mavContainer.setRequestHandled(true) 已設(shè)置為 true 了
if (mavContainer.isRequestHandled()) {
return null;
}
// 下面的代碼是做視圖解析
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
可以看到,該方法一開始就調(diào)用了 mavContainer.isRequestHandled() 方法,如果為 true,則返回 null,并進(jìn)行下面的視圖解析。而 mavContainer.setRequestHandled(true) 方法已經(jīng)將其設(shè)置為 true 了。這就是為什么加了 @ResponseBody 注解的方法不做視圖解析的原因。
四、總結(jié)
- @ResponseBody 注解即可加在方法中,也可以通過(guò) @RestController 注解加在類上
- 類上添加了 @RestController 注解等效于為該類的所有方法上添加 @ResponseBody 注解
- @ResponseBody 通過(guò)各種類型轉(zhuǎn)換器實(shí)現(xiàn)數(shù)據(jù)的轉(zhuǎn)換,如將數(shù)據(jù)轉(zhuǎn)換為 String、JSON、XML 等格式。并將數(shù)據(jù)寫入到 response body 中。而且它們使用的都是 UTF-8 編碼。
- 對(duì)于自定義的 Java 類轉(zhuǎn)換為 JSON 格式的數(shù)據(jù),該類要是可序列化的。
- 使用了 @ResponseBody 注解標(biāo)記的方法不再做視圖解
到此這篇關(guān)于Java源碼解析之@ResponseBody注解原理的文章就介紹到這了,更多相關(guān)@ResponseBody注解原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java把Map轉(zhuǎn)為對(duì)象的實(shí)現(xiàn)代碼
在項(xiàng)目開發(fā)中,經(jīng)常碰到map轉(zhuǎn)實(shí)體對(duì)象或者對(duì)象轉(zhuǎn)map的場(chǎng)景,工作中,很多時(shí)候我們可能比較喜歡使用第三方j(luò)ar包的API對(duì)他們進(jìn)行轉(zhuǎn)化,但這里,我想通過(guò)反射的方式對(duì)他們做轉(zhuǎn)化,感興趣的同學(xué)跟著小編來(lái)看看吧2023-08-08
Failed to execute goal org...的解決辦法
這篇文章主要介紹了Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1的解決辦法的相關(guān)資料,需要的朋友可以參考下2017-06-06
Spring Cloud Gateway 獲取請(qǐng)求體(Request Body)的多種方法
這篇文章主要介紹了Spring Cloud Gateway 獲取請(qǐng)求體(Request Body)的多種方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
Java刪除指定文件夾下的所有內(nèi)容的方法(包括此文件夾)
下面小編就為大家?guī)?lái)一篇Java刪除指定文件夾下的所有內(nèi)容的方法(包括此文件夾) 。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12
java 實(shí)現(xiàn)多個(gè)list 合并成一個(gè)去掉重復(fù)的案例
這篇文章主要介紹了java 實(shí)現(xiàn)多個(gè)list 合并成一個(gè)去掉重復(fù)的案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
深入理解Java運(yùn)行時(shí)數(shù)據(jù)區(qū)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Java運(yùn)行時(shí)數(shù)據(jù)區(qū)的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-06-06

