咱們常常須要在HttpResponse中設置一些headers,咱們使用Spring MVC框架的時候咱們如何給Response設置Header呢?javascript
So easy, 看下面的代碼:html
@RequestMapping(value = "/rulelist", method = RequestMethod.GET) @ResponseBody public String getRuleList(HttpServletRequest request, HttpServletResponse response) { response.addHeader("test", "test"); return service.getRuleList(); }
經過驗證,咱們能夠看到test項已經被成功添加到response的頭部信息java
Content-Length: 2 kilobytes Content-Type: text/plain;charset=ISO-8859-1 Server: Apache-Coyote/1.1 test: test
接下來,咱們但願修改Content-Type,從而統一服務器端和客戶端的內容編碼。咱們繼續修改代碼,json
@RequestMapping(value = "/rulelist", method = RequestMethod.GET) @ResponseBody public String getRuleList(HttpServletRequest request, HttpServletResponse response) { response.addHeader("Content-Type", "application/json;charset=UTF-8"); return service.getRuleList(); }
接下來,咱們驗證一下結果:服務器
Content-Length: 2 kilobytes Content-Type: text/plain;charset=ISO-8859-1 Server: Apache-Coyote/1.1
和咱們預想的並同樣,response的content-type header沒有被設置成"application/json;charset=UTF-8",很使人困惑。網絡
那麼,接下來讓咱們來探索下Spring MVC內部是如何處理這一過程的。首先咱們先要對Spring MVC框架處理Http請求的流程有一個總體的瞭解。app
下圖清晰地向你們展現了Spring MVC處理HTTP請求的流程,(圖片來自網絡)框架
具體流程以下:this
1. DispatcherServlet接收到Request請求編碼
2. HandlerMapping選擇一個合適的Handler處理Request請求
3-4. 選擇合適的HandlerAdapter,調用用戶編寫的Controller處理業務邏輯。(HandlerAdapter主要是幫助Spring MVC支持多種類型的Controller)
5. Controller將返回結果放置到Model中而且返回view名稱給Handler Adapter
6. DispatcherServlet選擇合適的ViewResolver來生成View對象
7-8. View對象利用Model中的數據進行渲染並返回數據
相信你們對於上面的處理流程並不陌生,上面的流程圖向咱們展現了SpringMVC生成ModelAndView並返回response的大致流程。
下面咱們來看看咱們上面代碼片斷的處理流程是如何進行的?
從上面的流程圖咱們能夠看到,content-type header是單獨被處理的,具體過程能夠參考下面的源碼(AbstractMessageConverterMethodProcessor):
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException { Class<?> returnValueClass = getReturnValueType(returnValue, returnType); HttpServletRequest servletRequest = inputMessage.getServletRequest(); List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest); //適合的兼容media types類型實際上,咱們可使用produces = {}來指定咱們須要的mediatype List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass); Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>(); for (MediaType requestedType : requestedMediaTypes) { for (MediaType producibleType : producibleMediaTypes) { if (requestedType.isCompatibleWith(producibleType)) { compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType)); } } } if (compatibleMediaTypes.isEmpty()) { if (returnValue != null) { throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes); } return; } List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes); MediaType.sortBySpecificityAndQuality(mediaTypes); MediaType selectedMediaType = null; //選擇最匹配的mediaType for (MediaType mediaType : mediaTypes) { if (mediaType.isConcrete()) { selectedMediaType = mediaType; break; } else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) { selectedMediaType = MediaType.APPLICATION_OCTET_STREAM; break; } } if (selectedMediaType != null) { selectedMediaType = selectedMediaType.removeQualityValue(); for (HttpMessageConverter<?> messageConverter : this.messageConverters) { //遍歷messageConvertors, 尋找能夠處理相應返回類型和mediatype的HttpMessageConvertor if (messageConverter.canWrite(returnValueClass, selectedMediaType)) { returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType, (Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage); if (returnValue != null) { //這裏將會填充mediatype到header,並將httpmessage發送給請求者 ((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage); if (logger.isDebugEnabled()) { logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" + messageConverter + "]"); } } return; } } } if (returnValue != null) { throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes); } }
接下來,將選擇好的mediatype寫入到HttpOutputMessage中
public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { final HttpHeaders headers = outputMessage.getHeaders(); //設置contenttype到HttpOutputMessage if (headers.getContentType() == null) { MediaType contentTypeToUse = contentType; if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) { contentTypeToUse = getDefaultContentType(t); } if (contentTypeToUse != null) { headers.setContentType(contentTypeToUse); } } if (headers.getContentLength() == -1) { Long contentLength = getContentLength(t, headers.getContentType()); if (contentLength != null) { headers.setContentLength(contentLength); } } /* 省略了不相干代碼 */ }
最終的Headers設置在ServletServerHttpResponse類中完成,
private void writeHeaders() { if (!this.headersWritten) { for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) { String headerName = entry.getKey(); for (String headerValue : entry.getValue()) { //將複合類中以前設置的header(content-type)內容補充到servletResponse this.servletResponse.addHeader(headerName, headerValue); } } // HttpServletResponse exposes some headers as properties: we should include those if not already present if (this.servletResponse.getContentType() == null && this.headers.getContentType() != null) { this.servletResponse.setContentType(this.headers.getContentType().toString()); } if (this.servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null && this.headers.getContentType().getCharSet() != null) { this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharSet().name()); } this.headersWritten = true; } }
從上述的代碼中,咱們能夠看到在RequestResponseBodyMethodProcessor這個ReturnValueHandler中,media-type被單獨的邏輯進行處理,所以直接在ServletResponse中設置content-type header並不能正常生效。
須要在@RequestMapping中添加produces = {} 進行設置才能夠。
參考:https://www.cnblogs.com/kaiblog/p/7565231.html
咱們常常須要在HttpResponse中設置一些headers,咱們使用Spring MVC框架的時候咱們如何給Response設置Header呢?
Sooooooooooooo easy, 看下面的代碼:
1
2
3
4
5
6
7
|
@RequestMapping
(value =
"/rulelist"
, method = RequestMethod.GET)
@ResponseBody
public
String getRuleList(HttpServletRequest request,
HttpServletResponse response) {
response.addHeader(
"test"
,
"test"
);
return
service.getRuleList();
}
|
經過驗證,咱們能夠看到test項已經被成功添加到response的頭部信息
1
2
3
4
|
Content-Length: 2 kilobytes
Content-Type: text/plain;charset=ISO-8859-1
Server: Apache-Coyote/1.1
test: test
|
接下來,咱們但願修改Content-Type,從而統一服務器端和客戶端的內容編碼。咱們繼續修改代碼,
1
2
3
4
5
6
7
|
@RequestMapping
(value =
"/rulelist"
, method = RequestMethod.GET)
@ResponseBody
public
String getRuleList(HttpServletRequest request,
HttpServletResponse response) {
response.addHeader(
"Content-Type"
,
"application/json;charset=UTF-8"
);
return
service.getRuleList();
}
|
接下來,咱們驗證一下結果:
1
2
3
|
Content-Length: 2 kilobytes
Content-Type: text/plain;charset=ISO-8859-1
Server: Apache-Coyote/1.1
|
和咱們預想的並同樣,response的content-type header沒有被設置成"application/json;charset=UTF-8",很使人困惑。
那麼,接下來讓咱們來探索下Spring MVC內部是如何處理這一過程的。首先咱們先要對Spring MVC框架處理Http請求的流程有一個總體的瞭解。
下圖清晰地向你們展現了Spring MVC處理HTTP請求的流程,(圖片來自網絡)
具體流程以下:
1. DispatcherServlet接收到Request請求
2. HandlerMapping選擇一個合適的Handler處理Request請求
3-4. 選擇合適的HandlerAdapter,調用用戶編寫的Controller處理業務邏輯。(HandlerAdapter主要是幫助Spring MVC支持多種類型的Controller)
5. Controller將返回結果放置到Model中而且返回view名稱給Handler Adapter
6. DispatcherServlet選擇合適的ViewResolver來生成View對象
7-8. View對象利用Model中的數據進行渲染並返回數據
相信你們對於上面的處理流程並不陌生,上面的流程圖向咱們展現了SpringMVC生成ModelAndView並返回response的大致流程。
下面咱們來看看咱們上面代碼片斷的處理流程是如何進行的?
從上面的流程圖咱們能夠看到,content-type header是單獨被處理的,具體過程能夠參考下面的源碼(AbstractMessageConverterMethodProcessor):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
protected
<T>
void
writeWithMessageConverters(T returnValue, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws
IOException, HttpMediaTypeNotAcceptableException {
Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
HttpServletRequest servletRequest = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
//適合的兼容media types類型實際上,咱們可使用produces = {}來指定咱們須要的mediatype
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
Set<MediaType> compatibleMediaTypes =
new
LinkedHashSet<MediaType>();
for
(MediaType requestedType : requestedMediaTypes) {
for
(MediaType producibleType : producibleMediaTypes) {
if
(requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if
(compatibleMediaTypes.isEmpty()) {
if
(returnValue !=
null
) {
throw
new
HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return
;
}
List<MediaType> mediaTypes =
new
ArrayList<MediaType>(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes);
MediaType selectedMediaType =
null
;
//選擇最匹配的mediaType
for
(MediaType mediaType : mediaTypes) {
if
(mediaType.isConcrete()) {
selectedMediaType = mediaType;
break
;
}
else
if
(mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break
;
}
}
if
(selectedMediaType !=
null
) {
selectedMediaType = selectedMediaType.removeQualityValue();
for
(HttpMessageConverter<?> messageConverter :
this
.messageConverters) {
//遍歷messageConvertors, 尋找能夠處理相應返回類型和mediatype的HttpMessageConvertor
if
(messageConverter.canWrite(returnValueClass, selectedMediaType)) {
returnValue =
this
.adviceChain.invoke(returnValue, returnType, selectedMediaType,
(Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
if
(returnValue !=
null
) {
//這裏將會填充mediatype到header,並將httpmessage發送給請求者
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
if
(logger.isDebugEnabled()) {
logger.debug(
"Written ["
+ returnValue +
"] as \""
+ selectedMediaType +
"\" using ["
+
messageConverter +
"]"
);
}
}
return
;
}
}
}
if
(returnValue !=
null
) {
throw
new
HttpMediaTypeNotAcceptableException(
this
.allSupportedMediaTypes);
}
}
|
接下來,將選擇好的mediatype寫入到HttpOutputMessage中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
final
void
write(
final
T t, MediaType contentType, HttpOutputMessage outputMessage)
throws
IOException, HttpMessageNotWritableException {
final
HttpHeaders headers = outputMessage.getHeaders();
//設置contenttype到HttpOutputMessage
if
(headers.getContentType() ==
null
) {
MediaType contentTypeToUse = contentType;
if
(contentType ==
null
|| contentType.isWildcardType() || contentType.isWildcardSubtype()) {
contentTypeToUse = getDefaultContentType(t);
}
if
(contentTypeToUse !=
null
) {
headers.setContentType(contentTypeToUse);
}
}
if
(headers.getContentLength() == -
1
) {
Long contentLength = getContentLength(t, headers.getContentType());
if
(contentLength !=
null
) {
headers.setContentLength(contentLength);
}
}
/* 省略了不相干代碼 */
}
|
最終的Headers設置在ServletServerHttpResponse類中完成,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
private
void
writeHeaders() {
if
(!
this
.headersWritten) {
for
(Map.Entry<String, List<String>> entry :
this
.headers.entrySet()) {
String headerName = entry.getKey();
for
(String headerValue : entry.getValue()) {
//將複合類中以前設置的header(content-type)內容補充到servletResponse
this
.servletResponse.addHeader(headerName, headerValue);
}
}
// HttpServletResponse exposes some headers as properties: we should include those if not already present
if
(
this
.servletResponse.getContentType() ==
null
&&
this
.headers.getContentType() !=
null
) {
this
.servletResponse.setContentType(
this
.headers.getContentType().toString());
}
if
(
this
.servletResponse.getCharacterEncoding() ==
null
&&
this
.headers.getContentType() !=
null
&&
this
.headers.getContentType().getCharSet() !=
null
) {
this
.servletResponse.setCharacterEncoding(
this
.headers.getContentType().getCharSet().name());
}
this
.headersWritten =
true
;
}
}
|
從上述的代碼中,咱們能夠看到在RequestResponseBodyMethodProcessor這個ReturnValueHandler中,media-type被單獨的邏輯進行處理,所以直接在ServletResponse中設置content-type header並不能正常生效。
須要在@RequestMapping中添加produces = {} 進行設置才能夠。