converters chain

Request data
=> HttpMessageConverter
=> FormHttpMessageConverter
=> MappingJackson2HttpMessageConverter && @RequestBody
=> Yml2HttpMessageConverter(自定义)
=> Validation验证
=> 数据持久化
=> 返回json

数据流转

HttpMessageConverter

采用RequestResponseBodyMethodProcessor方法执行器,
前台提交数据,设置contentType,spring-web根据contentType,解析数据,并映射。

spring内置当HttpMessageConverter有

  • ByteArrayHttpMessageConverter – converts byte arrays
  • StringHttpMessageConverter – converts Strings
  • ResourceHttpMessageConverter – converts org.springframework.core.io.Resource for any type of octet stream
  • SourceHttpMessageConverter – converts javax.xml.transform.Source
  • FormHttpMessageConverter – converts form data to/from a MultiValueMap<String, String>.
  • Jaxb2RootElementHttpMessageConverter – converts Java objects to/from XML (added only if JAXB2 is present on the classpath)
  • MappingJackson2HttpMessageConverter – converts JSON (added only if Jackson 2 is present on the classpath)
  • MappingJacksonHttpMessageConverter – converts JSON (added only if Jackson is present on the classpath)
  • AtomFeedHttpMessageConverter – converts Atom feeds (added only if Rome is present on the classpath)
  • RssChannelHttpMessageConverter – converts RSS feeds (added only if Rome is present on the classpath)

平时我们用当最多当就是MappingJackson2HttpMessageConverterFormHttpMessageConverter

  • FormHttpMessageConverter使用的contentTypeapplication/x-www-form-urlencodedmultipart/form-data
  • MappingJackson2HttpMessageConverter使用的contentTypeapplication/json

NOTED

如果使用GET请求数据,那么使用ServletModelAttributeMethodProcessor,将不会使用转化器类。

pom.xml

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

类型转换

  • MappingJackson2HttpMessageConverter对基本类型(包括枚举,List<> String[])支持都非常好。如果自定义类需要继承JsonDeserializer
@JsonComponent
public class PhoneJsonDeserializer extends JsonDeserializer<Phone> {
    @Override
    public Phone deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        String[] phonetArr = node.asText().split("-");
        Phone phone = new Phone();
        phone.setCode(phonetArr[0]);
        phone.setNumber(phonetArr[1]);
        return phone;
    }
}
  • GET提交和multipart/form-data实现Converter
@Component
public class PhoneConverter implements Converter<String, Phone> {

    @Override
    public Phone convert(String source) {
        if (StringUtils.isEmpty(source)) {
            return null;
        }

        String[] phonetArr = source.split("-");
        Phone phone = new Phone();
        phone.setCode(phonetArr[0]);
        phone.setNumber(phonetArr[1]);

        return phone;
    }
}

自定义转换器

定义一个可以转换application/yml的转换器
pom.xml添加

 <dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.19</version>
</dependency>
@Component
public class Yml2HttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    public Yml2HttpMessageConverter() {
        super(new MediaType[]{new MediaType("application", "yml"), new MediaType("application", "ymal")});
    }

    @Override
    protected boolean supports(Class<?> aClass) {
        return true;
    }

    @Override
    protected Object readInternal(Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        Yaml yaml = new Yaml();
        return yaml.loadAs(httpInputMessage.getBody(), aClass);
    }

    @Override
    protected void writeInternal(Object o, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        Yaml yaml = new Yaml();
        OutputStream outputStream = httpOutputMessage.getBody();
        outputStream.write(yaml.dump(o).getBytes());
        outputStream.close();
    }
}

@Component注解会放在在所有converter的第一个。优先于MappingJackson2HttpMessageConverter。要改变顺序,WebMvcConfigurer中去实现方法extendMessageConverters。导致response的时候,都是yml格式,而不是json格式。

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * https://www.cnblogs.com/slankka/p/11437034.html
     * 加到最后
     * @param converters
     */
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new Yml2HttpMessageConverter());
    }
}

