zoukankan      html  css  js  c++  java
  • 16.SpringBoot学习(十六)——Spring Boot MessageConverter消息转换器

    1.简介

    1.1 概述

    Spring MVC uses the HttpMessageConverter interface to convert HTTP requests and responses. Sensible defaults are included out of the box. For example, objects can be automatically converted to JSON (by using the Jackson library) or XML (by using the Jackson XML extension, if available, or by using JAXB if the Jackson XML extension is not available). By default, strings are encoded in UTF-8.

    Spring MVC使用HttpMessageConverter接口转换HTTP请求和响应。开箱即用中包含明智的默认设置。例如,可以将对象自动转换为JSON(通过使用Jackson库)或XML(通过使用Jackson XML扩展(如果可用)或通过使用JAXB(如果Jackson XML扩展不可用))。默认情况下,字符串以UTF-8编码。

    1.2 特点

    HttpMessageConverter 是一个接口,它包含以下几个方法

    image-20200729211313871

    • canRead: 判断是否支持解析当前 MediaType
    • canWrite: 判断是否支持输出当前 MediaType
    • getSupportedMediaTypes: 获取支持的 MediaTypes
    • read: 解析http消息内容
    • write: 输出指定MediaType的消息内容

    这里的MediaType即为http请求中常见的 Content-Type;例如:application/json、application/xml等

    2.演示环境

    1. JDK 1.8.0_201
    2. Spring Boot 2.2.0.RELEASE
    3. 构建工具(apache maven 3.6.3)
    4. 开发工具(IntelliJ IDEA )

    3.演示代码

    3.1 代码说明

    自定义HttpMessageConverter消息转换器,实现消息的解析和输出

    3.2 代码结构

    image-20200729212037648

    3.3 maven 依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    

    3.4 配置文件

    无配置

    3.5 java代码

    UserModel.java

    public class UserModel {
    
        private Long id;
        private String name;
        private Integer age;
        private Date birthday;
        private BigDecimal salary;
        private String phone;
    
        public UserModel() {}
    
        public UserModel(Long id, String name, Integer age, Date birthday, BigDecimal salary, String phone) {
            this.id = id;
            this.name = name;
            this.age = age;
            this.birthday = birthday;
            this.salary = salary;
            this.phone = phone;
        }
    
        // get&set&toString
    }
    

    UserRepository.java

    @Repository
    public class UserRepository {
    
        private static final AtomicLong ID_GENERATOR = new AtomicLong(2);
    
        private static final Map<Long, UserModel> USER_MAP = new HashMap<>();
    
        @PostConstruct
        public void init() {
            UserModel user1 =
                new UserModel(1L, "zhangsan", 20, new Date(), new BigDecimal("23456.11"), "13666666666");
            UserModel user2 =
                new UserModel(2L, "lisi", 30, new Date(), new BigDecimal("12345.67"), "13888888888");
            USER_MAP.put(user1.getId(), user1);
            USER_MAP.put(user2.getId(), user2);
        }
    
        public List<UserModel> findAll() {
            return new ArrayList<>(USER_MAP.values());
        }
    
        public UserModel findById(Long id) {
            return USER_MAP.get(id);
        }
    
        public UserModel add(UserModel userModel) {
            long id = ID_GENERATOR.incrementAndGet();
            userModel.setId(id);
            USER_MAP.put(id, userModel);
            return userModel;
        }
    
        public UserModel update(UserModel userModel) {
            USER_MAP.put(userModel.getId(), userModel);
            return USER_MAP.get(userModel.getId());
        }
    
        public UserModel deleteById(Long id) {
            UserModel userModel = USER_MAP.get(id);
            USER_MAP.remove(id);
            return userModel;
        }
    }
    

    WebMvcConfig.java

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Override
        public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
            // 扩展 MessageConverter,将 PropertiesHttpMessageConverter 放在第一位
            converters.add(0, new PropertiesHttpMessageConverter());
        }
    }
    

    PropertiesHttpMessageConverter.java

    public class PropertiesHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
    
        public PropertiesHttpMessageConverter() {
            super(MediaType.valueOf("text/properties"));
            setDefaultCharset(StandardCharsets.UTF_8);
        }
    
        @Override
        protected boolean supports(Class<?> clazz) {
            return clazz.isAssignableFrom(UserModel.class);
        }
    
        @Override
        protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
    
            Properties props = new Properties();
            props.load(new InputStreamReader(inputMessage.getBody(), getDefaultCharset()));
    
            // 要求对象必须有无参构造函数
            Object instance = ReflectUtils.newInstance(UserModel.class);
            Field[] fields = clazz.getDeclaredFields();
    
            Stream.of(fields).filter(field -> props.containsKey(field.getName())).forEach(field -> {
                String property = props.getProperty(field.getName());
                Class<?> fieldType = field.getType();
                field.setAccessible(true);
                ReflectionUtils.setField(field, instance, resolveFieldValue(property, fieldType));
            });
    
            return instance;
        }
    
        @Override
        protected void writeInternal(Object user, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotReadableException {
    
            Properties props = new Properties();
            Field[] fields = user.getClass().getDeclaredFields();
    
            Stream.of(fields).forEach(field -> {
                String fieldName = field.getName();
                field.setAccessible(true);
                Object fieldValue = ReflectionUtils.getField(field, user);
                props.put(fieldName, String.valueOf(fieldValue));
            });
    
            props.store(new OutputStreamWriter(outputMessage.getBody(), getDefaultCharset()),
                "written by properties message converter");
        }
    
        private Object resolveFieldValue(String property, Class<?> fieldType) {
    
            if (Integer.class == fieldType) {
                return Integer.valueOf(property);
            } else if (Long.class == fieldType) {
                return Long.valueOf(property);
            } else if (Short.class == fieldType) {
                return Short.valueOf(property);
            } else if (Byte.class == fieldType) {
                return Byte.valueOf(property);
            } else if (String.class == fieldType) {
                return property;
            } else if (Float.class == fieldType) {
                return Float.valueOf(property);
            } else if (Double.class == fieldType) {
                return Double.valueOf(property);
            } else if (BigDecimal.class == fieldType) {
                return new BigDecimal(property);
            } else if (Date.class == fieldType) {
                try {
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                    return sdf.parse(property);
                } catch (ParseException e) {
                    e.printStackTrace();
                    return null;
                }
            }
    
            return property;
        }
    }
    

    UserController.java

    @RestController
    @RequestMapping(value = "/user")
    public class UserController {
    
        @Autowired
        private UserRepository userRepository;
    
        @PostMapping(value = "/add1", consumes = "text/properties", produces = "application/json;charset=UTF-8")
        public UserModel add1(@RequestBody UserModel userModel) {
            return userRepository.add(userModel);
        }
    
        @PostMapping(value = "/add2", consumes = "application/json;charset=UTF-8", produces = "text/properties")
        public UserModel add2(@RequestBody UserModel userModel) {
            return userRepository.add(userModel);
        }
    }
    

    3.6 git 地址

    spring-boot/spring-boot-10-message-converter

    4.效果展示

    启动 SpringBoot10MessageConverterApplication.main 方法,在 spring-boot-message-converter.http 访问下列地址,观察输出信息是否符合预期。

    接收 text/properties 类型的参数,输出 application/json 格式内容

    ### POST /user/add1
    POST http://localhost:8080/user/add1
    Accept: application/json;charset=utf-8
    Content-Type: text/properties;charset=utf-8
    
    name=wangwu
    age=22
    birthday=1996-05-05
    salary=6666.66
    phone=13555555555
    

    image-20200729212747549

    接收 application/json 类型的参数,输出 text/properties 格式内容

    ### POST /user/add2
    POST http://localhost:8080/user/add2
    Accept: text/properties;charset=utf-8
    Content-Type: application/json;charset=utf-8
    
    {
    "name": "wangwu",
    "age": "22",
    "birthday": "1996-05-05",
    "salary": "6666.66",
    "phone": "13555555555"
    }
    

    image-20200729212844773

    5.源码分析

    5.1 自定义MessageConverter执行流程

    以 UserController#add1 为例,简单分析一下源码

    image-20200729214822124.png

    在 AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters 中有这样一段

    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
    
        //...
    
        try {
            message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
    		// 遍历所有的 messageConverters
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
                GenericHttpMessageConverter<?> genericConverter =
                    (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
                // 执行 canRead 方法,判断是否可以支持当前的 Content-Type
                if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
                    (targetClass != null && converter.canRead(targetClass, contentType))) {					
                    // 如果可以支持,判断是否有消息体
                    if (message.hasBody()) {
                        // 解析前处理逻辑
                        HttpInputMessage msgToUse =
                            getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
                        // 调用 read 方法解析消息内容
                        body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                                ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
                        // 解析后处理逻辑
                        body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
                    }
                    else {
                        // 消息体为空时处理逻辑
                        body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
                    }
                    break;
                }
            }
        }
        catch (IOException ex) {
            throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
        }
    
        // ...
    
        return body;
    }
    

    这里需要遍历 messageConverters 来寻找一个合适的处理器,那么这里的 messageConverters 如何获取到自定义的 HTTPMessageConverter 呢?

    其实,在项目启动的时候,自定义 HTTPMessageConverter 被加载到 applicationContext 中。RequestMappingHandlerAdapter 在初始化完成后,调用其 afterPropertiesSet

    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBody advice beans
        initControllerAdviceCache();
    
        if (this.argumentResolvers == null) {
            // 获取参数处理的 resolvers
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.initBinderArgumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
            this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }
    

    在 getDefaultArgumentResolvers 中有声明 RequestResponseBodyMethodProcessor 和 RequestPartMethodArgumentResolver,二者都需要调用 getMessageConverters 方法

    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
    
        // Annotation-based argument resolution
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        resolvers.add(new RequestParamMapMethodArgumentResolver());
        resolvers.add(new PathVariableMethodArgumentResolver());
        resolvers.add(new PathVariableMapMethodArgumentResolver());
        resolvers.add(new MatrixVariableMethodArgumentResolver());
        resolvers.add(new MatrixVariableMapMethodArgumentResolver());
        resolvers.add(new ServletModelAttributeMethodProcessor(false));
        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new RequestHeaderMapMethodArgumentResolver());
        resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new SessionAttributeMethodArgumentResolver());
        resolvers.add(new RequestAttributeMethodArgumentResolver());
    
        // Type-based argument resolution
        resolvers.add(new ServletRequestMethodArgumentResolver());
        resolvers.add(new ServletResponseMethodArgumentResolver());
        resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RedirectAttributesMethodArgumentResolver());
        resolvers.add(new ModelMethodProcessor());
        resolvers.add(new MapMethodProcessor());
        resolvers.add(new ErrorsMethodArgumentResolver());
        resolvers.add(new SessionStatusMethodArgumentResolver());
        resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
    
        // Custom arguments
        if (getCustomArgumentResolvers() != null) {
            resolvers.addAll(getCustomArgumentResolvers());
        }
    
        // Catch-all
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));
    
        return resolvers;
    }
    

    它的实现如下

    public List<HttpMessageConverter<?>> getMessageConverters() {
        return this.messageConverters;
    }
    

    这里的 messageConverters 通过构造函数加入了一部分,也在 WebMvcAutoConfiguration 中进行了扩展。

    5.2 自定义MessageConverter加载

    image-20200729222813002.png

    在 spring boot 启动的时候,会加载到 WebMvcAutoConfiguration.EnableWebMvcConfiguration 中 requestMappingHandlerAdapter 方法,这个方法用来声明一个 RequestMappingHandlerAdapter 的 bean,它又通过调用 super.requestMappingHandlerAdapter 来进行实例化。

    在 super.requestMappingHandlerAdapter 通过adapter.setMessageConverters(getMessageConverters()); 将 messageConverters 设置到 adapter 上,这里的 getMessageConverters 实现如下

    protected final List<HttpMessageConverter<?>> getMessageConverters() {
        if (this.messageConverters == null) {
            this.messageConverters = new ArrayList<>();
            // 配置 messageConverters
            configureMessageConverters(this.messageConverters);
            if (this.messageConverters.isEmpty()) {
                // 如果messageConverters为空,加载默认的配置
                addDefaultHttpMessageConverters(this.messageConverters);
            }
            // 加载扩展的 messageConverter
            extendMessageConverters(this.messageConverters);
        }
        return this.messageConverters;
    }
    

    在 extendMessageConverters 中通过委派,调用 DelegatingWebMvcConfiguration 的 extendMessageConverters 来扩展 messageConverters,最终调用到 WebMvcConfigurer 的 extendMessageConverters 方法

    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        for (WebMvcConfigurer delegate : this.delegates) {
            delegate.extendMessageConverters(converters);
        }
    }
    

    而 WebMvcConfig 恰好是 WebMvcConfigurer 的实现类,重写了它的 extendMessageConverters 方法,所以自定义的 HTTPMessageConverter 被加载到 RequestMappingHandlerAdapter 中。

    6.参考

    1. Spring Boot Features/message-converters
  • 相关阅读:
    ASP.NET中常用的26个优化性能方法(转)
    代码整洁
    【在开发中常用的UI控件】
    【加法计算器--结果label不显示加值】
    【点击textfield的时候不出现键盘】
    【XCODE上的项目运行到模拟器上是一片空白】
    【xcode commit失败 please tell me who you are】
    【storyboard 上没有箭头的解决办法】
    【ios模拟器上没 home键,怎么返回的?】
    【这是一个JAVA开发者的博客~】
  • 原文地址:https://www.cnblogs.com/col-smile/p/13423614.html
Copyright © 2011-2022 走看看