Spring Boot REST(二)源碼分析

Spring Boot REST(二)源碼分析

Spring 系列目錄(http://www.javashuo.com/article/p-hskusway-em.html)html

SpringBoot REST 系列相關的文章:java

  1. SpringBoot REST(一)核心接口
  2. SpringBoot REST(二)源碼分析

在上一篇文章中提到了 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");
}

1、自定義媒體類型

1.1 媒體類型

首先要解釋媒體類型這個概念,常見的媒體類型有 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");
}

1.2 引入 application/xml 解析器

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

Spring Boot 默認只能處理 json 這種媒體類型,引入上述的 jar 包後就能夠處理 xml 格式了。app

1.3 自定義解析器

(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) 測試

  1. 測試1:
請求地址:localhost:8080//v3/properties/to/json
請求頭:Accept: application/properties, Content-Type: application/json
請求參數:user.id=1 user.name=binarylei
  1. 測試2:
請求地址:localhost:8080/v3/json/to/properties
請求頭:Accept: application/json, Content-Type: application/properties
請求參數:{}

2、源碼分析

@EnableWebMvc 注入了 DelegatingWebMvcConfiguration 組件,其類圖結構以下:

DelegatingWebMvcConfiguration 類圖

2.1 默認 HttpMessageConverter 加載

在 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}

2.2 HttpMessageConverter 執行過程

上文中提到 Spring Boot 啓動時會在 messageConverters 集合中加載多個 HttpMessageConverter,到底執行那個呢?毫無疑問,執行確定有三個過程:一是匹配對應的 HttpMessageConverter;二是執行 Handler;三是執行 HttpMessageConverter 響應結果。

HttpMessageConverter 的執行是在 AbstractMessageConverterMethodProcessor#writeWithMessageConverters 中執行的,這個方法很長,咱們一點點來看。

2.2.1 匹配 HttpMessageConverter

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  // 客戶端請求的媒體類型

2.2.2 執行 HttpMessageConverter

// 遍歷 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 後返回。

2.2.3 HttpMessageConverter

HttpMessageConverter

若是須要自定義 HttpMessageConverter,能夠直接繼承 AbstractHttpMessageConverter 類,重寫 supports、readInternal、writeInternal 方法。


天天用心記錄一點點。內容也許不重要,但習慣很重要!

相關文章
相關標籤/搜索