【从零开始学Spring Boot】-16.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 是一个接口,它包含以下几个方法

  • 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 代码结构

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

接收 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"
}

5.参考

  1. Spring Boot Features/message-converters

文章作者: Soulballad
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Soulballad !
评论
 上一篇
【源码分析-Spring Boot】-15.Spring Boot MessageConverter 执行和原理 【源码分析-Spring Boot】-15.Spring Boot MessageConverter 执行和原理
Spring Boot MessageConverter:【从零开始学Spring Boot】-16.Spring Boot MessageConverter消息转 1.自定义MessageConverter执行流程以 UserCont
2020-07-30
下一篇 
【源码分析-Spring Boot】-14.Spring Boot WebFlux Server 装配及启动 【源码分析-Spring Boot】-14.Spring Boot WebFlux Server 装配及启动
Spring Boot WebFlux:【从零开始学Spring Boot】-15.Spring Boot WebFlux路由 1.Webflux Server 如何启动? 使用 webflux 时,默认的 applicationCon
2020-07-29
  目录