Convert理解_第一弹:HttpMessageConverter,GenericConverter,Converter,Formatter

一、 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只完成了数据类型的转换,却不负责输入输出数据的格式化工作,日期时间、货币等虽都以字符串形式存在,却有不同的格式。

扫描二维码关注公众号,回复: 13115057 查看本文章

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 SPIConvert SPI可以实现任意类型的转换;Format SPI支持国际化,并在前者的基础上实现了String与任意类型的转换。这两类SPI属于spring-core,被整个spring-framework共享,是一种通用的类型转换器。

HttpMessageConverter虽然功能上也表现为HttpMessage与任意类型的转换,但其接口和Convert SPI并没有继承关系。HttpMessageConverter属于spring-webHttpMessage是SpringMVC对Servlet规范中HttpServletRequestHttpServletResponse的包装,因此接受请求时需要把HttpMessage转换成用户需要的数据,在生成响应时需要把用户生成的数据转换成HttpMessage。如果用户在XML的<mvc:message-converters>中没有指定register-defaults=false,SpringMVC默认至少会注册一些自带的HttpMessageConvertor(从先后顺序排列分别为ByteArrayHttpMessageConverterStringHttpMessageConverterResourceHttpMessageConverterSourceHttpMessageConverterAllEncompassingFormHttpMessageConverter)。

如果后端服务使用Restful API的形式,一般使用JSON作为前后端通信的格式规范,由于SpringMVC自带MappingJackson2HttpMessageConverter,在依赖中引入jackson后,容器会把该转换器自动注册到converter链的末尾。

两者的分工

Http请求中有几个常用的部分可以用来传递业务信息,以常见的GetPost方法为例。

是否可用 URL Parameter Header Body
Get
Post

那么上述的4个部分都是用HttpMessageConverter来进行类型转换的吗?显然不是,HttpMessageConverterConvert 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类型,而接受的参数idshopId都是Integer类型,因此Spring会自动查找合适的Converter(具体实现为StringToNumberConverterFactory的工厂产品)把字符串“12345”“1”转化为数字123451,分别赋值给shopIdid。处理完业务逻辑后,方法返回true,但需要将其格式化成String类型的“true”才能输出到响应的Body中,这时Spring就会使用StringToBooleanConverter来完成转换。如下图所示,除了上述常见的数据绑定和格式化显示功能,数据验证功能(JSR-303)基于数据绑定也间接利用了这两套SPI。

preview

结语

在SpringMVC处理请求时,HttpMessageConverterConvert SPI分别用来反序列化请求的Body和非Body部分,即HttpMessageConverter是一套小型、独立、额外为用户提供的专门的Body体的类型转换器;而Convert SPI则与PropertyEditor类似,可以处理更为通用的类型转换。

猜你喜欢

转载自blog.csdn.net/yunxing323/article/details/111771946