预备知识

对于SpringMvc,在xml配置<mvc:annotation-drivern>或是@EnableWebMvc时,Spring IoC容器会自动生成一个关于转换器和格式化器的类实例——FormattingConversionServiceFactoryBean,它是一个工厂,通过它可以获得DefaultFormattingConversionService类对象,它实现了转换器与格式化器的注册机(即ConverterRegistry与FormatterRegistry),我们可以通过它注册转换器与格式化器,SpringMvc默认已经注册了一些转换器与处理器

 

HttpMessageConverter

这个接口进行HTTP参数与java类型的转换,是HTTP参数与String类型、文件类型的简易转换,通过转换器或是格式化器的进一步转换,才能变为丰富的参数类型或是POJO,该接口定义如下:
 

public interface HttpMessageConverter<T> {

    //表示转换器是否能够将对应的Http类型转换为clazz类型,mediaType为Http类型
	boolean canRead(Class<?> clazz, MediaType mediaType);

    //表示转换器是否可以将clazz类型转换为H对应的ttp类型
	boolean canWrite(Class<?> clazz, MediaType mediaType);

    //返回转换器支持转换的Http类型
	List<MediaType> getSupportedMediaTypes();

    //从http请求消息中读取信息,将其转换clazz类型,HttpInputMessage是Http请求消息
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

    //将T类型的对象写到响应流中,同时指定相应的媒体类型为 contentType,HttpOutputMessage是Http应答消息
	void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

只有canRead函数返回True,才可以执行read函数,只有canWrite函数返回True,才可以执行write函数,流程如下图片来源

SpringMvc的数据转换、格式化、控制器通知-LMLPHP

HttpMessageConverter比较重要的函数源码如下:

public interface HttpInputMessage extends HttpMessage {
    /**
    * Return the body of the message as an input stream.
    * @return the input stream body (never {@code null})
    * @throws IOException in case of I/O Errors
    */
    //获取body中的数据作为输入流
    InputStream getBody() throws IOException;
}

public interface HttpOutputMessage extends HttpMessage {
    /**
    * Return the body of the message as an output stream.
    * @return the output stream body (never {@code null})
    * @throws IOException in case of I/O Errors
    */
    //将数据转为输出流
    OutputStream getBody() throws IOException;
}

InputStream与OutputStream是servlet提供的接口,这两个流操作是与http交互的,意味着HttpMessageConverter具有读取和写入http报文的能力,而HandleAdapter就是通过HttpMessageConveter、格式化器、转换器实现http请求参数与控制器形参的相互转换的,也就是适配(个人理解),HttpMessageConverter负责读取Http请求,将http请求简单转换为java类型,除此之外,还负责将数据写入到http应答中

 

HttpMessageConveter只负责简单转换(http类型与String、文件类型的转换),对于一些细节上的转化,以Spring Core包提供的Conveter和GenericConveter,以及Spring Context包的Formatter实现

 

Converter(一对一转换)

接口代码:

public interface Converter<S,T>{

    T convert(S source);

}

SpringMvc实现了许多转换器,但是有时候我们需要自定义实现转换器,此时只要实现Converter接口,并进行注册即可(通过预备知识中讲的FormattingConversionServiceFactoryBean类,实际是注册到DefaultFormattingConversionService类中),下面通过一个例子进行讲解,代码来自《java EE 互联网轻量级框架整合开发》(不推荐新手看)

 

  1. 自定义转换器
    package com.ssm.StringToRoleConverter
    
    public class StringToRoleConverter implements Converter<String,Role>{
    
        @Override
        public Role convert(String str){
            //空串
            if(StringUtild.isEmpty(str)){
                return null;
            }
    
            //不包含指定字符
            if(str.indexOf("-")==-1){
                return null;
            }
    
            String[] arr=str.split("-");
            //字符串长度不对
            if(arr.length!=3){
                return null;
            }
    
            //POJO就不敲了,看set函数就知道有哪些属性了
            Role role=new role();
            role.setId(Long.parseLong(arr[0]));
            role.setRoleName(arr[1]);
            role.setNote(arr[2]);
            return role;
        }
    }

     

  2. 注册自定义转换器(java配置)
    private List<Converter> myConverter=null;
    
