Spring 系列目錄(http://www.javashuo.com/article/p-hskusway-em.html)html
SpringBoot REST 系列相關的文章:java
在上一篇文章中提到了 Spring Boot 中的 REST 的一些使用方法,@ResponseBody 默認返回一個 json,若是須要返回 xml 或者自定義返回媒體類型時怎麼辦呢?git
@GetMapping("/v1/{user_id}") public User user(@PathVariable("user_id") String userId) { return new User(userId, "binarylei", "123456"); }
首先要解釋媒體類型這個概念,常見的媒體類型有 application/json、application/xml 等。github
(1) 瀏覽器spring
瀏覽器便可以指定要發送的格式(Content-Type),也能夠指定能夠要接收的數據格式(Accept),以下表示發送 json 格式,接收 xml 格式:json
Content-Type: application/xml Accept: application/json
(2) 服務器瀏覽器
@RequestMapping 註解有兩個參數能夠匹配這種請求。下面這個只處理髮送的請求是 xml 格式,返回 json 的請求。服務器
@GetMapping(value = "/v3/xml/to/json", consumes = "application/xml", produces = "application/json") public User propertiesToHJson(@RequestBody User user) { return new User("1", "com/github/binarylei", "123456"); }
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency>
Spring Boot 默認只能處理 json 這種媒體類型,引入上述的 jar 包後就能夠處理 xml 格式了。app
(1) PropertiesHttpMessageConverteride
public class PropertiesHttpMessageConverter extends AbstractHttpMessageConverter<User> { public PropertiesHttpMessageConverter() { super(Charset.forName("utf-8"), MediaType.valueOf("application/properties")); } @Override protected boolean supports(Class clazz) { return clazz == User.class; } @Override protected User readInternal(Class<? extends User> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { Properties properties = new Properties(); properties.load(inputMessage.getBody()); User user = new User(); user.setUserId(properties.getProperty("user.id")); user.setUsername(properties.getProperty("user.name")); return user; } @Override protected void writeInternal(User user, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { Properties properties = new Properties(); properties.setProperty("user.id", user.getUserId()); properties.setProperty("user.name", user.getUsername()); properties.setProperty("user.password", user.getPassword()); properties.store(outputMessage.getBody(), "write"); } }
(2) 配置類 PropertiesWebMvcConfigurer
@Configuration public class PropertiesWebMvcConfigurer implements WebMvcConfigurer { @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new PropertiesHttpMessageConverter()); } }
(3) rest 接口定義
@GetMapping(value = "/v3/properties/to/json", consumes = "application/properties", produces = "application/json") public User propertiesToHJson(@RequestBody User user) { return new User("1", "binarylei", "123456"); } @GetMapping(value = "/v3/json/to/properties", consumes = "application/json", produces = "application/properties") public User jsonToProperties(@RequestBody User user) { return new User("1", "binarylei", "123456"); }
(4) 測試
請求地址:localhost:8080//v3/properties/to/json 請求頭:Accept: application/properties, Content-Type: application/json 請求參數:user.id=1 user.name=binarylei
請求地址:localhost:8080/v3/json/to/properties 請求頭:Accept: application/json, Content-Type: application/properties 請求參數:{}
@EnableWebMvc 注入了 DelegatingWebMvcConfiguration 組件,其類圖結構以下:
在 WebMvcConfigurationSupport 類中定義了許多默認的 HttpMessageConverter,根據是否有相應的類加載來判斷是否啓動對應的 HttpMessageConverter。
// 類型轉換器 private List<HttpMessageConverter<?>> messageConverters; protected final List<HttpMessageConverter<?>> getMessageConverters() { if (this.messageConverters == null) { this.messageConverters = new ArrayList<>(); configureMessageConverters(this.messageConverters); // (1) if (this.messageConverters.isEmpty()) { addDefaultHttpMessageConverters(this.messageConverters); // (2) } extendMessageConverters(this.messageConverters); // (3) } return this.messageConverters; }
(1) 由子類 DelegatingWebMvcConfiguration 重寫了 configureMessageConverters 方法,其實是委託給了 WebMvcConfigurer 完成。
(2) 加載默認的 HttpMessageConverter
(3) 同 (1),也是由子類重寫 extendMessageConverters
下面咱們看一下 Spring Boot 默認加載了那些 HttpMessageConverter
boolean jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); boolean jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader); protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) { StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316 messageConverters.add(new ByteArrayHttpMessageConverter()); messageConverters.add(stringHttpMessageConverter); messageConverters.add(new ResourceHttpMessageConverter()); messageConverters.add(new ResourceRegionHttpMessageConverter()); try { messageConverters.add(new SourceHttpMessageConverter<>()); } catch (Throwable ex) { // Ignore when no TransformerFactory implementation is available... } messageConverters.add(new AllEncompassingFormHttpMessageConverter()); // 省略... if (jackson2XmlPresent) { Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml(); if (this.applicationContext != null) { builder.applicationContext(this.applicationContext); } messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build())); } if (jackson2Present) { Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json(); if (this.applicationContext != null) { builder.applicationContext(this.applicationContext); } messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build())); } // 省略... }
能夠看到除了 ByteArrayHttpMessageConverter 等是固定加載外,其他的都是經過判斷是否有相應的類來決定是否啓用。若是須要使用相應的解析器,只須要到相應的 jar 包添加到 pom.xml 中便可。
最終容器中加載了以下的 HttpMessageConverter 解析器:
0 = {ByteArrayHttpMessageConverter@5783} 1 = {StringHttpMessageConverter@5784} 2 = {ResourceHttpMessageConverter@5785} 3 = {ResourceRegionHttpMessageConverter@5786} 4 = {SourceHttpMessageConverter@5787} 5 = {AllEncompassingFormHttpMessageConverter@5788} 6 = {MappingJackson2XmlHttpMessageConverter@5789} 7 = {MappingJackson2HttpMessageConverter@5790}
上文中提到 Spring Boot 啓動時會在 messageConverters 集合中加載多個 HttpMessageConverter,到底執行那個呢?毫無疑問,執行確定有三個過程:一是匹配對應的 HttpMessageConverter;二是執行 Handler;三是執行 HttpMessageConverter 響應結果。
HttpMessageConverter 的執行是在 AbstractMessageConverterMethodProcessor#writeWithMessageConverters 中執行的,這個方法很長,咱們一點點來看。
MediaType selectedMediaType = null; MediaType contentType = outputMessage.getHeaders().getContentType(); if (contentType != null && contentType.isConcrete()) { selectedMediaType = contentType; } else { HttpServletRequest request = inputMessage.getServletRequest(); // 1. 獲取客戶端可接受的類型 Accept: application/jsion List<MediaType> acceptableTypes = getAcceptableMediaTypes(request); // 2. 服務端能夠生成的全部 MediaType 類型 List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType); if (body != null && producibleTypes.isEmpty()) { throw new HttpMessageNotWritableException( "No converter found for return value of type: " + valueType); } // 3. acceptableTypes 和 producibleTypes 比較,找出可用的 MediaType 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); } return; } MediaType.sortBySpecificityAndQuality(mediaTypesToUse); // 4. 若是有多個 MediaType 可用,選擇一個可用的返回 for (MediaType mediaType : mediaTypesToUse) { // 只要是非 */* 就直接返回 if (mediaType.isConcrete()) { selectedMediaType = mediaType; break; } else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) { selectedMediaType = MediaType.APPLICATION_OCTET_STREAM; break; } } }
客戶端能夠傳兩個請求頭過來:
Accept: application/xml // 客戶端可接收的媒體類型 Content-Type: application/json // 客戶端請求的媒體類型
// 遍歷 messageConverters,若是 converter 支持 selectedMediaType 則直接返回 for (HttpMessageConverter<?> converter : this.messageConverters) { GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null); // 1. canWrite 返回 true 則直接執行並結束循環 if (genericConverter != null ? ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) : converter.canWrite(valueType, selectedMediaType)) { // 2. 拿到 handler 的執行結果 body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<?>>) converter.getClass(), inputMessage, outputMessage); if (body != null) { Object theBody = body; addContentDispositionHeader(inputMessage, outputMessage); // 3. 執行對應的 genericConverter if (genericConverter != null) { genericConverter.write(body, targetType, selectedMediaType, outputMessage); } else { ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage); } } return; } }
核心的步驟 converter.write(body, selectedMediaType, outputMessage) 將 POJO 轉換爲 json 或 xml 後返回。
若是須要自定義 HttpMessageConverter,能夠直接繼承 AbstractHttpMessageConverter 類,重寫 supports、readInternal、writeInternal 方法。
天天用心記錄一點點。內容也許不重要,但習慣很重要!