SpringMvc框架(数据转换和数据格式化)

目录,更新ing,学习Java的点滴记录

  目录放在这里太长了,附目录链接大家可以自由选择查看--------Java学习目录

SpringMvc知识

第一篇---->SpringMvc初识|MVC|三层架构
第二篇---->IoC容器上下文和映射请求上下文
第三篇---->熟悉基本开发流程
第四篇---->接收各类请求参数的方式
第五篇---->获取请求中的Request,Session,Cookie等对象属性
第六篇---->拦截器开发
第七篇---->视图和视图解析器
第八篇---->数据校验
第九篇---->文件上传方式
第十篇---->数据转换和数据格式化

1 数据转换和格式化介绍

  • 在整理的前面几篇中,我们看到了以下几种情况:一,通过一些注解就可以让我们开发的控制器Controller获取到各种类型的参数,做到这一点的核心就在于SpringMvc的消息转换机制;二,SpringMvc执行流程中说到HandlerAdapter去执行处理器,处理器是什么呢?

  • 处理器和我们经常编写的控制器Controller不是同一个概念,处理器是在控制器功能的基础上加上了一层包装.有了这层包装在HTTP请求到达控制器之前它就能对HTTP的各类消息进行处理.

  • 首先当一个请求到达DispatcherServlet的时候,需要找到对应的HandlerMapping,然后根据HandlerMapping去找到对应的HandlerAdapter执行处理器.处理器在调用控制器之前,需要先获取HTTP发送过来的信息,然后将其转变为控制器的各种不同类型的参数,这就是各类注解能够的到丰富类型参数的原因.使用的是Http的消息转换器(HttpMessageConveter)对消息转换,但是这是一个比较原始的转换,它是String类型和文件类型比较简易的转换,它还需要进一步转换才能转换为POJO或者其他丰富的参数类型.在Spring4提供了转换器和格式化器,这样通过注解的信息和参数类型,就能够把HTTP发送过来的各种消息转换称为控制器所需要的各类参数.

  • 处理器处理完了参数的转换之后,就会进行数据校验,第八篇也已经提到过了这一点.完成了上面这些内容,才是真正调用我们自己开发的控制器Controller,将之前转换成功的参数传递进去,这样我们编写的控制器就能够得到丰富的Java类型的支持了,这样控制器中的处理方法就能够完成业务逻辑,最后将结果返回,处理器还会根据返回结果做处理,如果能找到对应处理结果类型的HttpMessageConveter的实现类,就会调用对应实现类的方法对控制器返回结果进行HTTP转换,对返回结果操作的这一步不是必须的,可以转换的前提是能够找到对应的转换器,到这里,处理器的功能就完成了.

  • 再往后就是试图解析和视图解析器相关的流程了,这个过程也是比较复杂的,甚至还可能需要自定义一些特殊转换规则.

  • 下面给出一个SpringMvc消息转换的流程
      SpringMvc框架(数据转换和数据格式化)_第1张图片

  • 对于SpringMvc,我们可以在SpringMvc配置文件中配置一个,这时候,SpringIOC容器就会自定义生成一个关于转换器和格式化器的类实例----FormattingConversionServiceFactoryBean,这样就可以从SpringIOC容器中获取这个对象了."以貌观之"可以看出,它是一个工厂(Factory),那么对于工厂而言,就需要生成它的产品.它的产品主要就是DefaultFormattingConversionService类对象,这个类对象继承了一些类,并实现了很多的接口.下面给出一个继承结构图
      SpringMvc框架(数据转换和数据格式化)_第2张图片

  • 在上图中可以看到一个顶层接口---ConversionService接口,DefaultFormattingConversionService还实现了转换器的注册机(ConverterRegistry)和格式化器注册机(FormatterRegistry)两个接口,换句话说,可以在它这里注册转换器或者格式化器.事实上,SpringMvc已经注册了一些常用的转换器和格式化器.在SpringMvc流程中涉及到处理器,而且知道处理器并非控制器,但是它包含了控制器的逻辑,在运行控制器之前,它就会使用这些转换器把Http的数据转换成对应的类型,用来填充控制器的参数,这就是为什么可以在控制器保持一定规则的前提下就可以获取到参数的原因.当控制器返回数据模型后,再通过SpringMvc后面对应的流程渲染数据,然后显示给客户端.

  • 补充一下,在Java类型转换之前,在SpringMvc中,为了应对Http请求,它还定义了HttpMessageConveter,它是一个总体的接口,通过它可以读入HTTP的请求内容.也就是说,在读取HTTP请求的参数和内容的时候会先用HttpMessageConveter读取,进行简单的类型转换,主要是字符串,然后就可以使用各类转换器进行转换了,在逻辑业务处理完成后,还可以通过它把数据转换为响应给用户的内容.

  • 在Spring中,转换器分为两大类,一类是Converter接口所定义的,另外一种是GenericConverter,他们都可以使用注册机注册.二者都是SpringCore项目,而非SpringMvc项目,作用范围是Java内部各种类型之间的转换.