    @Autowired
    private FormattingConversionServiceFactoryBean fcsfb=null;
    
    @Bean(name="myConverter")
    public List<Converter> initMyConverter(){
        if(myConverter==null){
            myConverter=new ArrayList<Converter>();
        }
        //自定义的字符串和角色转换器
        Converter roleConverter=new StringToRoleConverter();
        myConverter.add(roleConverter);
        //注册转换器
        fcsfb.getObject().addConverter(roleConverter);
        return myConverter;
    
    
    }

     

  3. 相比于java配置,我更喜欢xml,因为更加简洁
    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <bean class="com.ssm.StringToRoleConverter"/>
            </list>
        </property>
    </bean>

     

  4. http请求样例:/update?role=1-update_role_name_1-update_note_1,运用上面的转换器,即可将请求参数转换为Role对象

 

其实还有一个数组和集合转换器GenericConverter,它在上面的一对一转换器Converter的基础上进一步将多个数据转换为数组和集合,Spring Core项目实现的GenericConverter基本够用,如果在以后某个项目需要自定义时在回来写例子

 

格式化器

使用场景:有时我们需要把字符串按照一定的格式转换为日期或者是金额,为了支持这些场景,Spring Context提供了相关的Formatter

 

Formatter接口扩展了两个接口Printer以及Parser,这两者定义如下:

interface Printer<T>{

    String print(T object,Locale locale);

}

interface Parse<T>{

    T parse(String text,Locale locale);

}

通过print方法能将结果按照一定的格式输出字符串,通过parse方法能够将满足一定格式的字符串转换为对象,它的内部实际是委托给Converter机制去实现的,需要自定义的场景不多,这里介绍两个注解

 

@DateTimeFormat:进行日期格式的转换,转换为java对象Date

@NumberFormat:进行数字的格式转换

@Controller
@RequestMapping("/convert")
public class ConvertController{

    @RequestMapping("/format")
    public ModelAndView format(@DateTimeFormat(ios=ISO.DATE)Date date,@NumberFormat(pattern="#,###.##")Double amount){
        ..........

    }
}

请求样例:http://localhost:8080/convert?date=2017-06-01&amount=123,000.00

http请求参数名与形参名字对应后,进行转换器转换

 

控制器通知

与SpringAOP一样,SpringMVC也可以为控制器加入通知,它主要涉及4个注解:

  • @ControllerAdvice:作用于类,用以标识全局性的控制的拦截器,将应用于对应的控制器
  • @InitBinder:允许在构造控制器参数的时候,加入一定的自定义控制
  • @ExceptionHandler:当控制器发生异常时,就会跳转到该方法上
  • @ModelAttribute:先于控制器方法执行,当标注方法返回对象时,会保存到数据模型中,并传递给拦截的控制器

实例:

//对com.ssm.advice控制器进行拦截
@ControllerAdvice(basePackages={"com.ssm.advice"})
public class Demo{

    //针对日期类型的格式化,与@DataTimeFormat作用一样
    @InitBinder
    public void initBinder(WebDataBinder binder){
        binder.registerCustomEditor(Date.class,new CustomDateEditor(new SimpleDataFormat("yyyy-MM-dd"),false));
    }

    //model中添加的数据可以在被拦截控制器中使用
    @ModelAttribute
    public void populateModel(Model model){
        model.addAttribute("projectName","1234");
    }

    //被拦截控制器出现Exception异常时,会调用该方法,该方法返回会返回exception视图
    @ExceptionHandler(Exception.class)
    public String exception(){
        return "exception"
    }

}

也可以在控制器当中使用@InitBinder、@ExceptionHandler、@ModelAttribute,此时标注的方法只对当前控制器有效,控制器形参可以通过@ModelAttribute("参数名")获得提前保存在数据模型中的数据,拿上面的例子来说,如果一个控制器方法想获得projectName参数的值:

@RequestMapping(value="getProjectName")
public String getProjectName(@ModelAttribute("projectName") String name){

    .......

}

最终name的值将为1234

 

其他

至此,自己对于SpringMVC的工作流程有了一定了解,HttpMessageConverter、转换器、格式化器负责进行类型转换,现在还有一个疑问,SpringMvc是如何进行请求参数绑定的,这个疑问接下来会查阅资料后总结

 

10-07 10:26