目录放在这里太长了,附目录链接大家可以自由选择查看--------Java学习目录
第一篇---->SpringMvc初识|MVC|三层架构
第二篇---->IoC容器上下文和映射请求上下文
第三篇---->熟悉基本开发流程
第四篇---->接收各类请求参数的方式
第五篇---->获取请求中的Request,Session,Cookie等对象属性
第六篇---->拦截器开发
第七篇---->视图和视图解析器
第八篇---->数据校验
第九篇---->文件上传方式
第十篇---->数据转换和数据格式化
在整理的前面几篇中,我们看到了以下几种情况:一,通过一些注解就可以让我们开发的控制器Controller获取到各种类型的参数,做到这一点的核心就在于SpringMvc的消息转换机制;二,SpringMvc执行流程中说到HandlerAdapter去执行处理器,处理器是什么呢?
处理器和我们经常编写的控制器Controller不是同一个概念,处理器是在控制器功能的基础上加上了一层包装
.有了这层包装在HTTP请求到达控制器之前它就能对HTTP的各类消息进行处理.
首先当一个请求到达DispatcherServlet的时候,需要找到对应的HandlerMapping,然后根据HandlerMapping去找到对应的HandlerAdapter执行处理器.处理器在调用控制器之前,需要先获取HTTP发送过来的信息,然后将其转变为控制器的各种不同类型的参数,这就是各类注解能够的到丰富类型参数的原因.使用的是Http的消息转换器(HttpMessageConveter)对消息转换,但是这是一个比较原始的转换,它是String类型和文件类型比较简易的转换
,它还需要进一步转换才能转换为POJO或者其他丰富的参数类型.在Spring4提供了转换器和格式化器
,这样通过注解的信息和参数类型,就能够把HTTP发送过来的各种消息转换称为控制器所需要的各类参数.
处理器处理完了参数的转换之后,就会进行数据校验,第八篇也已经提到过了这一点.完成了上面这些内容,才是真正调用我们自己开发的控制器Controller,将之前转换成功的参数传递进去,这样我们编写的控制器就能够得到丰富的Java类型的支持了,这样控制器中的处理方法就能够完成业务逻辑,最后将结果返回,处理器还会根据返回结果做处理,如果能找到对应处理结果类型的HttpMessageConveter的实现类,就会调用对应实现类的方法对控制器返回结果进行HTTP转换,对返回结果操作的这一步不是必须的,可以转换的前提是能够找到对应的转换器
,到这里,处理器的功能就完成了.
再往后就是试图解析和视图解析器相关的流程了,这个过程也是比较复杂的,甚至还可能需要自定义一些特殊转换规则.
对于SpringMvc,我们可以在SpringMvc配置文件中配置一个
,这时候,SpringIOC容器就会自定义生成一个关于转换器和格式化器的类实例----FormattingConversionServiceFactoryBean,这样就可以从SpringIOC容器中获取这个对象了."以貌观之"可以看出,它是一个工厂(Factory),那么对于工厂而言,就需要生成它的产品.它的产品主要就是DefaultFormattingConversionService类对象,这个类对象继承了一些类,并实现了很多的接口.下面给出一个继承结构图
在上图中可以看到一个顶层接口---ConversionService接口
,DefaultFormattingConversionService还实现了转换器的注册机(ConverterRegistry)和格式化器注册机(FormatterRegistry)两个接口,换句话说,可以在它这里注册转换器或者格式化器.事实上,SpringMvc已经注册了一些常用的转换器和格式化器
.在SpringMvc流程中涉及到处理器,而且知道处理器并非控制器,但是它包含了控制器的逻辑,在运行控制器之前,它就会使用这些转换器把Http的数据转换成对应的类型,用来填充控制器的参数,这就是为什么可以在控制器保持一定规则的前提下就可以获取到参数的原因.当控制器返回数据模型后,再通过SpringMvc后面对应的流程渲染数据,然后显示给客户端.
补充一下,在Java类型转换之前,在SpringMvc中,为了应对Http请求,它还定义了HttpMessageConveter,它是一个总体的接口
,通过它可以读入HTTP的请求内容.也就是说,在读取HTTP请求的参数和内容的时候会先用HttpMessageConveter读取
,进行简单的类型转换,主要是字符串,然后就可以使用各类转换器进行转换了,在逻辑业务处理完成后,还可以通过它把数据转换为响应给用户的内容.
在Spring中,转换器分为两大类,一类是Converter接口所定义的,另外一种是GenericConverter,他们都可以使用注册机注册
.二者都是SpringCore项目,而非SpringMvc项目,作用范围是Java内部各种类型之间的转换.
MappingJackson2HttpMessageConveter,这是一个关于Json消息的转换类,通过它可以把控制器返回的结果再处理器内转换为json数据
.使用方式
当我们使用@ResponseBody的时候,SpringMvc就会将应答类型转换为json,然后就可以通过响应类型找到配置的MappingJackson2HttpMessageConveter进行转换了.
这个接口非常简单,可以通过实现该接口来实现数据的转换,实际上SpringMvc已经默认实现了很多的转换器.
通过HttpMessageConveter把Http消息读出后,SpringMvc就开始使用这些转换器来将http的信息,转换为控制器的参数,这就是能在控制器上获得各类参数的原因.大部分情况下,SpringMvc默认提供的功能,已经能够满足大部分需求了,但是有时候我们还需要自定义转换规则,步骤也很方便,只需要实现Converter接口,然后注册给对应的转换服务类就可以了
.
需求:
存在一个User类,属性有id,name,age,正常情况下是在表单中分别输入三个属性,然后在处理器方法中直接由SpringMvc接收一个User对象进行自动封装,现在我们要前端输入一个id-name-age的字符串,然后通过自定义转换器来转成User对象
现在我们只是定义好了一个转换类,SpringMvc此时还并不会帮助我们对User对象进行转换,因为还需要注册
.ConversionService 是 Spring 类型转换体系的核心接口。可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC 容器中定义一个 ConversionService
. Spring 将自动识别出 IOC 容器中的 ConversionService,并在 Bean 属性配置及 SpringMVC 处理方法入参绑定等场合使用它进行数据的转换可通过 ConversionServiceFactoryBean 的 converters 属性 注册自定义的类型转换器
注册转换器方式:通过xml方式,这里需要在mvc:annotation-driven/元素上指定要进行转换的服务类,然后通过配置conversion-service属性来加载对应的转换器
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());
}
}
}
“见名知意”—带有条件的转换器,只有当它定义的matches方法返回true时,才会进行转换.为了与原有接口GenericConverter,在新的接口出现了ConditionalGenericConverter,它也是最常用的集合转换接口
.它继承了两个接口的方法,可以做到判断和转换的作用.
在该接口的基础上,SpringCore开发了很多实现类,这些实现类都会注册
到ConversionService对象里面,通过ConditionalConverter的matcher方法进行匹配
.如果可以匹配,则会调用convert方法进行转换
,它能够提供各种对数组和集合的转换.
下面找一个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;
}
}
SpringContext提供了进行数据格式化的Formatter,它需要实现一个接口Formatter,而Formatter又扩展了两个接口Printer和Parser.
通过print方法可以将结果按照一定的格式输出字符串.通过parse方法能够将满足一定格式的字符串转换为对象.这样就可以对数据进行格式化了,内部实际上是委托给Converter机制去实现的.
平时用的最多的两个注解就是@DateTimeFormat和@NumberFormat
日期格式化器在SpringMvc中是在系统启动时完成初始化,同时还提供了@DateTimeFormat来进行日期格式的定义,采用注解@NumberFormat进行数字格式转换
互斥的属性
:style
:类型为 NumberFormat.Style。用于指定样式类 型,包括三种:Style.NUMBER(正常数字类型)、 Style.CURRENCY(货币类型)、 Style.PERCENT( 百分数类型);pattern
:类型为 String,自定义样式, 如patter="#,###";pattern 属性
:类型为字符串。指定解析/格式化字段数据的模式, 如:”yyyy-MM-dd hh:mm:ss”iso 属性
:类型为 DateTimeFormat.ISO。指定解析/格式化字段数据 的ISO模式,包括四种:ISO.NONE(不使用) – 默style 属性
:字符串类型。通过样式指定日期时间的格式,由两位字 符组成,第一位表示日期的格式,第二位表示时间的格式:S:短日 期/时间格式、M:中日期/时间格式、L:长日期/时间格式、F:完整 日期/时间格式、-:忽略日期或时间格式创建一个表单,需要输入日期和金额,然后在后端处理器方法中进行接收并输出,这里以日期格式为yyyy-MM-dd和金额格式(逗号和小数点形式)为例
通过注解@DateTimeFormat和@NumberFormat,然后通过pattern的方式,处理器就可以将参数通过对应的格式化器进行转换,然后传递给控制器了,由于处理器方法形参和表单属性的name一致,可以自动匹配.
可以看到控制台中成功输出了数据,格式化成功,但是我们平时用的比较多的是使用处理器方法接收一个实体类,然后对实体类进行操作,下面修改一下表单,并且创建一个含有日期和金额的实体类,并再次测试