2 数据转换

1.1 HttpMessageConveter和Json消息转换器

1.1.1 HttpMessageConveter

  • HttpMessageConveter是定义从HTTP接收请求信息和应答给用户的,HttpMessageConveter接口的定义如下:
      SpringMvc框架(数据转换和数据格式化)_第3张图片
  • 接口中定义的方法基本上都不需要我们实现,因为SpringMvc中已经提供了大量的实现了.所以可以说HttpMessageConveter是一个顶层或广泛的设计,同时虽然它的实现类也有很多,但是用的比较多的实现类还是很少的,比如MappingJackson2HttpMessageConveter,这是一个关于Json消息的转换类,通过它可以把控制器返回的结果再处理器内转换为json数据.

1.1.2 MappingJackson2HttpMessageConverter

  • 继承结构图
      SpringMvc框架(数据转换和数据格式化)_第4张图片

  • xml方式进行配置
      SpringMvc框架(数据转换和数据格式化)_第5张图片

  • 使用方式
      当我们使用@ResponseBody的时候,SpringMvc就会将应答类型转换为json,然后就可以通过响应类型找到配置的MappingJackson2HttpMessageConveter进行转换了.

  • 编写一个控制器演示
      SpringMvc框架(数据转换和数据格式化)_第6张图片

  SpringMvc框架(数据转换和数据格式化)_第7张图片

  • 注解@ResponseBody将标记SpringMvc,将响应结果转变为json,这样在控制器返回结果后,他会通过类型判断找到MappingJackson2HttpMessageConveter实例,在处理器内转变为json,从而满足json的转换要求.
  • 此外,对于一些更加精细的转换,是以Spring core项目提供的Converter和GenericConverter,以及Spring Context包的Formatter进行转换的.

1.2 一对一转换器(Converter)

1.2.1 介绍

  • Converter是一种一对一的转换器,首先看一下Converter的源码
      SpringMvc框架(数据转换和数据格式化)_第8张图片

  • 这个接口非常简单,可以通过实现该接口来实现数据的转换,实际上SpringMvc已经默认实现了很多的转换器.

  • 下面列举一些常用的转换器,从名字上就可以看出是进行什么转换的
      SpringMvc框架(数据转换和数据格式化)_第9张图片

  • 通过HttpMessageConveter把Http消息读出后,SpringMvc就开始使用这些转换器来将http的信息,转换为控制器的参数,这就是能在控制器上获得各类参数的原因.大部分情况下,SpringMvc默认提供的功能,已经能够满足大部分需求了,但是有时候我们还需要自定义转换规则,步骤也很方便,只需要实现Converter接口,然后注册给对应的转换服务类就可以了.

1.2.2 自定义转换器

  • 需求:
      存在一个User类,属性有id,name,age,正常情况下是在表单中分别输入三个属性,然后在处理器方法中直接由SpringMvc接收一个User对象进行自动封装,现在我们要前端输入一个id-name-age的字符串,然后通过自定义转换器来转成User对象

  • 创建自定义转换器
      SpringMvc框架(数据转换和数据格式化)_第10张图片

  • 现在我们只是定义好了一个转换类,SpringMvc此时还并不会帮助我们对User对象进行转换,因为还需要注册.ConversionService 是 Spring 类型转换体系的核心接口。可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC 容器中定义一个 ConversionService. Spring 将自动识别出 IOC 容器中的 ConversionService,并在 Bean 属性配置及 SpringMVC 处理方法入参绑定等场合使用它进行数据的转换可通过 ConversionServiceFactoryBean 的 converters 属性 注册自定义的类型转换器

  • 注册转换器方式:通过xml方式,这里需要在mvc:annotation-driven/元素上指定要进行转换的服务类,然后通过配置conversion-service属性来加载对应的转换器
      SpringMvc框架(数据转换和数据格式化)_第11张图片

  • 编写一个表单,输入对应格式字符串,在处理器方法中转换为User对象
      SpringMvc框架(数据转换和数据格式化)_第12张图片

  • 编写一个处理方法
      SpringMvc框架(数据转换和数据格式化)_第13张图片

  • 启动测试
      SpringMvc框架(数据转换和数据格式化)_第14张图片

  SpringMvc框架(数据转换和数据格式化)_第15张图片