这样response的时候,优先json格式,如果需要yml格式的返回可以手动指定

@GetMapping (produces = "application/yml")
public UserDTO getData(@RequestBody UserDTO userDTO) {
    log.info(userDTO.toString());
    return userDTO;
}

Validation

使用默认验证器

controller添加注解@Valid,model添加验证注解如:@NotBlank

    @GetMapping (produces = "application/yml")
    public UserDTO getData(@RequestBody @Valid UserDTO userDTO, BindingResult result) throws Exception {
        if (result.hasErrors()) {
            throw new Exception("验证失败");
        }
        log.info(userDTO.toString());
        return userDTO;
    }

自定义验证器

@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumberConstraint {
    String message() default "Invalid phone number";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
public class PhoneNumberValidator implements
        ConstraintValidator<PhoneNumberConstraint, Phone> {

    @Override
    public void initialize(PhoneNumberConstraint constraintAnnotation) {
    }

    /**
     * @param phone
     * @param context
     * @return
     */
    @Override
    public boolean isValid(Phone phone, ConstraintValidatorContext context) {
        if (Objects.isNull(phone)) {
            return true;
        }

        return (phone.getCode() + "-" + phone.getNumber()).matches("\\d+[-]\\d+");
    }
}

使用注解

 @PhoneNumberConstraint
 private Phone phone;

持久化

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.9</version>
    </dependency>

application.xml

spring:
  datasource:
    name: druidDataSource
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/iis?useUnicode=true&characterEncoding=UTF-8
      username: root
      password: jkxyx205
      initialSize: 1
      minIdle: 5
      maxActive: 10
      maxWait: 60000
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true

    hibernate:
      ddl-auto: update
public class ListJsonAttributeConverter<T> implements AttributeConverter<List<T>, String> {

    @Override
    public String convertToDatabaseColumn(List<T> list) {
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }

        try {
            return JsonUtils.toJson(list);
        } catch (IOException e) {
            return null;
        }
    }

    @Override
    public List<T> convertToEntityAttribute(String json) {
        Class<?> clazz = ClassUtils.getActualTypeArgument(this.getClass())[0];

        if (StringUtils.isBlank(json)) {
            return Collections.emptyList();
        }

        try {
            return (List<T>) JsonUtils.toList(json, clazz);
        } catch (IOException e) {
            e.printStackTrace();
            return Collections.emptyList();
        }
    }
}
public class PojoJsonAttributeConverter<T> implements AttributeConverter<T, String> {

    @Override
    public String convertToDatabaseColumn(T t) {
        if (Objects.isNull(t)) {
            return null;
        }

        try {
            return JsonUtils.toJson(t);
        } catch (IOException e) {
            return null;
        }
    }

    @Override
    public T convertToEntityAttribute(String json) {
        Class<?> clazz = ClassUtils.getActualTypeArgument(this.getClass())[0];
        if (StringUtils.isBlank(json)) {
            return null;
        }

        try {
            return (T) JsonUtils.toObject(json, clazz);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

将对象以 JSON 字符串的形式持久化到数据库中,使用继承指定范型类型

public class PriceConverter extends ListJsonAttributeConverter<List<QuotationExpression>> {}
public class OrderConverter extends PojoJsonAttributeConverter<QuotationExpression> {}

指定对应的convert

@Convert(converter = PriceConverter.class)
private List<QuotationExpression> quotationExpressionList;

@Convert(converter =OrderConverter.class)
private QuotationExpression quotationExpression;

https://vladmihalcea.com/jpa-attributeconverter/

https://zenidas.wordpress.com/recipes/jpa-converter-as-json/

response JSON

public class AddressJsonSerializer extends JsonSerializer<Address> {

    @Override
    public void serialize(Address address, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        address.setAddrDetail("China "+ address.getAddrDetail());
        jsonGenerator.writeObject(address);
    }
}
    @Convert(converter = AddressConverter.class)
    @JsonSerialize(using = AddressJsonSerializer.class)
    private Address address;