我們經常需要在HttpResponse中設置一些headers,我們使用Spring MVC框架的時候我們如何給Response設置Header呢?
So easy, 看下面的代碼:
@RequestMapping(value = "/rulelist", method = RequestMethod.GET)@ResponseBody public String getRuleList(HttpServletRequest request,HttpServletResponse response) {response.addHeader("test", "test");return service.getRuleList(); }
jvm oom、?
通過驗證,我們可以看到test項已經被成功添加到response的頭部信息
Content-Length: 2 kilobytes Content-Type: text/plain;charset=ISO-8859-1 Server: Apache-Coyote/1.1 test: test
?
Spring MVC、接下來,我們希望修改Content-Type,從而統一服務器端和客戶端的內容編碼。我們繼續修改代碼,
@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請求的流程有一個整體的了解。
下圖清晰地向大家展示了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):
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 = {}來指定我們需要的mediatypeList<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; //選擇最匹配的mediaTypefor (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的HttpMessageConvertorif (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到HttpOutputMessageif (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)內容補充到servletResponsethis.servletResponse.addHeader(headerName, headerValue);}}// HttpServletResponse exposes some headers as properties: we should include those if not already presentif (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 = {} 進行設置才可以。