1.3 数组和集合转换器(GenericConverter)

1.3.1 GenericConverter介绍

  • 一对一转换器存在的弊端是只能从一种类型转换成另一种类型,不能进行一对多转换,比如把String转换成List,String[]或者List,这些需求在一对一转换器中都无法满足.
  • SpringCore项目中在Spring3.0加入了另一个转换器GenericConverter,它能够满足数组和集合转换的要求

1.3.2 GenericConverter接口

  • 接口源码
public interface GenericConverter {

	//返回这个转换器可以转换的原类型和目标类型
	Set<ConvertiblePair> getConvertibleTypes();

	//将源类型转换成目标类型的转换方法
	Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);


	//可以转换的匹配的类
	final class ConvertiblePair {
		//源类型
		private final Class<?> sourceType;
		//目标类型
		private final Class<?> targetType;

		//创建一个新的源类型到目标类型的匹配对
		public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
			Assert.notNull(sourceType, "Source type must not be null");
			Assert.notNull(targetType, "Target type must not be null");
			this.sourceType = sourceType;
			this.targetType = targetType;
		}
		//获取源类型
		public Class<?> getSourceType() {
			return this.sourceType;
		}
		//获取目标类型
		public Class<?> getTargetType() {
			return this.targetType;
		}

		@Override
		public boolean equals(Object other) {
			if (this == other) {
				return true;
			}
			if (other == null || other.getClass() != ConvertiblePair.class) {
				return false;
			}
			ConvertiblePair otherPair = (ConvertiblePair) other;
			return (this.sourceType == otherPair.sourceType && this.targetType == otherPair.targetType);
		}

		@Override
		public int hashCode() {
			return (this.sourceType.hashCode() * 31 + this.targetType.hashCode());
		}

		@Override
		public String toString() {
			return (this.sourceType.getName() + " -> " + this.targetType.getName());
		}
	}

}
  • 这个一个底层接口,此外针对类型匹配的判断,Spring3.2还提供了ConditionalConverter接口

1.3.3 ConditionalConverter接口

  • 接口代码
      SpringMvc框架(数据转换和数据格式化)_第16张图片

  • “见名知意”—带有条件的转换器,只有当它定义的matches方法返回true时,才会进行转换.为了与原有接口GenericConverter,在新的接口出现了ConditionalGenericConverter,它也是最常用的集合转换接口.它继承了两个接口的方法,可以做到判断和转换的作用.

1.3.4 ConditionalGenericConverter(最常用)

  • 代码
      SpringMvc框架(数据转换和数据格式化)_第17张图片

  • 在该接口的基础上,SpringCore开发了很多实现类,这些实现类都会注册到ConversionService对象里面,通过ConditionalConverter的matcher方法进行匹配.如果可以匹配,则会调用convert方法进行转换,它能够提供各种对数组和集合的转换.

  • 常见实现类结构图
      SpringMvc框架(数据转换和数据格式化)_第18张图片

  • 下面找一个SpringMvc默认提供的实现类来看下代码StringToArray

final class StringToArrayConverter implements ConditionalGenericConverter {
	//转换服务类
	private final ConversionService conversionService;
	//构造方法,需要传入一个转换服务类对象
	public StringToArrayConverter(ConversionService conversionService) {
		this.conversionService = conversionService;
	}
	//可以进行转换的类型
	@Override
	public Set<ConvertiblePair> getConvertibleTypes() {
		return Collections.singleton(new ConvertiblePair(String.class, Object[].class));
	}
	//判断方法,查看是否支持Converter转换
	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
		return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor());
	}
	//转换方法
	@Override
	public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
		if (source == null) {
			return null;
		}
		//源数据
		String string = (String) source;
		//逗号分隔字符串
		String[] fields = StringUtils.commaDelimitedListToStringArray(string);
		//目标
		Object target = Array.newInstance(targetType.getElementTypeDescriptor().getType(), fields.length);
		//遍历数组
		for (int i = 0; i < fields.length; i++) {
			String sourceElement = fields[i];
			//执行类型转换(可以使用SpringCore提供的Converter或者自定义)
			Object targetElement = this.conversionService.convert(sourceElement.trim(), sourceType, targetType.getElementTypeDescriptor());
			Array.set(target, i, targetElement);
		}
		return target;
	}
}
  • 大部分情况下,都不需要自定义ConditionalGenericConverter实现类,默认提供的已经够用了

3 格式化

