springmvc转换器converter的使用

1:写在前面

页面传递的参数都是string,而在控制器中接收参数类型是不确定的,对于基础数据类型,springmvc已经提供了类型转换器,对于不支持的目标类型,例如日期类型,自定义的对象类型,则可以通过实现接口org.springframework.core.convert.converter.Converter接口的方法来实现,该接口定义如下:

@FunctionalInterface
public interface Converter<S, T> {
    
    
	@Nullable
	T convert(S source);
}

其中的泛型<S, T>中的S代表的是页面参数的类型,一般是String,T代表控制器中接收参数的类型,方法convert(source)就是我们要实现的从S转换为T的方法。接下来我们实现一个通过字符串向自定义对象转换的小栗子。

2:定义接收参数对象

public class GoodsModel {
    
    
	private String goodsname;
	private double goodsprice;
	private int goodsnumber;
	...getter and setter...
}

3:定义类型转换器

我们假定传递参数为英文逗号分割的字符串,通过字符串分割后可以获取构建GoodModel对象需要的值,则源码如下:

public class MyGoodsModelConverter implements Converter<String, GoodsModel> {
    
    

	public MyGoodsModelConverter() {
    
    
		System.out.println("MyGoodsModelConverter no args constructor invoked!!!");
	}
	@Override
	public GoodsModel convert(String source) {
    
    
		String logHead = "MyGoodsModelConverter_convert";
		System.out.println(logHead + " begin, source is: " + source);
		// 创建一个Goods实例
		GoodsModel goods = new GoodsModel();
		// 以英文逗号,分隔
		String stringvalues[] = source.split(",");
		if (stringvalues != null && stringvalues.length == 3) {
    
    
			// 为Goods实例赋值
			goods.setGoodsname(stringvalues[0]);
			goods.setGoodsprice(Double.parseDouble(stringvalues[1]));
			goods.setGoodsnumber(Integer.parseInt(stringvalues[2]));
			System.out.println(logHead + " end, goods is: " + goods);
			return goods;
		} else {
    
    
			throw new IllegalArgumentException(String.format(
					"类型转换失败, 需要格式'apple, 10.58,200 ',但格式是[% s ] ", source));
		}

	}
}

4:注册类型转换器

从String自动转换为dongshi.converter.GoodsModel的转换器需要注意这里的转换器是属于MVC范畴的,所以一定要配置在springMVC的配置文件,即{dispatcher-servlet-name}-spring.xml文件中,如

<servlet>
  	<servlet-name>letsGO</servlet-name>
  	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  	...
  	<!-- 值越小优先级越高 -->
  	<load-on-startup>1</load-on-startup>
</servlet>

则需要配置的配置文件是letsGO-servlet.xml,配置方式如下:

<bean id="myConversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
	<property name="converters">
		<set>
			<bean class="dongshi.converter.MyGoodsModelConverter"/>
		</set>
	</property>
</bean>
<mvc:annotation-driven conversion-service="myConversionService">
	...消息转换等配置,和自定义转换器配置没有关系...
</mvc:annotation-driven>

5:controller

@RequestMapping("/mytest")
@Controller
public class MyTestController {
    
    

	@RequestMapping("/testConverter")
	@ResponseBody
	public GoodsModel testConverter(@RequestParam("goods") GoodsModel testConverter) {
    
    
		System.out.println(testConverter);
		return testConverter;
	}
}

6:测试

分别在转换器的return goods;和controler的return testConverter;打断点测试,通过curlcurl http://localhost:8080/Gradle___org_springframework___dongsir_testspringmvc_5_1_18_BUILD_SNAPSHOT_war__exploded_/mytest/testConverter?goods=3,4,5,转换器如下图:
在这里插入图片描述
继续走代码到controller断点,如下图:
在这里插入图片描述
执行后返回:
{"goodsname":"3","goodsprice":4.0,"goodsnumber":5}

7:springboot环境

如果是在springboot环境中,则只需要定义转换器为spring的bean就可以了,springboot会自动从容器中扫描并通过DefaultFormattingConversionService类完成注册,其中,自动注册部分源码如下:

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#addFormatters
@Override
public void addFormatters(FormatterRegistry registry) {
    
    
	ApplicationConversionService.addBeans(registry, this.beanFactory);
}
org.springframework.boot.convert.ApplicationConversionService#addBeans
public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {
    
    
    Set<Object> beans =new LinkedHashSet();
    
    // 注册自定义的GenericConverter
    beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());
    // 注册自定义Converter(本例重点)
    beans.addAll(beanFactory.getBeansOfType(Converter.class).values());
    // 忽略
    beans.addAll(beanFactory.getBeansOfType(Printer.class).values());
    // 忽略
    beans.addAll(beanFactory.getBeansOfType(Parser.class).values());
    ...

}

