你的位置:首页 > Java教程

[Java教程]请求参数到表述层的类型转换——SpringMVC


一、不论是SpringMVC 的 JSR-303 数据校验还是自定义类型转换器,都需要配置 <mvc:annotation-driven/>,而添加这个配置后,会自动注册RequestMappingHandlerMapping、

RequestMappingHandlerAdapter 与 ExceptionHandlerExceptionResolver  三个bean。在没添加前,正常的请求都是由 AnnotationMethodHandlerAdapter 进行处理的。我之前

的几篇文章都是以 AnnotationMethodHandlerAdapter 的 handler() 进行分析。在添加后,正常请求是由 RequestMappingHandlerAdapter 的 handler() 进行处理的。它们的

逻辑是不同的。这里就不对源码进行具体说明。

二、Spring 引入了 core.convert 包,提供了一个通用的类型转换系统。

1.在 core.convert.support 包下提供了许多默认的类型转化器,为类型转换提供和极大的方便。

2.自定义类型转换器

(1)实现 Converter 接口

package org.springframework.core.convert.converter;public interface Converter<S, T> { T convert(S source);}

创建自定义类型转换器,只需要实现该接口,参数 S 表示需要转换的类型,T 表示转换后的类型。对于每次调用 convert() 方法,必须保证参数 source 不能为 null。

如果转换失败,可能会抛出异常。特别的,一个 IllegalArgumentException 会被抛出来指明无效的 source 值。

请注意,需要保证转换器是线程安全的。

e1: 需要将 person=name:lily|age:23 转换为对应的 person 对象

 

自定义的类型转换器:

/** * @author solverpeng * @create 2016-08-15-14:50 */public class PersonConversionService implements Converter<String, Person>{  Person person = null;  @Override  public Person convert(String s) {    try {      if(s != null && s.length() > 0) {        String[] strings = s.split("\\|");        person = Person.class.newInstance();        for(String str : strings) {          String[] properties = str.split(":");          Field field = Person.class.getDeclaredField(properties[0]);          field.setAccessible(true);          Class<?> type = field.getType();          if(type.equals(Integer.class)) {            field.set(person, Integer.parseInt(properties[1]));            continue;          }          field.set(person, properties[1]);        }      }    } catch(InstantiationException | IllegalAccessException | NoSuchFieldException e) {      e.printStackTrace();    }    return person;  }}

需要在 SpringMVC Config 文件中添加的配置如下:

<bean id="customizeConversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">  <property name="converters">    <set>      <bean class="com.nucsoft.springmvc.converter.PersonConversionService"/>    </set>  </property></bean><mvc:annotation-driven conversion-service="customizeConversionService"/>

请求:

<a href="testConverter?person=name:lily|age:23">test converter</a>

目标 handler 方法:

@RequestMapping("/testConverter")public String testSpring2Person(Person person) {  System.out.println("persont:" + person);  return "success";}

控制台输出:

persont:Person{name='lily', age=23}

说明:这里不对 <mvc:annotation-driven /> 进行说明,以后会写文章介绍。

 

在介绍参数获取问题时,对 @RequestParam 的“如果方法的入参类型是一个 Map,不包含泛型类型,并且请求参数名称是被指定” 这种情况没有进行详细说明,这里通过一个例子说明。

参见:http://www.cnblogs.com/solverpeng/p/5733310.html

e2:将 String 转换为 Map,将 params=a:1|b:2 转换为 Map 类型。

创建自定义的类型转换器:

/** * @author solverpeng * @create 2016-08-15-15:40 */public class String2MapConversionService implements Converter<String, Map<String, Object>>{  @Override  public Map<String, Object> convert(String s) {    Map<String, Object> map = new HashMap<>();    if(s != null & s.length() > 0) {      String[] strings = s.split("\\|");      for(String string : strings) {        String[] split = string.split(":");        map.put(split[0], split[1]);      }    }    return map;  }}

SpringMVC Config 文件:

<bean id="customizeConversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">  <property name="converters">    <set>      <bean class="com.nucsoft.springmvc.converter.PersonConversionService"/>      <bean class="com.nucsoft.springmvc.converter.String2MapConversionService"/>    </set>  </property></bean><mvc:annotation-driven conversion-service="customizeConversionService"/>

请求:

<a href="testConverter2?params=a:1|b:2">test converter2</a>

目标 handler 方法:

@RequestMapping("/testConverter2")public String testString2Map(@RequestParam("params") Map map) {  System.out.println(map);  return "success";}

控制台输出:

{b=2, a=1}

(2)ConverterFactory

package org.springframework.core.convert.converter;public interface ConverterFactory<S, R> {   <T extends R> Converter<S, T> getConverter(Class<T> targetType);}

如果希望将一种类型转换为另一种类型及其子类对象时,那么使用这个接口。

e:num=23&num2=33.33 将 num 转换为对应的 Integer 类型,将 num2 转换为对应的 Double 类型。

类型转换器参见:org.springframework.core.convert.support.StringToNumberConverterFactory

final class StringToNumberConverterFactory implements ConverterFactory<String, Number> {  @Override  public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {    return new StringToNumber<T>(targetType);  }  private static final class StringToNumber<T extends Number> implements Converter<String, T> {    private final Class<T> targetType;    public StringToNumber(Class<T> targetType) {      this.targetType = targetType;    }    @Override    public T convert(String source) {      if (source.length() == 0) {        return null;      }      return NumberUtils.parseNumber(source, this.targetType);    }  }}

请求:

<a href="testString2Number?num=23&num2=33.33">test String to Number</a>

目标 handler 方法:

@RequestMapping("/testString2Number")public String testString2Number(@RequestParam("num") Integer num, @RequestParam("num2") Double num2) {  System.out.println("num:" + num);  System.out.println("num2:" + num2);  return "success";}

控制台输出:

num:23
num2:33.33

 

还有一个 GenericConverter ,我没看太明白,不做说明。待以后掌握了再做补充。

三、总结

介绍了 Spring 默认的类型转换器,以及如何自定义类型转换器,通常情况下,通过实现 Convert 接口就能完成大部分转换。没有对原理进行说明,因为使用 RequestMappingHandlerAdapter  后,

它的 handler 方法是另一套逻辑,整个流程现在还没有理太明白。 也没有对调用类型转换器的时机进行说明,在介绍完数据校验后作统一说明。