3.1 场景

  • 开发中很多用到的数据都需要进行格式化,比如说金额,日期等.对于日期来说传递的日期格式为yyyy-MM-dd还是yyyy-MM-dd hh:ss:mm等,这些都需要格式化;金额同样,是$10000或者1,234,456.8等,一定环境下,都需要将这些内容转成固定格式

3.2 使用格式化器(Formatter)

  • SpringContext提供了进行数据格式化的Formatter,它需要实现一个接口Formatter,而Formatter又扩展了两个接口Printer和Parser.
      SpringMvc框架(数据转换和数据格式化)_第19张图片

  • 通过print方法可以将结果按照一定的格式输出字符串.通过parse方法能够将满足一定格式的字符串转换为对象.这样就可以对数据进行格式化了,内部实际上是委托给Converter机制去实现的.

  • 平时用的最多的两个注解就是@DateTimeFormat和@NumberFormat

  • 日期格式化器在SpringMvc中是在系统启动时完成初始化,同时还提供了@DateTimeFormat来进行日期格式的定义,采用注解@NumberFormat进行数字格式转换

3.3 @NumberFormat和@DateTimeFormat

3.3.1 @NumberFormat

  • @NumberFormat 可对类似数字类型的属性进行标 注,它拥有两个互斥的属性
      style:类型为 NumberFormat.Style。用于指定样式类 型,包括三种:Style.NUMBER(正常数字类型)、 Style.CURRENCY(货币类型)、 Style.PERCENT( 百分数类型)
      ;pattern:类型为 String,自定义样式, 如patter="#,###";

3.3.2 @DateTimeFormat

  • @DateTimeFormat 注解可对
    java.util.Date、java.util.Calendar、java.long.Long 时间 类型进行标注:
      pattern 属性:类型为字符串。指定解析/格式化字段数据的模式, 如:”yyyy-MM-dd hh:mm:ss”
      iso 属性:类型为 DateTimeFormat.ISO。指定解析/格式化字段数据 的ISO模式,包括四种:ISO.NONE(不使用) – 默
    认、ISO.DATE(yyyy-MM-dd) 、ISO.TIME(hh:mm:ss.SSSZ)、
    ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)
      style 属性:字符串类型。通过样式指定日期时间的格式,由两位字 符组成,第一位表示日期的格式,第二位表示时间的格式:S:短日 期/时间格式、M:中日期/时间格式、L:长日期/时间格式、F:完整 日期/时间格式、-:忽略日期或时间格式

3.4 案例

  • 在springmvc.xml中配置mvc:annotation-driven/驱动,会帮我们加载关于格式化相关的实例
      在这里插入图片描述

  • 创建一个表单,需要输入日期和金额,然后在后端处理器方法中进行接收并输出,这里以日期格式为yyyy-MM-dd和金额格式(逗号和小数点形式)为例
      SpringMvc框架(数据转换和数据格式化)_第20张图片

  • 编写控制器中处理方法,可以思考下,下面的方法是否会正常输出结果
      SpringMvc框架(数据转换和数据格式化)_第21张图片

  • 启动测试
      SpringMvc框架(数据转换和数据格式化)_第22张图片
      SpringMvc框架(数据转换和数据格式化)_第23张图片

  • 可以看到控制台已经报错了,默认情况下是无法进行直接转换的,现在我们使用注解来对参数进行标注
      SpringMvc框架(数据转换和数据格式化)_第24张图片

  • 重新启动测试
      SpringMvc框架(数据转换和数据格式化)_第25张图片
      SpringMvc框架(数据转换和数据格式化)_第26张图片

  • 通过注解@DateTimeFormat和@NumberFormat,然后通过pattern的方式,处理器就可以将参数通过对应的格式化器进行转换,然后传递给控制器了,由于处理器方法形参和表单属性的name一致,可以自动匹配.

  • 可以看到控制台中成功输出了数据,格式化成功,但是我们平时用的比较多的是使用处理器方法接收一个实体类,然后对实体类进行操作,下面修改一下表单,并且创建一个含有日期和金额的实体类,并再次测试

  • 创建实体类
      SpringMvc框架(数据转换和数据格式化)_第27张图片

  • 修改表单
      SpringMvc框架(数据转换和数据格式化)_第28张图片

  • 新增处理器方法
      SpringMvc框架(数据转换和数据格式化)_第29张图片

  • 启动测试
      SpringMvc框架(数据转换和数据格式化)_第30张图片

  

  • 在这里插入图片描述
  • 我们只需要给对应实体类的属性上添加注解,这样Spring的处理器就会根据注解使用对应的转换器,按照配置进行转换,这时候控制器方法的参数可以定义为一个实体类.

你可能感兴趣的:(SpringMvc)