springmvc–RequestResponseBodyMethodProcessor
处理@RequestBody
注解和@ResponseBody
注解
文章目录
- springmvc--`RequestResponseBodyMethodProcessor`处理`@RequestBody`注解和`@ResponseBody`注解
- 1 简单介绍
- 2 处理方法参数
- 3 处理返回值
- 4 `MethodParameter`
- 5 `ServletServerHttpRequest`
- 6 `MediaType`
- 7 `HttpHeaders`
- 8 `EmptyBodyCheckingHttpInputMessage`
- 9 `HttpMessageConverter`
- 10 `RequestResponseBodyAdviceChain`
- 11 `ServletServerHttpResponse`
1 简单介绍
如其名,它就是用来处理@RequestBody
注解和@ResponseBody
注解的。
我们来看一下它的类图
从它继承的接口我们也能发现,该
Processor
拥有处理方法参数和返回值的功能
1.1 创建位置
RequestMappingHandlerAdapter
实现了InitializingBean
接口,所以spring
在它初始化阶段自动回调InitializingBean
接口的afterPropertiesSet()
方法,在这个方法中创建一些默认的参数解析器,其中就包含RequestResponseBodyMethodProcessor
@Override
public void afterPropertiesSet() {
//.....................省略一些不相关的代码
//用户未进行手动配置,就会使用springmvc默认配置的解析器
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
//.....................省略一些不相关的代码
}
/**
* Return the list of argument resolvers to use including built-in resolvers
* and custom resolvers provided via {@link #setCustomArgumentResolvers}.
*/
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
//在这里创建了RequestResponseBodyMethodProcessor对象
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
1.2 构造方法
/**
* Complete constructor for resolving {@code @RequestBody} method arguments.
* For handling {@code @ResponseBody} consider also providing a
* {@code ContentNegotiationManager}.
* @since 4.2
*/
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters,
@Nullable List<Object> requestResponseBodyAdvice) {
super(converters, null, requestResponseBodyAdvice);
}
看它父类AbstractMessageConverterMethodProcessor
的构造方法
/* Extensions associated with the built-in message converters */
private static final Set<String> SAFE_EXTENSIONS = new HashSet<>(Arrays.asList(
"txt", "text", "yml", "properties", "csv",
"json", "xml", "atom", "rss",
"png", "jpe", "jpeg", "jpg", "gif", "wbmp", "bmp"));
//内容协商管理器
private final ContentNegotiationManager contentNegotiationManager;
//支持的扩展类型
private final Set<String> safeExtensions = new HashSet<>();
/**
* Constructor with list of converters and ContentNegotiationManager as well
* as request/response body advice instances.
*/
protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters,
@Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) {
//父类构造方法
super(converters, requestResponseBodyAdvice);
//用户未指定就使用默认的内容协商管理器ContentNegotiationManager
this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
//内容协商管理器支持的文件类型
this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions());
this.safeExtensions.addAll(SAFE_EXTENSIONS);
}
看它父类AbstractMessageConverterMethodArgumentResolver
的构造方法
//消息转换器
protected final List<HttpMessageConverter<?>> messageConverters;
//支持的媒体类型
protected final List<MediaType> allSupportedMediaTypes;
//执行链
private final RequestResponseBodyAdviceChain advice;
/**
* Constructor with converters and {@code Request~} and {@code ResponseBodyAdvice}.
* @since 4.2
*/
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters,
@Nullable List<Object> requestResponseBodyAdvice) {
Assert.notEmpty(converters, "'messageConverters' must not be empty");
//消息转换器
this.messageConverters = converters;
//这些消息转换器所支持的媒体类型
this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
//RequestBodyAdvice和ResponseBodyAdvice的执行链,见10
this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
}
可以看到,构造方法调用过程中会做以下几件事情
- 创建内容协商管理器
ContentNegotiationManager
- 获取该参数解析器自身支持的媒体类型(从它拥有的消息转换器得到)
- 将有
@ControllerAdvice
注解的标注的RequestBodyAdvice
接口和ResponseBodyAdvice
接口的实现类封装为RequestResponseBodyAdviceChain
执行链
2 处理方法参数
这就离不开HandlerMethodArgumentResolver
接口定义的方法了
public interface HandlerMethodArgumentResolver {
/**
* 判断是否支持处理该方法参数
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 解析该方法参数,得到参数值
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
那我们就按照顺序看这些方法的原理
2.1 supportsParameter(MethodParameter parameter)
方法
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
判断过程很简单,就是检查方法参数上有没有
@RequestBody
注解,有就支持解析该参数
2.2 resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)
方法
/**
* Throws MethodArgumentNotValidException if validation fails.
* @throws HttpMessageNotReadableException if {@link RequestBody#required()}
* is {@code true} and there is no body content or if there is no suitable
* converter to read the content with.
*/
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//如果参数是Optional容器类型,则返回容器内部参数,见4.1
parameter = parameter.nestedIfOptional();
/**
* 读取请求,为参数创建一个参数值,见2.2.1
* parameter.getNestedGenericParameterType()方法,获取当前方法参数的嵌套泛型类型,见4.2
*/
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
//获取参数的变量名,类似于user
String name = Conventions.getVariableNameForParameter(parameter);
//数据绑定工厂
if (binderFactory != null) {
//创建本次请求的数据绑定器
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
//校验数据,见2.2.2
validateIfApplicable(binder, parameter);
/**
* 校验过程中发生错误,统一在此处抛出异常
* getBindingResult()方法,
*/
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
/**
* MODEL_KEY_PREFIX=“org.springframework.validation.BindingResult.”
* 将绑定结果保存到模型中
*/
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
//适配给定参数值和方法参数,见2.2.3
return adaptArgumentIfNecessary(arg, parameter);
}
2.2.1 读取请求体输入流中的数据,为参数创建一个参数值
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
//得到原始的request请求对象
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
//构建一个服务请求对象,见5.1
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
//解析请求体内容并进行类型转换
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
if (arg == null && checkRequired(parameter)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
parameter.getExecutable().toGenericString(), inputMessage);
}
return arg;
}
解析请求体内容并进行类型转换
/**
* 消息转换器,见9.2
* HandlerAdaptor创建RequestResponseBodyMethodProcessor的时候,自动将HandlerAdaptor
* 拥有的所有HttpMessageConverter作为构造器参数构造该对象
*/
protected final List<HttpMessageConverter<?>> messageConverters;
/**
* Create the method argument value of the expected parameter type by reading
* from the given HttpInputMessage.
* @param <T> the expected type of the argument value to be created
* @param inputMessage the HTTP input message representing the current request
* @param parameter the method parameter descriptor
* @param targetType the target type, not necessarily the same as the method
* parameter type, e.g. for {@code HttpEntity<String>}.
* @return the created method argument value
* @throws IOException if the reading from the request fails
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
*/
@SuppressWarnings("unchecked")
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
//媒体类型
MediaType contentType;
boolean noContentType = false;
try {
/**
* 先得到请求的所有请求头,见5.2
* 然后获取内容类型,见7.2
*/
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
/**
* 未在请求头中指定内容类型
* 则按照"application/octet-stream"此种类型解析请求体数据
*/
if (contentType == null) {
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
//获取方法参数所在的类,就是@Controller注解类
Class<?> contextClass = parameter.getContainingClass();
//参数的clazz对象
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
//重新解析参数clazz对象
if (targetClass == null) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
//获取本次请求的请求方式
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message;
try {
//验证请求体输入流中是否包含数据,见8
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
//遍历所有的Http消息转换器,对请求体输入流中的数据进行类型转换
for (HttpMessageConverter<?> converter : this.messageConverters) {
//得到当前消息转换器的clazz对象
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
/**
* 在这里把消息转换器分为了两类
* 一类是HttpMessageConverter,一类是GenericHttpMessageConverter
* 其实它们并没有明显的区别,见9
*/
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
//能够转换请求体中内容
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
//请求体输入流中有数据,见8
if (message.hasBody()) {
/**
* 消息转换器转换前拦截
* 调用所有RequestBodyAdvice对象的beforeBodyRead()方法拦截,见10.2
* getAdvice()方法获取字段advice的值,
* 也就是RequestResponseBodyAdviceChain,见1.2
*/
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
//执行消息转换器的read()方法,完成类型转换
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
/**
* 消息转换器转换后拦截
* 调用所有RequestBodyAdvice对象的afterBodyRead()方法拦截
*/
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
/**
* 拦截空请求体
* 调用所有RequestBodyAdvice对象的handleEmptyBody()方法拦截
*/
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
/**
* 说明找不到合适的消息转换器进行类型转换,或者请求体中没有数据
*/
if (body == NO_VALUE) {
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && !message.hasBody())) {
return null;
}
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}
MediaType selectedContentType = contentType;
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
});
return body;
}
2.2.2 校验数据
/**
* Validate the binding target if applicable.
* <p>The default implementation checks for {@code @javax.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* @param binder the DataBinder to be used
* @param parameter the method parameter descriptor
* @since 4.1.5
* @see #isBindExceptionRequired
*/
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
//得到参数上的所有注解信息
Annotation[] annotations = parameter.getParameterAnnotations();
//遍历注解
for (Annotation ann : annotations) {
//获取@Validated注解信息
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
//如果有Validated注解,或者注解名字以Valid开头,则进入数据校验
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
//获取@Validated注解value属性值
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
/**
* 数据校验
* 数据绑定器中包含一个数据校验器,springmvc借助它完成校验功能
*/
binder.validate(validationHints);
break;
}
}
}
2.2.3 适配给定参数值和方法参数
/**
* Adapt the given argument against the method parameter, if necessary.
* @param arg the resolved argument
* @param parameter the method parameter descriptor
* @return the adapted argument, or the original resolved argument as-is
* @since 4.3.5
*/
@Nullable
protected Object adaptArgumentIfNecessary(@Nullable Object arg, MethodParameter parameter) {
/**
* 参数真实类型Optional容器
* 就执行Optional.of(arg)方法包装为容器
*/
if (parameter.getParameterType() == Optional.class) {
if (arg == null || (arg instanceof Collection && ((Collection<?>) arg).isEmpty()) ||
(arg instanceof Object[] && ((Object[]) arg).length == 0)) {
return Optional.empty();
}
else {
return Optional.of(arg);
}
}
return arg;
}
这个方法主要用来处理参数类型是
Optional
,它会执行Optional.of(arg)
方法将真实参数值包装为Optional
容器
3 处理返回值
这就离不开HandlerMethodReturnValueHandler
接口定义的方法了
public interface HandlerMethodReturnValueHandler {
//判断是否支持处理该返回值
boolean supportsReturnType(MethodParameter returnType);
//处理返回值,写入响应体中
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
那我们就按照顺序看这些方法的原理
3.1 supportsReturnType(MethodParameter returnType)
方法
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
判断
2
点,满足一下两点之一,表明支持处理该返回值
- 参数所在类上有没有
@ResponseBody
注解- 参数所在方法上或返回值上是否标注了
@ResponseBody
注解
3.2 handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
方法
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
/**
* 设置请求已被处理
* ModelAndViewContainer中有个requestHandled字段用来标识请求是否被处理
*/
mavContainer.setRequestHandled(true);
//将原始的请求对象包装为ServletServerHttpRequest,见3.2.1
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
将原始的响应对象包装为ServletServerHttpResponse,见3.2.2
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
//将返回值写入响应体输出流中,见3.2.3
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
3.2.1 将原始的请求对象包装为ServletServerHttpRequest
/**
* Create a new {@link HttpInputMessage} from the given {@link NativeWebRequest}.
* @param webRequest the web request to create an input message from
* @return the input message
*/
protected ServletServerHttpRequest createInputMessage(NativeWebRequest webRequest) {
//先拿到原生的请求对象
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
//包装为ServletServerHttpRequest,见5.1
return new ServletServerHttpRequest(servletRequest);
}
3.2.2 将原始的响应对象包装为ServletServerHttpResponse
/**
* Creates a new {@link HttpOutputMessage} from the given {@link NativeWebRequest}.
* @param webRequest the web request to create an output message from
* @return the output message
*/
protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
//获取原生的响应对象
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
Assert.state(response != null, "No HttpServletResponse");
//包装为ServletServerHttpResponse,见11.1
return new ServletServerHttpResponse(response);
}
3.2.3 将返回值写入响应体输出流中
/**
* Writes the given return type to the given output message.
* @param value the value to write to the output message
* @param returnType the type of the value
* @param inputMessage the input messages. Used to inspect the {@code Accept} header.
* @param outputMessage the output message to write to
* @throws IOException thrown in case of I/O errors
* @throws HttpMediaTypeNotAcceptableException thrown when the conditions indicated
* by the {@code Accept} header on the request cannot be met by the message converters
* @throws HttpMessageNotWritableException thrown if a given message cannot
* be written by a converter, or if the content-type chosen by the server
* has no compatible converter.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
Object body;
Class<?> valueType;
Type targetType;
//返回值为CharSequence类型
if (value instanceof CharSequence) {
body = value.toString();
valueType = String.class;
targetType = String.class;
}
//其他类型
else {
body = value;
//获取返回值的类型,见3.2.3.1
valueType = getReturnValueType(body, returnType);
/**
* getGenericType(returnType)方法,解析方法声明的返回类型的泛型类型,见3.2.3.2
* getContainingClass()方法,获取方法声明的返回类型的包含类
*
* 获取方法声明的返回类型的泛型类型
*/
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
/**
* 处理器方法返回了一个资源类型的返回值,见3.2.3.3
* Resource的子类且非InputStreamResource
*/
if (isResourceType(value, returnType)) {
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource) value;
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
body = HttpRange.toResourceRegions(httpRanges, resource);
valueType = body.getClass();
targetType = RESOURCE_REGION_LIST_TYPE;
}
catch (IllegalArgumentException ex) {
outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
}
}
}
MediaType selectedMediaType = null;
/**
* getHeaders()方法,获取响应头,见11.3
* getContentType()方法,获取内容类型,见7.2
*/
MediaType contentType = outputMessage.getHeaders().getContentType();
//判断用户指定响应的内容类型格式是否正确
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
//用户指定了响应的内容类型,且格式正确,那么就使用用户指定的
if (isContentTypePreset) {
if (logger.isDebugEnabled()) {
logger.debug("Found 'Content-Type:" + contentType + "' in response");
}
selectedMediaType = contentType;
}
/********************************内容协商************************************/
//否则进入下面的内容协商过程
else {
//获取原生的请求对象
HttpServletRequest request = inputMessage.getServletRequest();
//得到浏览器可接收所有内容类型,见3.2.3.4
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
//得到服务器能够生产的所有内容类型,见3.2.3.5
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
//有返回值,但是服务器能生产内容类型为null,那么肯定就得抛出异常
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
List<MediaType> mediaTypesToUse = new ArrayList<>();
/**
* 双重循环,将浏览器可接收所有内容类型和服务器能够生产的所有内容类型一一比较
* 找到所有同时兼容浏览器和服务器需求的内容类型
*/
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
/**
* 判断requestedType和producibleType这两个媒体类型是否兼容
* 例如text/*就和text/plain、text/html兼容,反之亦然
*/
if (requestedType.isCompatibleWith(producibleType)) {
/**
* getMostSpecificMediaType()方法, 比较requestedType和
* producibleType两个媒体类型谁更具体,返回更具体的那个
* 比如text/*和text/plain,那么就返回text/plain
*/
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (mediaTypesToUse.isEmpty()) {
//有返回值却找不到合适的内容类型写入,抛异常
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
if (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
return;
}
//对支持的内容类型排序
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
for (MediaType mediaType : mediaTypesToUse) {
//使用第一个格式正确的内容类型
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
//如果媒体类型是*/*、application/*之一
//则约定使用application/octet-stream类型写入到响应体中
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
//application/octet-stream
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Using '" + selectedMediaType + "', given " +
acceptableTypes + " and supported " + producibleTypes);
}
}
/********************************内容协商完成*********************************/
if (selectedMediaType != null) {
//得到该媒体类型的副本
selectedMediaType = selectedMediaType.removeQualityValue();
//遍历所有的HttpMessageConverter
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
/**
* canWrite()方法判断的时候都加入媒体类型selectedMediaType的判断
* 需要同时满足三个条件,值类型,方法声明的返回值类型,媒体类型
*/
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
//序列化之前调用ResponseBodyAdvice,过程和RequestBodyAdvice差不多
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
//处理器方法返回了返回值
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
//添加Content-Disposition响应头,见3.2.3.6
addContentDispositionHeader(inputMessage, outputMessage);
//序列化,将返回值写入响应体中
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
/**
* 内容协商失败
* 在此处抛出异常
*/
if (body != null) {
Set<MediaType> producibleMediaTypes =
(Set<MediaType>) inputMessage.getServletRequest()
.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
throw new HttpMessageNotWritableException(
"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
}
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
该方法可以说是核心中的核心,它完成了对处理器方法返回类型的确定,并进行内容协商,找到合适的
HttpMessageConverter
将返回值序列化到响应体中。
3.2.3.1 获取返回值的类型
/**
* Return the type of the value to be written to the response. Typically this is
* a simple check via getClass on the value but if the value is null, then the
* return type needs to be examined possibly including generic type determination
* (e.g. {@code ResponseEntity<T>}).
*/
protected Class<?> getReturnValueType(@Nullable Object value, MethodParameter returnType) {
return (value != null ? value.getClass() : returnType.getParameterType());
}
- 有返回值就直接使用
返回值.getClass()
方法得到返回值的类型- 没有返回值就通过解析方法返回参数的类型,得到返回值的类型
3.2.3.2 解析方法声明的返回类型的泛型类型
/**
* Return the generic type of the {@code returnType} (or of the nested type
* if it is an {@link HttpEntity}).
*/
private Type getGenericType(MethodParameter returnType) {
//返回参数是HttpEntity类型的
if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
//解析HttpEntity的泛型类型
return ResolvableType.forType(returnType.getGenericParameterType()).getGeneric().getType();
}
//其他情况直接解析泛型
else {
return returnType.getGenericParameterType();
}
}
- 对方法声明的返回类型为
HttpEntity
的单独解析- 其他情况直接调用
getGenericParameterType()
方法解析泛型
3.2.3.3 判断处理器方法是不是返回了一个资源类型的返回值
/**
* Return whether the returned value or the declared return type extends {@link Resource}.
*/
protected boolean isResourceType(@Nullable Object value, MethodParameter returnType) {
//得到返回值类型,见3.2.3.1
Class<?> clazz = getReturnValueType(value, returnType);
//判断返回值是不是Resource且非InputStreamResource类型的
return clazz != InputStreamResource.class && Resource.class.isAssignableFrom(clazz);
}
判断处理器方法是不是返回了一个资源类型
- 非
InputStreamResource
Resource
及其子类
3.2.3.4 获取浏览器可接收所有内容类型
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
throws HttpMediaTypeNotAcceptableException {
//通过内容协商管理器得到用户在请求中设置的可接收内容类型
return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
}
3.2.3.5 获取服务器能够生产的所有内容类型
/**
* Returns the media types that can be produced. The resulting media types are:
* <ul>
* <li>The producible media types specified in the request mappings, or
* <li>Media types of configured converters that can write the specific return value, or
* <li>{@link MediaType#ALL}
* </ul>
* @since 4.2
*/
@SuppressWarnings("unchecked")
protected List<MediaType> getProducibleMediaTypes(
HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
/**
* 先得到保存在请求域中的浏览器可接收的媒体类型
* PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE=
* org.springframework.web.servlet.HandlerMapping.producibleMediaTypes
*/
Set<MediaType> mediaTypes =
(Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<>(mediaTypes);
}
/**
* allSupportedMediaTypes字段在RequestResponseBodyMethodProcessor构造方法初始化值
* 它会解析持有的HttpMessageConverter,得到这些消息转换器能够转换类型,见1.1
* PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE=
* org.springframework.web.servlet.HandlerMapping.producibleMediaTypes
*/
else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<>();
/**
* 遍历所有的HttpMessageConverter
* 找到所有能将返回值类型的数据写入响应体的消息转换器
* 调用这些转换器的getSupportedMediaTypes()方法得到它们支持处理的媒体类型
*/
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter && targetType != null) {
if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
//获取该消息转换器支持的媒体类型
result.addAll(converter.getSupportedMediaTypes());
}
}
else if (converter.canWrite(valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}
else {
return Collections.singletonList(MediaType.ALL);
}
}
方法流程大概如下
- 先从请求域中获取服务器能够生产的媒体类,获取到就直接使用
- 否则判断
allSupportedMediaTypes
字段是否为null
(在RequestResponseBodyMethodProcessor
构造方法中,会解析它持有的HttpMessageConverter
,得到这些消息转换器能够转换类型,缓存在allSupportedMediaTypes
字段中)
null
表示支持生产所有媒体类型- 否则会重新遍历
HttpMessageConverter
,得到canWrite()
方法判定通过的HttpMessageConverter
所支持的媒体类型
3.2.3.6 添加Content-Disposition
响应头(文件下载)
/**
* Check if the path has a file extension and whether the extension is either
* on the list of {@link #SAFE_EXTENSIONS safe extensions} or explicitly
* {@link ContentNegotiationManager#getAllFileExtensions() registered}.
* If not, and the status is in the 2xx range, a 'Content-Disposition'
* header with a safe attachment file name ("f.txt") is added to prevent
* RFD exploits.
*/
private void addContentDispositionHeader(ServletServerHttpRequest request, ServletServerHttpResponse response) {
//获取响应头
HttpHeaders headers = response.getHeaders();
//响应头中包含Content-Disposition,则直接返回,不做任何处理
if (headers.containsKey(HttpHeaders.CONTENT_DISPOSITION)) {
return;
}
try {
//获取响应状态码
int status = response.getServletResponse().getStatus();
//下面这些响应状态码的不做处理
if (status < 200 || (status > 299 && status < 400)) {
return;
}
}
catch (Throwable ex) {
// ignore
}
//获取原生的请求对象
HttpServletRequest servletRequest = request.getServletRequest();
//获取当前请求的URI
String requestUri = UrlPathHelper.rawPathInstance.getOriginatingRequestUri(servletRequest);
int index = requestUri.lastIndexOf('/') + 1;
String filename = requestUri.substring(index);
String pathParams = "";
index = filename.indexOf(';');
if (index != -1) {
pathParams = filename.substring(index);
filename = filename.substring(0, index);
}
filename = UrlPathHelper.defaultInstance.decodeRequestString(servletRequest, filename);
//截取路径上文件的后缀名
String ext = StringUtils.getFilenameExtension(filename);
pathParams = UrlPathHelper.defaultInstance.decodeRequestString(servletRequest, pathParams);
String extInPathParams = StringUtils.getFilenameExtension(pathParams);
if (!safeExtension(servletRequest, ext) || !safeExtension(servletRequest, extInPathParams)) {
headers.add(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=f.txt");
}
}
4 MethodParameter
这是
spring
自己定义的类,代表方法参数。之前我已经贴过它很多方法源码了。
4.1 nestedIfOptional()
方法,返回嵌套在Optional
容器中的真实参数
/**
* Return a variant of this {@code MethodParameter} which points to
* the same parameter but one nesting level deeper in case of a
* {@link java.util.Optional} declaration.
* @since 4.3
* @see #isOptional()
* @see #nested()
*/
public MethodParameter nestedIfOptional() {
return (getParameterType() == Optional.class ? nested() : this);
}
如果参数类型为
Optional
容器,那么就会调用nested()
方法得到容器内的参数类型
4.1.1 getParameterType()
方法,得到参数类型
//缓存的方法参数类型
@Nullable
private volatile Class<?> parameterType;
/**
* Return the type of the method/constructor parameter.
* @return the parameter type (never {@code null})
*/
public Class<?> getParameterType() {
//先从缓存中获取
Class<?> paramType = this.parameterType;
if (paramType != null) {
return paramType;
}
//方法参数的包含类不等于方法所在类
if (getContainingClass() != getDeclaringClass()) {
paramType = ResolvableType.forMethodParameter(this, null, 1).resolve();
}
//计算方法参数的类型(反射)
if (paramType == null) {
paramType = computeParameterType();
}
this.parameterType = paramType;
return paramType;
}
//原生的反射方法计算参数类型
private Class<?> computeParameterType() {
if (this.parameterIndex < 0) {
Method method = getMethod();
if (method == null) {
return void.class;
}
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass())) {
return KotlinDelegate.getReturnType(method);
}
return method.getReturnType();
}
return this.executable.getParameterTypes()[this.parameterIndex];
}
- 通过
method.getReturnType()
方法得到返回值类型- 通过
executable.getParameterTypes()
方法得到所有参数类型
4.1.2 nested()
方法,得到嵌套的参数的MethodParameter
//缓存的嵌套参数
@Nullable
private volatile MethodParameter nestedMethodParameter;
//参数的嵌套级别,无嵌套时为1
private int nestingLevel;
/** Map from Integer level to Integer type index. */
@Nullable
Map<Integer, Integer> typeIndexesPerLevel;
/**
* Return a variant of this {@code MethodParameter} which points to the
* same parameter but one nesting level deeper.
* @since 4.3
*/
public MethodParameter nested() {
return nested(null);
}
/**
* Return a variant of this {@code MethodParameter} which points to the
* same parameter but one nesting level deeper.
* @param typeIndex the type index for the new nesting level
* @since 5.2
*/
public MethodParameter nested(@Nullable Integer typeIndex) {
//缓存中获取嵌套的参数
MethodParameter nestedParam = this.nestedMethodParameter;
if (nestedParam != null && typeIndex == null) {
return nestedParam;
}
/**
* 缓存中不存在,解析嵌套参数
* 此时会提升嵌套级别,在原来的基础上加1,代表Optional容器中参数
*/
nestedParam = nested(this.nestingLevel + 1, typeIndex);
if (typeIndex == null) {
this.nestedMethodParameter = nestedParam;
}
return nestedParam;
}
private MethodParameter nested(int nestingLevel, @Nullable Integer typeIndex) {
//克隆一份作为嵌套参数
MethodParameter copy = clone();
//设置当前参数的嵌套级别
copy.nestingLevel = nestingLevel;
if (this.typeIndexesPerLevel != null) {
copy.typeIndexesPerLevel = new HashMap<>(this.typeIndexesPerLevel);
}
if (typeIndex != null) {
copy.getTypeIndexesPerLevel().put(copy.nestingLevel, typeIndex);
}
//这些缓存都置为null,表示需要重新解析
copy.parameterType = null;
copy.genericParameterType = null;
return copy;
}
4.2 getNestedGenericParameterType()
方法,获取当前方法参数的嵌套泛型类型
/**
* Return the nested generic type of the method/constructor parameter.
* @return the parameter type (never {@code null})
* @since 4.2
* @see #getNestingLevel()
*/
public Type getNestedGenericParameterType() {
//参数有嵌套,所以级别肯定大于1
if (this.nestingLevel > 1) {
Type type = getGenericParameterType();
for (int i = 2; i <= this.nestingLevel; i++) {
/**
* ParameterizedType代表一个嵌套类型
* 比如Collection<String>,最终解析得到String的clazz对象
*/
if (type instanceof ParameterizedType) {
Type[] args = ((ParameterizedType) type).getActualTypeArguments();
Integer index = getTypeIndexForLevel(i);
type = args[index != null ? index : args.length - 1];
}
}
return type;
}
//无嵌套
else {
//直接获取参数Type类型,见4.2.1
return getGenericParameterType();
}
}
4.2.1 获取参数的Type
类型
//缓存的方法参数泛型类型
@Nullable
private volatile Type genericParameterType;
/**
* Return the generic type of the method/constructor parameter.
* @return the parameter type (never {@code null})
* @since 3.0
*/
public Type getGenericParameterType() {
//先从缓存中获取
Type paramType = this.genericParameterType;
if (paramType == null) {
//参数索引小于0,代表方法返回值
if (this.parameterIndex < 0) {
//使用反射method.getGenericReturnType()方法得到方法返回值类型
Method method = getMethod();
paramType = (method != null ?
(KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass()) ?
KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class);
}
//方法参数
else {
//反射得到方法或构造器的所有参数类型
Type[] genericParameterTypes = this.executable.getGenericParameterTypes();
int index = this.parameterIndex;
//构造器,并且是内部类
if (this.executable instanceof Constructor &&
ClassUtils.isInnerClass(this.executable.getDeclaringClass()) &&
genericParameterTypes.length == this.executable.getParameterCount() - 1) {
// Bug in javac: type array excludes enclosing instance parameter
// for inner classes with at least one generic constructor parameter,
// so access it with the actual parameter index lowered by 1
index = this.parameterIndex - 1;
}
//得到对应索引位置的参数对象
paramType = (index >= 0 && index < genericParameterTypes.length ?
genericParameterTypes[index] : computeParameterType());
}
//缓存
this.genericParameterType = paramType;
}
return paramType;
}
5 ServletServerHttpRequest
先看它的类图
下面是这4
个接口定义的方法
HttpMessage
接口:提供获取请求头的方法HttpRequest
接口:提供获取请求方式和请求URI
的方法HttpInputMessage
接口:提供获取请求体的方法ServerHttpRequest
接口:提供4
个方法
getPrincipal()
:得到已认证用户的Principal
实例getLocalAddress()
:获取接收请求的地址getRemoteAddress()
:获取发送请求的地址getAsyncRequestControl()
:返回一个异步请求控件,以便将该请求置于异步状态
拥有的字段如下所示
public class ServletServerHttpRequest implements ServerHttpRequest {
protected static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
protected static final Charset FORM_CHARSET = StandardCharsets.UTF_8;
//原始的request对象
private final HttpServletRequest servletRequest;
@Nullable
private URI uri;
//请求头
@Nullable
private HttpHeaders headers;
//异步请求控件,以便将该请求置于异步状态
@Nullable
private ServerHttpAsyncRequestControl asyncRequestControl;
}
5.1 构造方法
/**
* Construct a new instance of the ServletServerHttpRequest based on the
* given {@link HttpServletRequest}.
* @param servletRequest the servlet request
*/
public ServletServerHttpRequest(HttpServletRequest servletRequest) {
Assert.notNull(servletRequest, "HttpServletRequest must not be null");
this.servletRequest = servletRequest;
}
构造方法只是简单给servletRequest
属性赋值
5.2 getHeaders()
方法,获取请求头
@Override
public HttpHeaders getHeaders() {
//没有缓存
if (this.headers == null) {
this.headers = new HttpHeaders();
/**
* 使用原始的request.getHeaderNames()方法得到所有请求头名字
* Enumeration类似于迭代器
*/
for (Enumeration<?> names = this.servletRequest.getHeaderNames(); names.hasMoreElements();) {
//得到下一个请求头的名字
String headerName = (String) names.nextElement();
//获取名字对应的值,值可能有多个
for (Enumeration<?> headerValues = this.servletRequest.getHeaders(headerName);
headerValues.hasMoreElements();) {
String headerValue = (String) headerValues.nextElement();
//保存到springmvc定义的请求头类中
this.headers.add(headerName, headerValue);
}
}
// HttpServletRequest exposes some headers as properties:
// we should include those if not already present
try {
/*****************************媒体类型**********************************/
//获取媒体类型,见7.2
MediaType contentType = this.headers.getContentType();
/**
* 上面获取请求头的媒体类型,如果获取不到,就调用原始的request.getContentType()
* 方法,得到内容类型,最后通过MediaType.parseMediaType(requestContentType)方法
* 将内容类型转化为媒体类型,最后再保存到请求头中
*/
if (contentType == null) {
String requestContentType = this.servletRequest.getContentType();
if (StringUtils.hasLength(requestContentType)) {
//将String转化为MediaType,见6.2
contentType = MediaType.parseMediaType(requestContentType);
//设置媒体类型
this.headers.setContentType(contentType);
}
}
/*****************************字符编码**********************************/
if (contentType != null && contentType.getCharset() == null) {
//原始的request.getCharacterEncoding()方法获取字符编码
String requestEncoding = this.servletRequest.getCharacterEncoding();
//设置了字符编码
if (StringUtils.hasLength(requestEncoding)) {
//String类型转换为Charset
Charset charSet = Charset.forName(requestEncoding);
Map<String, String> params = new LinkedCaseInsensitiveMap<>();
//复制原来所有参数,并添加新的字符编码参数
params.putAll(contentType.getParameters());
params.put("charset", charSet.toString());
//重新构建一个媒体类型对象,保存到请求头中
MediaType mediaType = new MediaType(contentType.getType(), contentType.getSubtype(), params);
this.headers.setContentType(mediaType);
}
}
}
catch (InvalidMediaTypeException ex) {
// Ignore: simply not exposing an invalid content type in HttpHeaders...
}
//修改请求头内容长度
if (this.headers.getContentLength() < 0) {
int requestContentLength = this.servletRequest.getContentLength();
if (requestContentLength != -1) {
this.headers.setContentLength(requestContentLength);
}
}
}
return this.headers;
}
该方法额外做了很多事
- 获取请求中所有请求头的
key
和value
,并将它们封装到一个HttpHeaders
对象中- 解析请求头中的
Content-Type
属性,将其封装为一个MediaType
对象- 解析请求的字符编码,将其封装为一个
Charset
对象,并保存到媒体类型参数parameters
中,见6.1
5.3 getBody()
方法, 获取请求体输入流
@Override
public InputStream getBody() throws IOException {
//该请求是表单提交的post请求,见5.4
if (isFormPost(this.servletRequest)) {
//得到表单提交的post请求的输入流
return getBodyFromServletRequestParameters(this.servletRequest);
}
//非表单提交,直接使用原始的request.getInputStream()方法获取请求体输入流
else {
return this.servletRequest.getInputStream();
}
}
5.4 isFormPost(HttpServletRequest request)
方法, 判断请求是不是表单提交的POST
请求
private static boolean isFormPost(HttpServletRequest request) {
//获取内容类型
String contentType = request.getContentType();
/**
* FORM_CONTENT_TYPE="application/x-www-form-urlencoded"
* 代表表单类型内容
*/
return (contentType != null && contentType.contains(FORM_CONTENT_TYPE) &&
HttpMethod.POST.matches(request.getMethod()));
}
5.5 getBodyFromServletRequestParameters(HttpServletRequest request)
方法,得到表单提交的post
请求的请求体输入流
/**
* Use {@link javax.servlet.ServletRequest#getParameterMap()} to reconstruct the
* body of a form 'POST' providing a predictable outcome as opposed to reading
* from the body, which can fail if any other code has used the ServletRequest
* to access a parameter, thus causing the input stream to be "consumed".
*/
private static InputStream getBodyFromServletRequestParameters(HttpServletRequest request) throws IOException {
//字节数组输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
//使用转换流转换为字符流
Writer writer = new OutputStreamWriter(bos, FORM_CHARSET);
//得到所有的请求参数
Map<String, String[]> form = request.getParameterMap();
//使用字符流的write()方法将请求参数写入字符流中
for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {
String name = nameIterator.next();
List<String> values = Arrays.asList(form.get(name));
for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext();) {
String value = valueIterator.next();
writer.write(URLEncoder.encode(name, FORM_CHARSET.name()));
if (value != null) {
writer.write('=');
writer.write(URLEncoder.encode(value, FORM_CHARSET.name()));
if (valueIterator.hasNext()) {
writer.write('&');
}
}
}
if (nameIterator.hasNext()) {
writer.append('&');
}
}
writer.flush();
//构建一个输入流
return new ByteArrayInputStream(bos.toByteArray());
}
对于表单提交的
POST
请求,它的请求参数也是放在请求体中的,但是当你调用request.getParameterMap()
方法得到所有的请求参数的时候,它内部会自动调用request.getInputStream()
方法获取请求体输入流,读取流中的数据,将其转换为Map
集合。而流中的数据我们知道,只能被读取一次,为了让后面继续能从流中读取数据,所以在此处它先调用request.getParameterMap()
方法得到所有的请求参数,然后通过IO
流将这个Map
集合中的内容放到输入流中,然后返回这个输入流
6 MediaType
决定响应给客户端的内容格式
下面是它的类图
MimeType
代表一个MIME
类型,支持从String
解析到MIME
类型值的功能MediaType
添加对http
规范参数的支持,我们可以看到类中定义了很多常量
下面是MediaType
类中定义的常量
public class MediaType extends MimeType implements Serializable {
private static final long serialVersionUID = 2069937152339670231L;
/**
* Public constant media type that includes all media ranges (i.e. "*/*").
*/
public static final MediaType ALL;
/**
* A String equivalent of {@link MediaType#ALL}.
*/
public static final String ALL_VALUE = "*/*";
/**
* Public constant media type for {@code application/atom+xml}.
*/
public static final MediaType APPLICATION_ATOM_XML;
/**
* A String equivalent of {@link MediaType#APPLICATION_ATOM_XML}.
*/
public static final String APPLICATION_ATOM_XML_VALUE = "application/atom+xml";
/**
* Public constant media type for {@code application/cbor}.
* @since 5.2
*/
public static final MediaType APPLICATION_CBOR;
/**
* A String equivalent of {@link MediaType#APPLICATION_CBOR}.
* @since 5.2
*/
public static final String APPLICATION_CBOR_VALUE = "application/cbor";
/**
* Public constant media type for {@code application/x-www-form-urlencoded}.
*/
public static final MediaType APPLICATION_FORM_URLENCODED;
/**
* A String equivalent of {@link MediaType#APPLICATION_FORM_URLENCODED}.
*/
public static final String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";
/**
* Public constant media type for {@code application/json}.
*/
public static final MediaType APPLICATION_JSON;
/**
* A String equivalent of {@link MediaType#APPLICATION_JSON}.
* @see #APPLICATION_JSON_UTF8_VALUE
*/
public static final String APPLICATION_JSON_VALUE = "application/json";
/**
* Public constant media type for {@code application/json;charset=UTF-8}.
* @deprecated as of 5.2 in favor of {@link #APPLICATION_JSON}
* since major browsers like Chrome
* <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=438464">
* now comply with the specification</a> and interpret correctly UTF-8 special
* characters without requiring a {@code charset=UTF-8} parameter.
*/
@Deprecated
public static final MediaType APPLICATION_JSON_UTF8;
/**
* A String equivalent of {@link MediaType#APPLICATION_JSON_UTF8}.
* @deprecated as of 5.2 in favor of {@link #APPLICATION_JSON_VALUE}
* since major browsers like Chrome
* <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=438464">
* now comply with the specification</a> and interpret correctly UTF-8 special
* characters without requiring a {@code charset=UTF-8} parameter.
*/
@Deprecated
public static final String APPLICATION_JSON_UTF8_VALUE = "application/json;charset=UTF-8";
/**
* Public constant media type for {@code application/octet-stream}.
*/
public static final MediaType APPLICATION_OCTET_STREAM;
/**
* A String equivalent of {@link MediaType#APPLICATION_OCTET_STREAM}.
*/
public static final String APPLICATION_OCTET_STREAM_VALUE = "application/octet-stream";
/**
* Public constant media type for {@code application/pdf}.
* @since 4.3
*/
public static final MediaType APPLICATION_PDF;
/**
* A String equivalent of {@link MediaType#APPLICATION_PDF}.
* @since 4.3
*/
public static final String APPLICATION_PDF_VALUE = "application/pdf";
/**
* Public constant media type for {@code application/problem+json}.
* @since 5.0
* @see <a href="https://tools.ietf.org/html/rfc7807#section-6.1">
* Problem Details for HTTP APIs, 6.1. application/problem+json</a>
*/
public static final MediaType APPLICATION_PROBLEM_JSON;
/**
* A String equivalent of {@link MediaType#APPLICATION_PROBLEM_JSON}.
* @since 5.0
*/
public static final String APPLICATION_PROBLEM_JSON_VALUE = "application/problem+json";
/**
* Public constant media type for {@code application/problem+json}.
* @since 5.0
* @see <a href="https://tools.ietf.org/html/rfc7807#section-6.1">
* Problem Details for HTTP APIs, 6.1. application/problem+json</a>
* @deprecated as of 5.2 in favor of {@link #APPLICATION_PROBLEM_JSON}
* since major browsers like Chrome
* <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=438464">
* now comply with the specification</a> and interpret correctly UTF-8 special
* characters without requiring a {@code charset=UTF-8} parameter.
*/
@Deprecated
public static final MediaType APPLICATION_PROBLEM_JSON_UTF8;
/**
* A String equivalent of {@link MediaType#APPLICATION_PROBLEM_JSON_UTF8}.
* @since 5.0
* @deprecated as of 5.2 in favor of {@link #APPLICATION_PROBLEM_JSON_VALUE}
* since major browsers like Chrome
* <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=438464">
* now comply with the specification</a> and interpret correctly UTF-8 special
* characters without requiring a {@code charset=UTF-8} parameter.
*/
@Deprecated
public static final String APPLICATION_PROBLEM_JSON_UTF8_VALUE = "application/problem+json;charset=UTF-8";
/**
* Public constant media type for {@code application/problem+xml}.
* @since 5.0
* @see <a href="https://tools.ietf.org/html/rfc7807#section-6.2">
* Problem Details for HTTP APIs, 6.2. application/problem+xml</a>
*/
public static final MediaType APPLICATION_PROBLEM_XML;
/**
* A String equivalent of {@link MediaType#APPLICATION_PROBLEM_XML}.
* @since 5.0
*/
public static final String APPLICATION_PROBLEM_XML_VALUE = "application/problem+xml";
/**
* Public constant media type for {@code application/rss+xml}.
* @since 4.3.6
*/
public static final MediaType APPLICATION_RSS_XML;
/**
* A String equivalent of {@link MediaType#APPLICATION_RSS_XML}.
* @since 4.3.6
*/
public static final String APPLICATION_RSS_XML_VALUE = "application/rss+xml";
/**
* Public constant media type for {@code application/stream+json}.
* @since 5.0
*/
public static final MediaType APPLICATION_STREAM_JSON;
/**
* A String equivalent of {@link MediaType#APPLICATION_STREAM_JSON}.
* @since 5.0
*/
public static final String APPLICATION_STREAM_JSON_VALUE = "application/stream+json";
/**
* Public constant media type for {@code application/xhtml+xml}.
*/
public static final MediaType APPLICATION_XHTML_XML;
/**
* A String equivalent of {@link MediaType#APPLICATION_XHTML_XML}.
*/
public static final String APPLICATION_XHTML_XML_VALUE = "application/xhtml+xml";
/**
* Public constant media type for {@code application/xml}.
*/
public static final MediaType APPLICATION_XML;
/**
* A String equivalent of {@link MediaType#APPLICATION_XML}.
*/
public static final String APPLICATION_XML_VALUE = "application/xml";
/**
* Public constant media type for {@code image/gif}.
*/
public static final MediaType IMAGE_GIF;
/**
* A String equivalent of {@link MediaType#IMAGE_GIF}.
*/
public static final String IMAGE_GIF_VALUE = "image/gif";
/**
* Public constant media type for {@code image/jpeg}.
*/
public static final MediaType IMAGE_JPEG;
/**
* A String equivalent of {@link MediaType#IMAGE_JPEG}.
*/
public static final String IMAGE_JPEG_VALUE = "image/jpeg";
/**
* Public constant media type for {@code image/png}.
*/
public static final MediaType IMAGE_PNG;
/**
* A String equivalent of {@link MediaType#IMAGE_PNG}.
*/
public static final String IMAGE_PNG_VALUE = "image/png";
/**
* Public constant media type for {@code multipart/form-data}.
*/
public static final MediaType MULTIPART_FORM_DATA;
/**
* A String equivalent of {@link MediaType#MULTIPART_FORM_DATA}.
*/
public static final String MULTIPART_FORM_DATA_VALUE = "multipart/form-data";
/**
* Public constant media type for {@code multipart/mixed}.
* @since 5.2
*/
public static final MediaType MULTIPART_MIXED;
/**
* A String equivalent of {@link MediaType#MULTIPART_MIXED}.
* @since 5.2
*/
public static final String MULTIPART_MIXED_VALUE = "multipart/mixed";
/**
* Public constant media type for {@code multipart/related}.
* @since 5.2.5
*/
public static final MediaType MULTIPART_RELATED;
/**
* A String equivalent of {@link MediaType#MULTIPART_RELATED}.
* @since 5.2.5
*/
public static final String MULTIPART_RELATED_VALUE = "multipart/related";
/**
* Public constant media type for {@code text/event-stream}.
* @since 4.3.6
* @see <a href="https://www.w3.org/TR/eventsource/">Server-Sent Events W3C recommendation</a>
*/
public static final MediaType TEXT_EVENT_STREAM;
/**
* A String equivalent of {@link MediaType#TEXT_EVENT_STREAM}.
* @since 4.3.6
*/
public static final String TEXT_EVENT_STREAM_VALUE = "text/event-stream";
/**
* Public constant media type for {@code text/html}.
*/
public static final MediaType TEXT_HTML;
/**
* A String equivalent of {@link MediaType#TEXT_HTML}.
*/
public static final String TEXT_HTML_VALUE = "text/html";
/**
* Public constant media type for {@code text/markdown}.
* @since 4.3
*/
public static final MediaType TEXT_MARKDOWN;
/**
* A String equivalent of {@link MediaType#TEXT_MARKDOWN}.
* @since 4.3
*/
public static final String TEXT_MARKDOWN_VALUE = "text/markdown";
/**
* Public constant media type for {@code text/plain}.
*/
public static final MediaType TEXT_PLAIN;
/**
* A String equivalent of {@link MediaType#TEXT_PLAIN}.
*/
public static final String TEXT_PLAIN_VALUE = "text/plain";
/**
* Public constant media type for {@code text/xml}.
*/
public static final MediaType TEXT_XML;
/**
* A String equivalent of {@link MediaType#TEXT_XML}.
*/
public static final String TEXT_XML_VALUE = "text/xml";
private static final String PARAM_QUALITY_FACTOR = "q";
//定义了所有的媒体类型,我们可以直接使用
static {
// Not using "valueOf' to avoid static init cost
ALL = new MediaType("*", "*");
APPLICATION_ATOM_XML = new MediaType("application", "atom+xml");
APPLICATION_CBOR = new MediaType("application", "cbor");
APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded");
APPLICATION_JSON = new MediaType("application", "json");
APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8);
APPLICATION_OCTET_STREAM = new MediaType("application", "octet-stream");
APPLICATION_PDF = new MediaType("application", "pdf");
APPLICATION_PROBLEM_JSON = new MediaType("application", "problem+json");
APPLICATION_PROBLEM_JSON_UTF8 = new MediaType("application", "problem+json", StandardCharsets.UTF_8);
APPLICATION_PROBLEM_XML = new MediaType("application", "problem+xml");
APPLICATION_RSS_XML = new MediaType("application", "rss+xml");
APPLICATION_STREAM_JSON = new MediaType("application", "stream+json");
APPLICATION_XHTML_XML = new MediaType("application", "xhtml+xml");
APPLICATION_XML = new MediaType("application", "xml");
IMAGE_GIF = new MediaType("image", "gif");
IMAGE_JPEG = new MediaType("image", "jpeg");
IMAGE_PNG = new MediaType("image", "png");
MULTIPART_FORM_DATA = new MediaType("multipart", "form-data");
MULTIPART_MIXED = new MediaType("multipart", "mixed");
MULTIPART_RELATED = new MediaType("multipart", "related");
TEXT_EVENT_STREAM = new MediaType("text", "event-stream");
TEXT_HTML = new MediaType("text", "html");
TEXT_MARKDOWN = new MediaType("text", "markdown");
TEXT_PLAIN = new MediaType("text", "plain");
TEXT_XML = new MediaType("text", "xml");
}
}
6.1 构造方法
/**
* Create a new {@code MediaType} for the given primary type and subtype.
* <p>The parameters are empty.
* @param type the primary type
* @param subtype the subtype
* @throws IllegalArgumentException if any of the parameters contain illegal characters
*/
public MediaType(String type, String subtype) {
super(type, subtype, Collections.emptyMap());
}
父类构造方法完成内容类型存储
//主类型
private final String type;
//亚型,进一步缩小范围
private final String subtype;
//参数
private final Map<String, String> parameters;
/**
* Create a new {@code MimeType} for the given type, subtype, and parameters.
* @param type the primary type
* @param subtype the subtype
* @param parameters the parameters (may be {@code null})
* @throws IllegalArgumentException if any of the parameters contains illegal characters
*/
public MimeType(String type, String subtype, @Nullable Map<String, String> parameters) {
Assert.hasLength(type, "'type' must not be empty");
Assert.hasLength(subtype, "'subtype' must not be empty");
checkToken(type);
checkToken(subtype);
this.type = type.toLowerCase(Locale.ENGLISH);
this.subtype = subtype.toLowerCase(Locale.ENGLISH);
//保存指定的参数
if (!CollectionUtils.isEmpty(parameters)) {
Map<String, String> map = new LinkedCaseInsensitiveMap<>(parameters.size(), Locale.ENGLISH);
parameters.forEach((attribute, value) -> {
checkParameters(attribute, value);
map.put(attribute, value);
});
this.parameters = Collections.unmodifiableMap(map);
}
else {
this.parameters = Collections.emptyMap();
}
}
主要将主类型
type
和亚型subtype
保存到MimeType
中
6.2 parseMediaType(String mediaType)
方法,将String
转换为MediaType
/**
* Parse the given String into a single {@code MediaType}.
* @param mediaType the string to parse
* @return the media type
* @throws InvalidMediaTypeException if the media type value cannot be parsed
*/
public static MediaType parseMediaType(String mediaType) {
MimeType type;
try {
//借助工具类将String转换为MimeType
type = MimeTypeUtils.parseMimeType(mediaType);
}
catch (InvalidMimeTypeException ex) {
throw new InvalidMediaTypeException(ex);
}
try {
//拷贝MimeType对象的值构建一个MediaType
return new MediaType(type.getType(), type.getSubtype(), type.getParameters());
}
catch (IllegalArgumentException ex) {
throw new InvalidMediaTypeException(mediaType, ex.getMessage());
}
}
使用
MimeTypeUtils.parseMimeType(mediaType)
方法将String
转换为MimeType
类型,最后再拷贝MimeType
对象中的值,封装为MediaType
类型
7 HttpHeaders
一种数据结构,表示http
请求或响应的标头
先看它的类图
下面是HttpHeaders
类中定义的常量,表示http
请求或响应可能包含的标头
public class HttpHeaders implements MultiValueMap<String, String>, Serializable {
/**
* The HTTP {@code Accept} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.2">Section 5.3.2 of RFC 7231</a>
*/
public static final String ACCEPT = "Accept";
/**
* The HTTP {@code Accept-Charset} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.3">Section 5.3.3 of RFC 7231</a>
*/
public static final String ACCEPT_CHARSET = "Accept-Charset";
/**
* The HTTP {@code Accept-Encoding} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.4">Section 5.3.4 of RFC 7231</a>
*/
public static final String ACCEPT_ENCODING = "Accept-Encoding";
/**
* The HTTP {@code Accept-Language} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.5">Section 5.3.5 of RFC 7231</a>
*/
public static final String ACCEPT_LANGUAGE = "Accept-Language";
/**
* The HTTP {@code Accept-Ranges} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7233#section-2.3">Section 5.3.5 of RFC 7233</a>
*/
public static final String ACCEPT_RANGES = "Accept-Ranges";
/**
* The CORS {@code Access-Control-Allow-Credentials} response header field name.
* @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
/**
* The CORS {@code Access-Control-Allow-Headers} response header field name.
* @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
/**
* The CORS {@code Access-Control-Allow-Methods} response header field name.
* @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
/**
* The CORS {@code Access-Control-Allow-Origin} response header field name.
* @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
/**
* The CORS {@code Access-Control-Expose-Headers} response header field name.
* @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
/**
* The CORS {@code Access-Control-Max-Age} response header field name.
* @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
/**
* The CORS {@code Access-Control-Request-Headers} request header field name.
* @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
/**
* The CORS {@code Access-Control-Request-Method} request header field name.
* @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
/**
* The HTTP {@code Age} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.1">Section 5.1 of RFC 7234</a>
*/
public static final String AGE = "Age";
/**
* The HTTP {@code Allow} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.4.1">Section 7.4.1 of RFC 7231</a>
*/
public static final String ALLOW = "Allow";
/**
* The HTTP {@code Authorization} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7235#section-4.2">Section 4.2 of RFC 7235</a>
*/
public static final String AUTHORIZATION = "Authorization";
/**
* The HTTP {@code Cache-Control} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2">Section 5.2 of RFC 7234</a>
*/
public static final String CACHE_CONTROL = "Cache-Control";
/**
* The HTTP {@code Connection} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7230#section-6.1">Section 6.1 of RFC 7230</a>
*/
public static final String CONNECTION = "Connection";
/**
* The HTTP {@code Content-Encoding} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.2.2">Section 3.1.2.2 of RFC 7231</a>
*/
public static final String CONTENT_ENCODING = "Content-Encoding";
/**
* The HTTP {@code Content-Disposition} header field name.
* @see <a href="https://tools.ietf.org/html/rfc6266">RFC 6266</a>
*/
public static final String CONTENT_DISPOSITION = "Content-Disposition";
/**
* The HTTP {@code Content-Language} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.3.2">Section 3.1.3.2 of RFC 7231</a>
*/
public static final String CONTENT_LANGUAGE = "Content-Language";
/**
* The HTTP {@code Content-Length} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7230#section-3.3.2">Section 3.3.2 of RFC 7230</a>
*/
public static final String CONTENT_LENGTH = "Content-Length";
/**
* The HTTP {@code Content-Location} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.4.2">Section 3.1.4.2 of RFC 7231</a>
*/
public static final String CONTENT_LOCATION = "Content-Location";
/**
* The HTTP {@code Content-Range} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7233#section-4.2">Section 4.2 of RFC 7233</a>
*/
public static final String CONTENT_RANGE = "Content-Range";
/**
* The HTTP {@code Content-Type} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.1.5">Section 3.1.1.5 of RFC 7231</a>
*/
public static final String CONTENT_TYPE = "Content-Type";
/**
* The HTTP {@code Cookie} header field name.
* @see <a href="https://tools.ietf.org/html/rfc2109#section-4.3.4">Section 4.3.4 of RFC 2109</a>
*/
public static final String COOKIE = "Cookie";
/**
* The HTTP {@code Date} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.2">Section 7.1.1.2 of RFC 7231</a>
*/
public static final String DATE = "Date";
/**
* The HTTP {@code ETag} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
*/
public static final String ETAG = "ETag";
/**
* The HTTP {@code Expect} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-5.1.1">Section 5.1.1 of RFC 7231</a>
*/
public static final String EXPECT = "Expect";
/**
* The HTTP {@code Expires} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.3">Section 5.3 of RFC 7234</a>
*/
public static final String EXPIRES = "Expires";
/**
* The HTTP {@code From} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.1">Section 5.5.1 of RFC 7231</a>
*/
public static final String FROM = "From";
/**
* The HTTP {@code Host} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7230#section-5.4">Section 5.4 of RFC 7230</a>
*/
public static final String HOST = "Host";
/**
* The HTTP {@code If-Match} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7232#section-3.1">Section 3.1 of RFC 7232</a>
*/
public static final String IF_MATCH = "If-Match";
/**
* The HTTP {@code If-Modified-Since} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7232#section-3.3">Section 3.3 of RFC 7232</a>
*/
public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
/**
* The HTTP {@code If-None-Match} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7232#section-3.2">Section 3.2 of RFC 7232</a>
*/
public static final String IF_NONE_MATCH = "If-None-Match";
/**
* The HTTP {@code If-Range} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7233#section-3.2">Section 3.2 of RFC 7233</a>
*/
public static final String IF_RANGE = "If-Range";
/**
* The HTTP {@code If-Unmodified-Since} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7232#section-3.4">Section 3.4 of RFC 7232</a>
*/
public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
/**
* The HTTP {@code Last-Modified} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7232#section-2.2">Section 2.2 of RFC 7232</a>
*/
public static final String LAST_MODIFIED = "Last-Modified";
/**
* The HTTP {@code Link} header field name.
* @see <a href="https://tools.ietf.org/html/rfc5988">RFC 5988</a>
*/
public static final String LINK = "Link";
/**
* The HTTP {@code Location} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.2">Section 7.1.2 of RFC 7231</a>
*/
public static final String LOCATION = "Location";
/**
* The HTTP {@code Max-Forwards} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-5.1.2">Section 5.1.2 of RFC 7231</a>
*/
public static final String MAX_FORWARDS = "Max-Forwards";
/**
* The HTTP {@code Origin} header field name.
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454</a>
*/
public static final String ORIGIN = "Origin";
/**
* The HTTP {@code Pragma} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.4">Section 5.4 of RFC 7234</a>
*/
public static final String PRAGMA = "Pragma";
/**
* The HTTP {@code Proxy-Authenticate} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7235#section-4.3">Section 4.3 of RFC 7235</a>
*/
public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate";
/**
* The HTTP {@code Proxy-Authorization} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7235#section-4.4">Section 4.4 of RFC 7235</a>
*/
public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
/**
* The HTTP {@code Range} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7233#section-3.1">Section 3.1 of RFC 7233</a>
*/
public static final String RANGE = "Range";
/**
* The HTTP {@code Referer} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.2">Section 5.5.2 of RFC 7231</a>
*/
public static final String REFERER = "Referer";
/**
* The HTTP {@code Retry-After} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.3">Section 7.1.3 of RFC 7231</a>
*/
public static final String RETRY_AFTER = "Retry-After";
/**
* The HTTP {@code Server} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.4.2">Section 7.4.2 of RFC 7231</a>
*/
public static final String SERVER = "Server";
/**
* The HTTP {@code Set-Cookie} header field name.
* @see <a href="https://tools.ietf.org/html/rfc2109#section-4.2.2">Section 4.2.2 of RFC 2109</a>
*/
public static final String SET_COOKIE = "Set-Cookie";
/**
* The HTTP {@code Set-Cookie2} header field name.
* @see <a href="https://tools.ietf.org/html/rfc2965">RFC 2965</a>
*/
public static final String SET_COOKIE2 = "Set-Cookie2";
/**
* The HTTP {@code TE} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7230#section-4.3">Section 4.3 of RFC 7230</a>
*/
public static final String TE = "TE";
/**
* The HTTP {@code Trailer} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7230#section-4.4">Section 4.4 of RFC 7230</a>
*/
public static final String TRAILER = "Trailer";
/**
* The HTTP {@code Transfer-Encoding} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7230#section-3.3.1">Section 3.3.1 of RFC 7230</a>
*/
public static final String TRANSFER_ENCODING = "Transfer-Encoding";
/**
* The HTTP {@code Upgrade} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7230#section-6.7">Section 6.7 of RFC 7230</a>
*/
public static final String UPGRADE = "Upgrade";
/**
* The HTTP {@code User-Agent} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.3">Section 5.5.3 of RFC 7231</a>
*/
public static final String USER_AGENT = "User-Agent";
/**
* The HTTP {@code Vary} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.4">Section 7.1.4 of RFC 7231</a>
*/
public static final String VARY = "Vary";
/**
* The HTTP {@code Via} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7230#section-5.7.1">Section 5.7.1 of RFC 7230</a>
*/
public static final String VIA = "Via";
/**
* The HTTP {@code Warning} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7234#section-5.5">Section 5.5 of RFC 7234</a>
*/
public static final String WARNING = "Warning";
/**
* The HTTP {@code WWW-Authenticate} header field name.
* @see <a href="https://tools.ietf.org/html/rfc7235#section-4.1">Section 4.1 of RFC 7235</a>
*/
public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
/**
* An empty {@code HttpHeaders} instance (immutable).
* @since 5.0
*/
public static final HttpHeaders EMPTY = new ReadOnlyHttpHeaders(new LinkedMultiValueMap<>());
/**
* Pattern matching ETag multiple field values in headers such as "If-Match", "If-None-Match".
* @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
*/
private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?");
private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = new DecimalFormatSymbols(Locale.ENGLISH);
private static final ZoneId GMT = ZoneId.of("GMT");
/**
* Date formats with time zone as specified in the HTTP RFC to use for formatting.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a>
*/
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US).withZone(GMT);
/**
* Date formats with time zone as specified in the HTTP RFC to use for parsing.
* @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a>
* 两个日期转换器
*/
private static final DateTimeFormatter[] DATE_PARSERS = new DateTimeFormatter[] {
DateTimeFormatter.RFC_1123_DATE_TIME,
DateTimeFormatter.ofPattern("EEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy", Locale.US).withZone(GMT)
};
//真正用来保存请求头数据的地方
final MultiValueMap<String, String> headers;
}
7.1 构造方法
/**
* Construct a new, empty instance of the {@code HttpHeaders} object.
* <p>This is the common constructor, using a case-insensitive map structure.
*/
public HttpHeaders() {
this(CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH)));
}
/**
* Construct a new {@code HttpHeaders} instance backed by an existing map.
* <p>This constructor is available as an optimization for adapting to existing
* headers map structures, primarily for internal use within the framework.
* @param headers the headers map (expected to operate with case-insensitive keys)
* @since 5.1
*/
public HttpHeaders(MultiValueMap<String, String> headers) {
Assert.notNull(headers, "MultiValueMap must not be null");
this.headers = headers;
}
初始化了一个空的
MultiValueMap
集合
7.2 getContentType()
方法,获取MediaType
/**
* Return the {@linkplain MediaType media type} of the body, as specified
* by the {@code Content-Type} header.
* <p>Returns {@code null} when the content-type is unknown.
*/
@Nullable
public MediaType getContentType() {
//获取Content-Type头的第一个值
String value = getFirst(CONTENT_TYPE);
//会自动将String转化为MediaType
return (StringUtils.hasLength(value) ? MediaType.parseMediaType(value) : null);
}
/**
* Return the first header value for the given header name, if any.
* @param headerName the header name
* @return the first header value, or {@code null} if none
*/
@Override
@Nullable
public String getFirst(String headerName) {
return this.headers.getFirst(headerName);
}
- 只会获取
Content-Type
头的第一个值- 会自动将
String
转化为MediaType
7.3 setContentType(@Nullable MediaType mediaType)
方法,将MediaType
保存到请求头中
/**
* Set the {@linkplain MediaType media type} of the body,
* as specified by the {@code Content-Type} header.
*/
public void setContentType(@Nullable MediaType mediaType) {
if (mediaType != null) {
Assert.isTrue(!mediaType.isWildcardType(), "Content-Type cannot contain wildcard type '*'");
Assert.isTrue(!mediaType.isWildcardSubtype(), "Content-Type cannot contain wildcard subtype '*'");
set(CONTENT_TYPE, mediaType.toString());
}
else {
remove(CONTENT_TYPE);
}
}
- 并不是保存
MediaType
对象,而是先将器转化为String
,再保存到headers
属性中- 如果传入的
MediaType
对象为null
,那么就会删除headers
属性中保存的Content-Type
头
看了
7.2
和7.3
这两个方法,其他类型的请求头设置和获取的过程大同小异,MediaType
只是一个过渡类型,方便用户使用,springmvc
在内部保存的是String
类型的字符串。
7.4 readOnlyHttpHeaders(HttpHeaders headers)
方法,将原始的头对象封装为一个ReadOnlyHttpHeaders
只读头对象
/**
* Apply a read-only {@code HttpHeaders} wrapper around the given headers,
* if necessary.
* @param headers the headers to expose
* @return a read-only variant of the headers, or the original headers as-is
*/
public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) {
Assert.notNull(headers, "HttpHeaders must not be null");
return (headers instanceof ReadOnlyHttpHeaders ? headers : new ReadOnlyHttpHeaders(headers.headers));
}
将原始的头对象封装为一个只读头对象,只读,不能写入
8 EmptyBodyCheckingHttpInputMessage
该类是
AbstractMessageConverterMethodArgumentResolver
的嵌套类,而AbstractMessageConverterMethodArgumentResolver
是RequestResponseBodyMethodProcessor
的父类
private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage {
//请求头
private final HttpHeaders headers;
//请求体输入流
@Nullable
private final InputStream body;
/**
* 构造对象是就会检查请求体输入流中是否包含数据
* 不包含数据,则将body字段置为null
*/
public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException {
//获取请求头,见5.2
this.headers = inputMessage.getHeaders();
//获取请求体输入流,见5.3
InputStream inputStream = inputMessage.getBody();
if (inputStream.markSupported()) {
inputStream.mark(1);
this.body = (inputStream.read() != -1 ? inputStream : null);
inputStream.reset();
}
else {
/**
* 这是一个回退输入流,给了用户第二次读取的机会
* 主要验证这个输入流中是否包含数据,
* 通过read()方法读取数据之后,可以对数据操作验证之后,再
* 通过unread(b)方法将读取数据再放回流中
*/
PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
//读取流中的数据
int b = pushbackInputStream.read();
//-1表明流中没有数据,此时不需要流
if (b == -1) {
this.body = null;
}
//流中有数据,将刚刚读取的数据放回流中
else {
this.body = pushbackInputStream;
pushbackInputStream.unread(b);
}
}
}
//接口的两个方法
@Override
public HttpHeaders getHeaders() {
return this.headers;
}
@Override
public InputStream getBody() {
return (this.body != null ? this.body : StreamUtils.emptyInput());
}
//通过该方法得到请求体中是否包含数据
public boolean hasBody() {
return (this.body != null);
}
}
- 它实现了
HttpInputMessage
接口,那么它就必须实现getBody()
方法和getHeaders()
方法- 它通过
PushbackInputStream
回退流检查请求体输入流中是否包含数据
9 HttpMessageConverter
这是
springmvc
中定义的转换器,本质上就是序列化和反序列化
- 反序列化:请求体输入流数据读取为用户需要的对象
- 序列化:将处理器方法的返回值写入响应体输出流中
public interface HttpMessageConverter<T> {
//判断该消息转换器是否可以将请求体数据读取为clazz类型数据
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
//判断该消息转换器是否可以将clazz类型数据写入响应体中
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
//获取该消息转换器支持的媒体类型
List<MediaType> getSupportedMediaTypes();
//将请求体数据转换为clazz类型对象并返回
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
//将t对象写入响应体中
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
这
5
个方法都很好理解,canRead()
方法判断是否可读,判断成功之后,直接调用read()
方法,读取请求体的内容;canWrite()
也是一样。
9.1 GenericHttpMessageConverter
除此之外,它还有一个子接口GenericHttpMessageConverter
,提供更加通用的消息类型转换(json
和对象互转),我们看到很熟悉的 MappingJackson2HttpMessageConverter
就是它的实现类。
public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T> {
boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType);
T read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType);
void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
下面是几种常见的消息转换器的类图
9.2 默认的消息转换器
使用
<mvc:annotation-driven/>
时,springmvc
自动向里面放入了8
个消息转换器,顺序如下所示
ByteArrayHttpMessageConverter
、StringHttpMessageConverter
、ResourceHttpMessageConverter
、ResourceRegionHttpMessageConverter
、SourceHttpMessageConverter
、AllEncompassingFormHttpMessageConverter
、MappingJackson2XmlHttpMessageConverter
、MappingJackson2HttpMessageConverter
,这两个http
消息转换器需要导入jackson
包
10 RequestResponseBodyAdviceChain
该类封装了有
@ControllerAdvice
注解的标注的RequestBodyAdvice
接口和ResponseBodyAdvice
接口的实现类对象,并同时实现这两个接口,用户通过它来调用RequestBodyAdvice
接口和ResponseBodyAdvice
接口方法
10.1 RequestBodyAdvice
该接口,类似于拦截器,它会在消息转换器读取请求之前执行
public interface RequestBodyAdvice {
boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType);
//消息转换器的canRead()方法执行之前执行
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;
//消息转换器的canRead()方法执行之后执行
Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
//请求体数据为空时执行
@Nullable
Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
}
springmvc
默认注册了一个JsonViewRequestBodyAdvice
,用来处理参数上的@JsonView
注解
10.2 beforeBodyRead()
方法,类型转换之前调用
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
/**
* 遍历执行所有RequestBodyAdvice对象的beforeBodyRead()方法
* getMatchingAdvice()方法会获取匹配(初步筛选)的RequestBodyAdvice对象,见10.3
*/
for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) {
//supports()方法判断通过才会执行拦截
if (advice.supports(parameter, targetType, converterType)) {
request = advice.beforeBodyRead(request, parameter, targetType, converterType);
}
}
return request;
}
经过两轮筛选才会执行
beforeBodyRead()
方法
- 处理器方法参数所在的类在
RequestBodyAdvice
拦截器的拦截范围中RequestBodyAdvice
拦截器的supports()
方法判定通过
10.3 获取匹配(初步筛选)的RequestBodyAdvice
对象
private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) {
//根据要求类型得到对应的拦截器集合
List<Object> availableAdvice = getAdvice(adviceType);
if (CollectionUtils.isEmpty(availableAdvice)) {
return Collections.emptyList();
}
List<A> result = new ArrayList<>(availableAdvice.size());
for (Object advice : availableAdvice) {
/**
* 所有被@ControllerAdvice注解标注的类都被封装为ControllerAdviceBean
* 具体的见springmvc--4--HandlerAdapter处理器适配器
*/
if (advice instanceof ControllerAdviceBean) {
ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
/**
* @ControllerAdvice注解可以指定它的生效范围
* isApplicableToBeanType()方法判断参数所在的类是否在它的生效范围之内
* 就相当于与拦截器拦截的路径
*/
if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) {
continue;
}
advice = adviceBean.resolveBean();
}
if (adviceType.isAssignableFrom(advice.getClass())) {
result.add((A) advice);
}
}
return result;
}
//根据类型返回对应的拦截器集合
private List<Object> getAdvice(Class<?> adviceType) {
if (RequestBodyAdvice.class == adviceType) {
return this.requestBodyAdvice;
}
else if (ResponseBodyAdvice.class == adviceType) {
return this.responseBodyAdvice;
}
else {
throw new IllegalArgumentException("Unexpected adviceType: " + adviceType);
}
}
- 用户可在
@ControllerAdvice
注解中配置RequestBodyAdvice
拦截器的拦截路径- 该方法会判断参数所在的类是否在
RequestBodyAdvice
拦截器拦截范围中- 只会返回能够拦截参数所在的类的
RequestBodyAdvice
对象
11 ServletServerHttpResponse
先看它的类图
下面是这6
个接口定义的方法
HttpMessage
接口:提供获取响应头的方法,和获取请求头用的是一个接口
HttpOutputMessage
接口:提供获取响应体输出流的方法
ServerHttpRequest
接口:提供3
个方法
setStatusCode()
:设置响应状态码flush()
:刷新之后,响应头就不能被修改了,只能在原来的基础上添加close()
:关闭响应,释放创建的资源
拥有的字段如下所示
public class ServletServerHttpResponse implements ServerHttpResponse {
//原生的响应对象
private final HttpServletResponse servletResponse;
//响应头
private final HttpHeaders headers;
//是否使用只读响应头
private boolean headersWritten = false;
//是否使用响应体
private boolean bodyUsed = false;
}
11.1 构造方法
/**
* Construct a new instance of the ServletServerHttpResponse based on the given {@link HttpServletResponse}.
* @param servletResponse the servlet response
*/
public ServletServerHttpResponse(HttpServletResponse servletResponse) {
Assert.notNull(servletResponse, "HttpServletResponse must not be null");
this.servletResponse = servletResponse;
//这里创建一个响应头对象,见11.2
this.headers = new ServletResponseHttpHeaders();
}
构造方法只做了两件事情
- 缓存原生的响应对象
- 创建一个响应头对象
11.2 ServletResponseHttpHeaders
这是
ServletServerHttpResponse
的内部类,扩展了7
章节的HttpHeaders
/**
* Extends HttpHeaders with the ability to look up headers already present in
* the underlying HttpServletResponse.
*
* <p>The intent is merely to expose what is available through the HttpServletResponse
* i.e. the ability to look up specific header values by name. All other
* map-related operations (e.g. iteration, removal, etc) apply only to values
* added directly through HttpHeaders methods.
*
* @since 4.0.3
*/
private class ServletResponseHttpHeaders extends HttpHeaders {
private static final long serialVersionUID = 3410708522401046302L;
@Override
public boolean containsKey(Object key) {
return (super.containsKey(key) || (get(key) != null));
}
@Override
@Nullable
public String getFirst(String headerName) {
String value = servletResponse.getHeader(headerName);
if (value != null) {
return value;
}
else {
return super.getFirst(headerName);
}
}
@Override
public List<String> get(Object key) {
Assert.isInstanceOf(String.class, key, "Key must be a String-based header name");
Collection<String> values1 = servletResponse.getHeaders((String) key);
if (headersWritten) {
return new ArrayList<>(values1);
}
boolean isEmpty1 = CollectionUtils.isEmpty(values1);
List<String> values2 = super.get(key);
boolean isEmpty2 = CollectionUtils.isEmpty(values2);
if (isEmpty1 && isEmpty2) {
return null;
}
List<String> values = new ArrayList<>();
if (!isEmpty1) {
values.addAll(values1);
}
if (!isEmpty2) {
values.addAll(values2);
}
return values;
}
}
11.3 getHeaders()
方法,获取响应头
@Override
public HttpHeaders getHeaders() {
//根据配置得到对应类型的响应头对象,见7.4
return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
}
获取响应头的时候可以根据
headersWritten
字段的值来得到对应类型的响应头对象
true
:只读响应头,只读,不能写入false
:普通的响应头