下面我们开始实例。

7.1:定义controller

@RestController
public class HelloWorldController {
    
    

    @RequestMapping("/testSpringBootConverter")
    @ResponseBody
    public GoodsModel testSpringBootConverter(@RequestParam("springboot-goods") GoodsModel testConverter) {
    
    
        System.out.println(testConverter);
        return testConverter;
    }
}

不注册自定义转换器访问测试:
在这里插入图片描述

7.2:定义转换器

注意添加@Component注册到spring的容器中,成为spring的bean:


public class MySpringBootGoodsModelConverter implements Converter<String, GoodsModel> {
    
    

	public MySpringBootGoodsModelConverter() {
    
    
		System.out.println("MyGoodsModelConverter no args constructor invoked!!!");
	}
	@Override
	public GoodsModel convert(String source) {
    
    
		String logHead = "MyGoodsModelConverter_convert";
		System.out.println(logHead + " begin, source is: " + source);
		// 创建一个Goods实例
		GoodsModel goods = new GoodsModel();
		// 以英文逗号,分隔
		String stringvalues[] = source.split(",");
		if (stringvalues != null && stringvalues.length == 3) {
    
    
			// 为Goods实例赋值
			goods.setGoodsname(stringvalues[0]);
			goods.setGoodsprice(Double.parseDouble(stringvalues[1]));
			goods.setGoodsnumber(Integer.parseInt(stringvalues[2]));
			System.out.println(logHead + " end, goods is: " + goods);
			return goods;
		} else {
    
    
			throw new IllegalArgumentException(String.format(
					"类型转换失败, 需要格式'apple, 10.58,200 ',但格式是[% s ] ", source));
		}

	}
}

这样就可以了,springboot就会自动完成注册了,启动应用时debug如下:
在这里插入图片描述
debug位置org.springframework.boot.convert.ApplicationConversionService#addBeans

7.3:访问测试

在这里插入图片描述

8:GenericConverter

前面的Converter是一对一转化器接口,即转换的源和目标都是单一的对象,GenericConverter可以认为是对Converter的增强,支持一对多,源码如下:

public interface GenericConverter {
    
    
	@Nullable
	Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

即控制器的参数可以是List类型的,在spring中已经提供了StringToCollectionConverter来默认提供String向集合转换,源码如下:

org.springframework.core.convert.support.StringToCollectionConverter#convert
@Override
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
    
    
	// 强转http中的字符串
	String string = (String) source;
	// 按照英文逗号分割为字符串数组(重点!!!)
	String[] fields = StringUtils.commaDelimitedListToStringArray(string);
	// 获取http参数的类型描述器,这里一般都是字符串
	TypeDescriptor elementDesc = targetType.getElementTypeDescriptor();
	Collection<Object> target = CollectionFactory.createCollection(targetType.getType(),
			(elementDesc != null ? elementDesc.getType() : null), fields.length);
	// 一般不会进if,所以只看else逻辑
	if (elementDesc == null) {
    
    
		for (String field : fields) {
    
    
			target.add(field.trim());
		}
	}
	else {
    
    
		// 遍历每个字段值,调用convertionService,实际上就是GenericConversionService方法,该方法内部会通过
		// sourceType+targetType(elementDesc)组合的方式来寻找可进行转换的转换器进行转换,转换后添加到最终的结果集中
		for (String field : fields) {
    
    
			Object targetElement = this.conversionService.convert(field.trim(), sourceType, elementDesc);
			target.add(targetElement);
		}
	}
	// 返回结果集
	return target;
}

该转换器是先通过切割字符串的方式获取字符串数组,然后对每个元素,在通过http原始类型+控制器中的元素类型匹配的方式获取对应的转换器再进行转换,因此,前面的例子,我们就可以定义一个List控制器参数,然后http参数通过逗号分割的方式,就可以进行多值传递了。

8.1:定义controller

@RequestMapping("/testSpringBootConverterWithList")
@ResponseBody
public List<GoodsModel> testSpringBootConverter(@RequestParam("springboot-goods-list") List<GoodsModel> testConverterList) {
    
    
    System.out.println(testConverterList);
    return testConverterList;
}

8.2:改造转换器

因为我们前面的转换器也是通过英文逗号分割的,这里和StringToCollectionConverter冲突,所以我们改为使用_分割:

将:
String stringvalues[] = source.split(",");
改为:
String stringvalues[] = source.split("_");

8.3:测试

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/wang0907/article/details/112910875