• Spring Boot REST(二)源码分析


    Spring Boot REST(二)源码分析

    Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)

    SpringBoot REST 系列相关的文章:

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

    在上一篇文章中提到了 Spring Boot 中的 REST 的一些使用方法,@ResponseBody 默认返回一个 json,如果需要返回 xml 或者自定义返回媒体类型时怎么办呢?

    @GetMapping("/v1/{user_id}")
    public User user(@PathVariable("user_id") String userId) {
        return new User(userId, "binarylei", "123456");
    }
    

    一、自定义媒体类型

    1.1 媒体类型

    首先要解释媒体类型这个概念,常见的媒体类型有 application/json、application/xml 等。

    (1) 浏览器

    浏览器即可以指定要发送的格式(Content-Type),也可以指定可以要接收的数据格式(Accept),如下表示发送 json 格式,接收 xml 格式:

    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 格式了。

    1.3 自定义解析器

    (1) PropertiesHttpMessageConverter

    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
    请求参数:{}
    

    二、源码分析

    @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 方法。


    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    K8s学习笔记
    NETCore在Centos下的内存泄漏排查
    搭建Linux下GO的开发环境
    Docker 运行mysql8.0挂载数据卷
    一张图解释Docker
    NetCore基于Consul+Ocelot+Docker+Jenkin搭建微服务架构
    Jenkins+Docker+Git实现自动化部署
    使用阿里云容器镜像服务示例
    Go重新学习记录
    软谋在线教育诚招php,java,.net,设计师讲师(可兼职)
  • 原文地址:https://www.cnblogs.com/binarylei/p/10201762.html
Copyright © 2020-2023  润新知