SpringMVC(四)自定义参数转换规则

版权声明:From Lay https://blog.csdn.net/Sadlay/article/details/84034311

SpringMVC(四)自定义参数转换规则

处理器获取参数逻辑

当一个请求到来时,在处理器执行的过程中,它首先会从HTTP请求和上下文环境来得到参数,如果是简易的参数它会以简单的转换器进行转换,而这些简单的转换器是SpringMVC自身已经提供了的。但是如果转换HTTP请求体(Body),它就会调用HttpMessageConverter接口的方法对请求体的信息进行转换,首先它会判断能否对请求体进行转换,如果可以就会将其转换为Java类型。

HttpMessageConverter接口源码


package org.springframework.http.converter;

public interface HttpMessageConverter<T> {
    //是否可读,其中clazz为Java类型,mediaType为http请求类型
    boolean canRead(Class<?> var1, @Nullable MediaType var2);
    
	//判断clazz类型是否能够转换为mediaType媒体类型
    boolean canWrite(Class<?> var1, @Nullable MediaType var2);
	
    //可支持的媒体类型列表
    List<MediaType> getSupportedMediaTypes();

    //当canRead()验证通过后,读入http请求信息
    T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;
    
	//当canWrite()方法验证通过后,写入响应
    void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;
}

上面的HttpMessageConverter接口只是将HTTP请求体转换为对应的Java对象,而对于HTTP参数和其他内容,还没有讨论。例如,以性别参数来说,前端可能传递给控制器的是一个整数,而控制器参数却是一个枚举,这样就需要提供自定义的参数转换规则。

在SpringMVC中,是通过WebDataBinder机制来获取参数的,它的主要作用是解析http请求的上下文,然后再控制器的调用之前转换参数并且提供验证的功能,为调用控制器的方法做准备。处理器会从HTTP请求中读取数据,然后通过三种接口来进行各类参数转换,者三种接口是ConverterFomatterGenericConverter。在SpringMVC的机制中这三种接口的实现类都采用了注册机的机制,默认的情况下SpringMVC已经在注册机内注册了许多的转换器,这样就可以实现大部分的数据类型的转换,所以在大部分的情况下下无需开发者再提供转换器。当下需要自定义转换规则时,只需要在注册机上注册自己的转换器就可以了。

实际上,WebDataBinder机制还有一个重要的功能,那就是验证转换结果。

SpringMVC\SpringMVC处理器HTTP请求体转换流程图
可以看到控制器的参数是处理器通过ConverterFormatterGenericConverter这三个接口转换出来的。

  • Converter:普通的转换器,例如有一个Integer类型的控制器参数,而从HTTP对应的为字符串,对应的Convert就会将字符串转换为Integer类型。
  • Formatter:格式化转换器,类似日期字符串就是通过它按照约定的格式转换为日期。
  • GenericConverter:将HTTP参数转换为数组。

转换器注册

对于数据类型转换,SpringMVC提供了一个服务机制去管理,它就是ConversionService接口。在默认情况下下,会使用这个接口的子类DefaultFormattingConversionService对象来管理这些转换器类。

ConversionService转换机制设计
可以看出,Converter、Formatter和GenericConverter可以通过注册机接口进行注册,这样处理器就可以获取对应的转换器来实现参数的转换。

上面讨论的是普通的SpringMVC的参数转换规则,而在spring boot中还提供了特殊的机制来管理这些转换器。Spring Boot的自动配置类WebMvcAutoConfiguration还定义了一个内部类WebMvcAutoConfigurationAdapter,代码如下

WebMvcAutoConfigurationAdapter源码

        public void addFormatters(FormatterRegistry registry) {
            
            //在IoC容器中获取Converter类型的Bean,然后获得迭代器
            Iterator var2 = this.getBeansOfType(Converter.class).iterator();
			//遍历迭代器,然后注册到服务类中
            while(var2.hasNext()) {
                Converter<?, ?> converter = (Converter)var2.next();
                registry.addConverter(converter);
            }
			
            //在IoC容器中获取GenericConverter类型的Bean,然后获得迭代器
            var2 = this.getBeansOfType(GenericConverter.class).iterator();
			//遍历迭代器,然后注册到服务类中
            while(var2.hasNext()) {
                GenericConverter converter = (GenericConverter)var2.next();
                registry.addConverter(converter);
            }
			
            //在IoC容器中获取Formatter类型的Bean,然后获得迭代器
            var2 = this.getBeansOfType(Formatter.class).iterator();
			//遍历迭代器,然后注册到服务类中
            while(var2.hasNext()) {
                Formatter<?> formatter = (Formatter)var2.next();
                registry.addFormatter(formatter);
            }

        }

