概述
一、 HttpMessageConverter
简单说就是 HTTP request (请求)和response (响应)的转换器 ,当请求和响应时,根据 MediaType 顺序选择注册的合适的类别的HttpMessageConverter对数据进行处理。
HTTP 请求和响应是基于文本的,意味着浏览器和服务器通过交换原始文本进行通信。但是,使用 Spring,controller 类中的方法返回纯 ‘String’ 类型和域模型(或其他 Java 内建对象)。如何将对象序列化/反序列化为原始文本?这由HttpMessageConverter 处理。
Http请求和响应报文本质上都是一串字符串,当请求报文来到java世界,它会被封装成为一个ServletInputStream的输入流,供我们读取报文。响应报文则是通过一个ServletOutputStream的输出流,来输出响应报文。我们从流中,只能读取到原始的字符串报文,同样,我们往输出流中,也只能写原始的字符。而在java世界中,处理业务逻辑,都是以一个个有业务意义的对象为处理维度的,那么在报文到达SpringMVC和从SpringMVC出去,都存在一个字符串到java对象的阻抗问题。这一过程,不可能由开发者手工转换。我们知道,在Struts2中,采用了OGNL来应对这个问题,而在SpringMVC中,它是HttpMessageConverter机制。
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, MediaType mediaType);
boolean canWrite(Class<?> clazz, MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
Spring MVC的@ResponseBody 的作用是把返回值直接写到HTTP response body里。 使用AnnotationMethodHandlerAdapter的handleResponseBody方法, AnnotationMethodHandlerAdapter使用request header中”Accept”的值和messageConverter支持的MediaType进行匹配,然后会用”Accept”的第一个值写入 response的”Content-Type”。
AnnotationMethodHandlerAdapter将会初始化7个转换器,可以通过调用AnnotationMethodHandlerAdapter的getMessageConverts()方法来获取转换器的一个集合
自定义HttpMessageConverter可参考其他博文。
二、GenericConverter Converter
GenericConverter Converter 都是 数据转换类,系统初始化时注册到FormatterRegistry 类, 对请求的参数进行类别转换,比如 字符串转为日期,字符串转为list
FormatterRegistry是一个用于注册格式化器和转换器的SPI。 FormattingConversionService是FormatterRegistry适合大多数环境的实现。该FormatterRegistrySPI允许您配置集中格式规则,而不是在你的控制器重复这样的配置。例如,您可能希望强制所有日期字段以某种方式格式化,或者具有特定注释的字段以某种方式格式化。
Converter主要是做Object与Object之间的类型转换,Formatter则是要完成任意Object与String之间的类型转换。前者适合于任何一层,而后者则主要用于web层
Converter只完成了数据类型的转换,却不负责输入输出数据的格式化工作,日期时间、货币等虽都以字符串形式存在,却有不同的格式。
Spring格式化框架要解决的问题是:从格式化的数据中获取真正的数据,绑定数据,将处理完成的数据输出为格式化的数据。Formatter接口就承担着这样的责任.
自定义Converter的场景其实蛮多的,比如最常见的StringToPhoneNumberConverter
它俩的互转,就可以定义个转换器,支持中间空格的电话号码格式~
三、Formatter
1 . 代码查看
FormatterRegistry
从接口继承关系中可以看出,它既可以注册格式化器,又可议注册转换器
// @since 3.0
public interface FormatterRegistry extends ConverterRegistry {
void addFormatter(Formatter<?> formatter);
void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
// 单独指定Printer和parser也是被允许的
void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);
// 注册处理注解的格式化器~~~~~ AnnotationFormatterFactory的实现类~~
void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);
}
FormattingConversionService
// @since 3.0 它继承自GenericConversionService ,所以它能对Converter进行一系列的操作~~~
// 实现了接口FormatterRegistry,所以它也可以注册格式化器了
// 实现了EmbeddedValueResolverAware,所以它还能有非常强大的功能:处理占位
public class FormattingConversionService extends GenericConversionService implements FormatterRegistry, EmbeddedValueResolverAware {
@Nullable
private StringValueResolver embeddedValueResolver;
private final Map<AnnotationConverterKey, GenericConverter> cachedPrinters = new ConcurrentHashMap<>(64);
private final Map<AnnotationConverterKey, GenericConverter> cachedParsers = new ConcurrentHashMap<>(64);
// 最终也是交给addFormatterForFieldType去做的
// getFieldType:它会拿到泛型类型。并且支持DecoratingProxy~~~
@Override
public void addFormatter(Formatter<?> formatter) {
addFormatterForFieldType(getFieldType(formatter), formatter);
}
// 存储都是分开存储的 读写分离
// PrinterConverter和ParserConverter都是一个GenericConverter 采用内部类实现的~~~ this代表一个ConversionService
// 注意:他们的ConvertiblePair必有一个类型是String.class
// Locale一般都可以这么获取:LocaleContextHolder.getLocale()
// 最终parse出来的result有可能也会交给conversionService.convert() 若类型能够匹配上的话
@Override
public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) {
addConverter(new PrinterConverter(fieldType, formatter, this));
addConverter(new ParserConverter(fieldType, formatter, this));
}
// 哪怕你是一个AnnotationFormatterFactory,最终也是被适配成了GenericConverter(ConditionalGenericConverter)
@Override
public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory) {
Class<? extends Annotation> annotationType = getAnnotationType(annotationFormatterFactory);
// 若你自定义的实现了EmbeddedValueResolverAware接口,还可以使用占位符哟~~~~
// AnnotationFormatterFactory是下面的重点内容~~~~
if (this.embeddedValueResolver != null && annotationFormatterFactory instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) annotationFormatterFactory).setEmbeddedValueResolver(this.embeddedValueResolver);
}
// 对每一种字段的type 都注册一个AnnotationPrinterConverter去处理~~~~~
// AnnotationPrinterConverter是一个ConditionalGenericConverter
// matches方法为:sourceType.hasAnnotation(this.annotationType);
// 这个判断是呼应的:因为annotationFormatterFactory只会作用在指定的字段类型上的~~~不符合类型条件的不用添加
Set<Class<?>> fieldTypes = annotationFormatterFactory.getFieldTypes();
for (Class<?> fieldType : fieldTypes) {
addConverter(new AnnotationPrinterConverter(annotationType, annotationFormatterFactory, fieldType));
addConverter(new AnnotationParserConverter(annotationType, annotationFormatterFactory, fieldType));
}
}
...
}
AnnotationFormatterFactory 它是一个工厂,专门创建出处理(格式化)指定字段field上标注有指定注解的。(Spring内助了两个常用注解:@DateTimeFormat和@NumberFormat) 我们常说的,要自定义注解来处理参数的格式化,就需要实现接口来自定义一个处理类。
// @since 3.0
public interface AnnotationFormatterFactory<A extends Annotation> {
// 此注解 可以作用的字段的类型~~~比如@DateTimeFormat只能作用域Date、Calendar、Long类型上~ 标注在被的类型上无效~~~
Set<Class<?>> getFieldTypes();
// 对标注有指定注解的字段进行格式化输出~~
Printer<?> getPrinter(A annotation, Class<?> fieldType);
// 对标注有指定注解的字段进行格式化解析~~~
Parser<?> getParser(A annotation, Class<?> fieldType);
}
NumberFormatAnnotationFormatterFactory
处理@NumberFormat
对数字进行格式化。
// 还继承自EmbeddedValueResolutionSupport,所以有resolveEmbeddedValue()方法,能够处理占位符
public class NumberFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
implements AnnotationFormatterFactory<NumberFormat> {
// 处理Byte、Short、Integer、Long、Float、Double、BigInteger、BigDecimal等类型~~~
@Override
public Set<Class<?>> getFieldTypes() {
return NumberUtils.STANDARD_NUMBER_TYPES;
}
@Override
public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation);
}
@Override
public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation);
}
// 可以看到,根据Style不同,返回的格式化器也是不同的~~~~
// 显然pattern非常强大,支持到了占位符,el取值~~~
private Formatter<Number> configureFormatterFrom(NumberFormat annotation) {
String pattern = resolveEmbeddedValue(annotation.pattern());
// 若指定了pattern,此处可以看出:直接当做数字处理NumberStyleFormatter
if (StringUtils.hasLength(pattern)) {
return new NumberStyleFormatter(pattern);
}
// 可能是钱币、百分比、数字 注意:都是使用的默认处理方式了~~~~
// @NumberFormat并不支持自定义 比如保留小数位、四舍五入等等
else {
Style style = annotation.style();
if (style == Style.CURRENCY) {
return new CurrencyStyleFormatter();
}
else if (style == Style.PERCENT) {
return new PercentStyleFormatter();
}
else {
return new NumberStyleFormatter();
}
}
}
}
@NumberFormat
是用来验证输入的数字格式。比如一般我们这样来格式化数值:@NumberFormat(pattern="#,###.##")
@NumberFormat
注解内容:
// @since 3.0 类比效果参见:java.text.NumberFormat
// 可以标注在方法上、属性field上、参数上~~~~
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface NumberFormat {
Style style() default Style.DEFAULT;
// 格式化数字的模版~~~ 若指定了pattern 那就使用new NumberStyleFormatter(pattern)进行格式化
String pattern() default "";
enum Style {
// 默认值 同 NUMBER
DEFAULT,
NUMBER,
PERCENT,
CURRENCY
}
}
FormattingConversionServiceFactoryBean
它和上面的不同,它是用于管理转换器、格式化器们的。比如我们自己自定义了一个转换器、格式化器需要注册,都以交给它。从名字可以看出,它主要是创建一个FormattingConversionService
,而它上面说了它既还有转换器,又有格式化器~~~
public class FormattingConversionServiceFactoryBean implements FactoryBean<FormattingConversionService>, EmbeddedValueResolverAware, InitializingBean {
@Nullable
private Set<?> converters;
@Nullable
private Set<?> formatters;
// 由此可见,我们要注册formatter不仅仅可以直接注册,也可通过formatterRegistrars注册进来~
@Nullable
private Set<FormatterRegistrar> formatterRegistrars;
private boolean registerDefaultFormatters = true;
@Nullable
private StringValueResolver embeddedValueResolver;
@Nullable
private FormattingConversionService conversionService; // 最终是它用于管理所有 备注:所有的formatter最终都是一个converter
// 这里把上面字段set进来的值,进行解析~~~~拆分~~~
@Override
public void afterPropertiesSet() {
// 由此可见,最终返回的是一个DefaultFormattingConversionService
this.conversionService = new DefaultFormattingConversionService(this.embeddedValueResolver, this.registerDefaultFormatters);
// 把set进来的这些converters都注册进去保存着~~~
ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
// 这里处理注册formatters和formatterRegistrars们~~~~
registerFormatters(this.conversionService);
}
private void registerFormatters(FormattingConversionService conversionService) {
if (this.formatters != null) {
for (Object formatter : this.formatters) {
if (formatter instanceof Formatter<?>) {
conversionService.addFormatter((Formatter<?>) formatter);
} else if (formatter instanceof AnnotationFormatterFactory<?>) {
conversionService.addFormatterForFieldAnnotation((AnnotationFormatterFactory<?>) formatter);
} else {
throw new IllegalArgumentException( "Custom formatters must be implementations of Formatter or AnnotationFormatterFactory");
}
}
}
if (this.formatterRegistrars != null) {
for (FormatterRegistrar registrar : this.formatterRegistrars) {
registrar.registerFormatters(conversionService);
}
}
}
@Override
@Nullable
public FormattingConversionService getObject() {
return this.conversionService;
}
// 类型实际上为DefaultFormattingConversionService
@Override
public Class<? extends FormattingConversionService> getObjectType() {
return FormattingConversionService.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
四、 自定义转换器/格式化器
1. 在WebMvcConfigurationSupport
有这么一句:
@Bean
public FormattingConversionService mvcConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
addFormatters(conversionService);
return conversionService;
}
它默认使用的就是DefaultFormattingConversionService
,因此我们只需要addFormatters()向里添加格式化器即可。(此处conversionService既是个ConverterRegistry,又是个FormatterRegistry,所以~~~) 此处只演示在WebMvc场景下的自定义:
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new PersonConverter());
//registry.addFormatter();
}
}
就这样我们就很方便的完成了我们对自定义转换器/格式化器~的添加。
2. 也可以实现GenericConverter,Converter 接口 ,然后注册bean到 WebMvcConfigurer中,
系统FormatterRegistry
会自动加载
beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());
beans.addAll(beanFactory.getBeansOfType(Converter.class).values());
3. 自定义String----->PhoneNumberModel的转换器
package cn.javass.chapter7.web.controller.support.converter;
//省略import
public class StringToPhoneNumberConverter implements Converter<String, PhoneNumberModel> {
Pattern pattern = Pattern.compile("^(\d{3,4})-(\d{7,8})$");
@Override
public PhoneNumberModel convert(String source) {
if(!StringUtils.hasLength(source)) {
//①如果source为空 返回null
return null;
}
Matcher matcher = pattern.matcher(source);
if(matcher.matches()) {
//②如果匹配 进行转换
PhoneNumberModel phoneNumber = new PhoneNumberModel();
phoneNumber.setAreaCode(matcher.group(1));
phoneNumber.setPhoneNumber(matcher.group(2));
return phoneNumber;
} else {
//③如果不匹配 转换失败
throw new IllegalArgumentException(String.format("类型转换失败,需要格式[010-12345678],但格式是[%s]", source));
}
}
}
五、Convert/Format机制与HttpMessageConverter的关系
Spring3引入了较Spring2的PropertyEditor
更加强大、通用的Convert/Format SPI
,Convert SPI
可以实现任意类型的转换;Format SPI
支持国际化,并在前者的基础上实现了String与任意类型的转换。这两类SPI属于spring-core
,被整个spring-framework
共享,是一种通用的类型转换器。
HttpMessageConverter
虽然功能上也表现为HttpMessage
与任意类型的转换,但其接口和Convert SPI
并没有继承关系。HttpMessageConverter
属于spring-web
。HttpMessage
是SpringMVC对Servlet
规范中HttpServletRequest
和HttpServletResponse
的包装,因此接受请求时需要把HttpMessage
转换成用户需要的数据,在生成响应时需要把用户生成的数据转换成HttpMessage
。如果用户在XML的<mvc:message-converters>
中没有指定register-defaults=false
,SpringMVC默认至少会注册一些自带的HttpMessageConvertor
(从先后顺序排列分别为ByteArrayHttpMessageConverter
、StringHttpMessageConverter
、ResourceHttpMessageConverter
、SourceHttpMessageConverter
、AllEncompassingFormHttpMessageConverter
)。
如果后端服务使用Restful API
的形式,一般使用JSON作为前后端通信的格式规范,由于SpringMVC自带MappingJackson2HttpMessageConverter
,在依赖中引入jackson后,容器会把该转换器自动注册到converter链的末尾。
两者的分工
Http请求中有几个常用的部分可以用来传递业务信息,以常见的Get
和Post
方法为例。
是否可用 | URL | Parameter | Header | Body |
---|---|---|---|---|
Get | 是 | 是 | 是 | 否 |
Post | 是 | 否 | 是 | 是 |
那么上述的4个部分都是用HttpMessageConverter
来进行类型转换的吗?显然不是,HttpMessageConverter
和Convert SPI
各有分工, HttpMessageConverte
只负责解析Http包的Body体部分1,其余部分都交由相关的Convert SPI
处理2。
(1.HandlerMethodInvoker.readWithMessageConverters 2. HandlerMethodArgumentResolver)
是否支持 | URL | Parameter | Header | Body |
---|---|---|---|---|
HttpMessageConverter | 否 | 否 | 否 | 是 |
Convert SPI | 是 | 是 | 是 | 否 |
除上表所示之外,SpringMVC还有一些需要Convert SPI
的场景,如读取Cookie值的@CookieValue
(本质是Header),解析矩阵URL的@MatrixVariable
(本质是URL),读取本地会话的@SessionAttribute
,解析SpEL的@Value
。
Convert SPI类型转换实例
在SpringMVC中,单次请求的整个处理流程中有哪些地方需要类型转换?以Delete /ajax/shop/12345/blacklist?id=1
请求为例,后端对应的处理方法如下。
@DeleteMapping("/ajax/{shopId}/blacklist") @ResponseBody
public boolean deleteBlackItem(@RequestParam Integer id, @PathVariable Integer shopId) {
//省略
return true;
}
由于请求的URL为String类型,而接受的参数id
和shopId
都是Integer类型,因此Spring会自动查找合适的Converter
(具体实现为StringToNumberConverterFactory
的工厂产品)把字符串“12345”
和“1”
转化为数字12345
和1
,分别赋值给shopId
和id
。处理完业务逻辑后,方法返回true
,但需要将其格式化成String类型的“true”
才能输出到响应的Body中,这时Spring就会使用StringToBooleanConverter
来完成转换。如下图所示,除了上述常见的数据绑定和格式化显示功能,数据验证功能(JSR-303
)基于数据绑定也间接利用了这两套SPI。
结语
在SpringMVC处理请求时,HttpMessageConverter
和Convert SPI
分别用来反序列化请求的Body和非Body部分,即HttpMessageConverter
是一套小型、独立、额外为用户提供的专门的Body体的类型转换器;而Convert SPI
则与PropertyEditor
类似,可以处理更为通用的类型转换。
最后
以上就是酷炫毛豆为你收集整理的Convert理解_第一弹:HttpMessageConverter,GenericConverter,Converter,Formatter一、 HttpMessageConverter二、GenericConverter Converter三、Formatter四、 自定义转换器/格式化器五、Convert/Format机制与HttpMessageConverter的关系的全部内容,希望文章能够帮你解决Convert理解_第一弹:HttpMessageConverter,GenericConverter,Converter,Formatter一、 HttpMessageConverter二、GenericConverter Converter三、Formatter四、 自定义转换器/格式化器五、Convert/Format机制与HttpMessageConverter的关系所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复