可以看到,在spring boot的初始化中,会将对应用户自定义的ConvertFormatterGenericConverter的实现类所传就的spring bean自动地注册到DefaultFormattingConversionService对象中。这样对于开发者,只需要自定义ConvertFormatterGenericConverter的接口Bean,spring boot就通过这个方法将它们注册到ConversionService对象中。其中,格式化Formatter接口在实际开发中使用率较低。

一对一转换器(Converter)

Converter是一对一转换器,也就是从一种类型转换为另外一种类型,其接口定义十分简单。如下

Converter接口源码

package org.springframework.core.convert.converter;

import org.springframework.lang.Nullable;

@FunctionalInterface
public interface Converter<S, T> {
    
    //转换方法,S代表原类型,T代表目标类型
    @Nullable
    T convert(S var1);
}

这个接口类型有原类型(S)和目标类型(T)两种,它们通过convert方法进行转换。

例如,http的类型为字符串(String)型,而控制器参数为Long型,那么就可以通过Spring内部提供的StringToNumber进行转换。

示例:假设前端要传递一个用户信息,这个用户信息的格式是{id}-{personName}-{note},而控制器的参数是Person对象。这里需要一个从String转换为Person的转换器。

package com.lay.mvc.converter;

import com.lay.mvc.entity.Person;
import org.springframework.core.convert.converter.Converter;

/**
 * @Description:自定义字符串用户转换器
 * @Author: lay
 * @Date: Created in 16:26 2018/11/13
 * @Modified By:IntelliJ IDEA
 */
@Component
public class StringToPersonConverter implements Converter<String, Person> {
    //转换方法
    @Override
    public Person convert(String s) {
        Person person=new Person();
        String[] strArr=s.split("-");
        Long id=Long.parseLong(strArr[0]);
        String personName=strArr[1];
        String note=strArr[2];
        person.setId(id);
        person.setPersonName(personName);
        person.setNote(note);
        return person;
    }
}

这里类标注了注解@Component,并且实现了Converter接口,这样Spring就会将这个类扫描并且装配到IoC容器中。

控制器验证

    /**
     *
     * @Description: 测试转换器 
     * @param: [person]
     * @return: com.lay.mvc.entity.Person
     * @auther: lay
     * @date: 16:37 2018/11/13 
     */
    @GetMapping("/converter")
    @ResponseBody
    public Person getPersonByConverter(Person person){
        return person;
    }

测试http://localhost:8080/my/converter?person=1-xiaohuahua-beautiful

GenericConverter集合和数组转换

GenericConverter是数组转换器。因为SpringMVC自身提供了一些数组转换器,需要自定义的并不多,所以这里只介绍SpringMVC自定义的数组转换器。

假设需要同时新增多个用户,这样便需要传递一个用户列表(List)给控制器。此时SpringMVC会使用StringToCellectionConverter转换它,这个类实现了GenericConverter接口,并且是SpringMVC内部已经注册的数据转换器。它首先会把字符串用逗号分隔称为一个个的子字符串,然后根据原类型为String,目标类型泛型为Person类,找到对应的Converter进行转换,将字符串转换为Person对象。

    /**
     *
     * @Description: 测试数组转换器
     * @param: List
     * @return: java.util.List<com.lay.mvc.entity.Person>
     * @auther: lay
     * @date: 16:47 2018/11/13
     */
    @GetMapping("/converterList")
    @ResponseBody
    public List<Person> personList(List<Person> personList){
        return personList;
    }

访问:http://localhost:8080/my/converterList?personList=1-xiaohuahua-beautiful,2-xiaoming-shuaige,3-daxiong-jinshi

这里参数使用了一个个逗号分隔,StringToCollectionConverter在处理时就通过逗号分隔,然后通过之前自定义的转换器StringToPerson将其变为用户对象,在组成一个列表List传递给控制器。

猜你喜欢

转载自blog.csdn.net/Sadlay/article/details/84034311