本文分析 Spring 注解驱动的实现原理,包括请求匹配、参数解析、结果处理三个方面。
- 请求匹配:核心注解 @RequestMapping。核心实现 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter。
- 参数解析:包括 @RequestBody/@RequestParam/@PathVariable/@CookieValue 等注解。核心接口 HandlerMethodArgumentResolver。
结果处理:核心注解 @ResponseBody,返回 REST 风格。核心接口 HandlerMethodReturnValueHandler。无论是 @RequestBody 还是 @ResponseBody,最终都是委托 HttpMessageConverter 进行媒体类型转换。
1. REST 注解驱动规范
REST 注解驱动有 Servlet 规范和 Spring 自定义两种,Spring 不支持 Servlet REST 规范。
Servlet REST 规范注解:(Spring 不支持)javax.ws.rs 包。大致可以分为以下几类:
- 请求方式:@POST/@GET、@HttpMethod
- 媒体类型:@Consumes/@Produces
- 参数解析:@PathParam/@HeaderParam/@CookieParam/@QueryParam/@FormParam
- SpringMVC 自定义注解:org.springframework.web.bind.annotation 包。
- 请求匹配:@RequestMapping。包括请求路径、请求方式、媒体类型、请求头、请求参数等匹配方式。相当于 Servlet 中的请求方式和媒体类型的复合注解。
- 参数解析:@RequestParam/@PathVariable/@CookieValue
- REST风格:@ResponseBody/@RequestBody。Spring 既要支持传统的 ModelAndView,也要支持 REST 风格,使用 @ResponseBody 自动切换到 REST 风格。
说明:可以看到,Spring 自定义注解规范和 Servlet REST 规范其实都差不多。但 Spring 生态做的比较强大,根本不叼 Serlvet,自己重新实现了一套注解驱动。
2. 请求匹配
请求匹配的核心 API 是 @RequestMapping 注解,处理这个注解的相关类有:
- RequestMappingHandlerMapping:处理器映射器。保存
<mapping, handler>
的映射关系,可以根据 request 查找对应的 handler。 - RequestMappingHandlerAdapter:处理器适配器。将 handler 包装成 HandlerAdapter 后执行,进行参数解析和结果转换。
RequestMappingInfo:保存 @RequestMapping 解析后的信息。
2.1 @RequestMapping
RequestMappingHandlerMapping 初始化时会解析所有的 @RequestMapping 注解,保存
<mapping, handler>
的映射关系。当收到请求时,会根据 request 从映射关系中查找对应的 handler 执行。初始化阶段:RequestMappingHandlerMapping 初始化时,调用 initHandlerMethods 方法解析所有的 @RequestMapping 注解。
- getCandidateBeanNames:获取容器中的所有 beanNames。
- isHandler:判断是否有 @Controller 或 @RequestMapping 注解。
- getMappingForMethod:读取该方法或类上的 @RequestMapping 注解,解析为 RequestMappingInfo。
- createHandlerMethod:将该方法包装成 HandlerMethod。
- 运行阶段:当收到请求时,会根据 request 从映射关系表中查找对应的 handler 执行。
- getMatchingCondition:实际上是调用 RequestMappingInfo#getMatchingCondition 逐一匹配 URL、请求方式、媒体类型等。
2.2 Handler 注册
Spring 为了方便 Handler 查找,注册了根据 mapping 或 url 等多种映射关系。 ```java private final Map> registry = new HashMap<>(); private final Map mappingLookup = new LinkedHashMap<>(); private final MultiValueMap urlLookup = new LinkedMultiValueMap<>(); private final Map > nameLookup = new ConcurrentHashMap<>();
- getMatchingCondition:实际上是调用 RequestMappingInfo#getMatchingCondition 逐一匹配 URL、请求方式、媒体类型等。
// mapping参数:将 @RequestMapping 注解解析成 RequestMappingInfo // handler参数:method方法所在的实例bean的beanName。执行时需要从容器中获取具体的bean // method参数:具体的的方法 public void register(T mapping, Object handler, Method method) { this.readWriteLock.writeLock().lock(); try { HandlerMethod handlerMethod = createHandlerMethod(handler, method); // 1. mappingLookup:RequestMappingInfo -> HandlerMethod this.mappingLookup.put(mapping, handlerMethod);
// 2. urlLookup:url -> RequestMappingInfo
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
// 3. nameLookup:mappingname -> HandlerMethod
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
// 4. registry:RequestMappingInfo -> MappingRegistration
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
} finally {
this.readWriteLock.writeLock().unlock();
}
}
<a name="rF5XG"></a>
## 2.3 Handler 查找
查找时,先根据 request.url 查找对应的 mapping。如果匹配不到,再匹配所有的 mapping。如果匹配到多个,取优先级最高的那个。所谓的 mapping 匹配,实际上是调用 RequestMappingInfo#getMatchingCondition(request) 方法,也就是我们在 @RequestMapping 上配置的条件。
```java
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
// 1. 先根据url匹配
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
// 2. 匹配所有的mappings
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
// 3. 匹配多个,取优先级最高的那一个
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
throw new IllegalStateException();
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
// 4. 未匹配到任何handler
} else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
return info.getMatchingCondition(request);
}
3. 执行 Handler
RequestMappingHandlerAdapter 是 Spring 中最主要的 HandlerAdapter,它的内部封装了参数解析和结果类型转换的相关组件。我们先看一下 RequestMappingHandlerAdapter 内部默认初始化的组件。
3.1 初始化
(1)属性
RequestMappingHandlerAdapter 中最重要的属性是参数处理器 HandlerMethodArgumentResolver 和结果处理器 HandlerMethodReturnValueHandler。
private HandlerMethodArgumentResolverComposite argumentResolvers; // 1. 参数处理器
private HandlerMethodReturnValueHandlerComposite returnValueHandlers; // 2. 结果处理器
// 3. 以下都是一些重要的辅助工具类
// 3.1. 内容协商。默认从请求头中获取Accept字段:HeaderContentNegotiationStrategy
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
// 3.2. 媒体类型MediaType格式转换器
private List<HttpMessageConverter<?>> messageConverters;
// 3.3. 解析方法参数名称
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
(2)afterPropertiesSet
@Override
public void afterPropertiesSet() {
// 1. @ControllerAdvice
initControllerAdviceCache();
// 2. 参数处理器
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
// 3. @InitBinder 和 @ModelAttribute 处理器
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
// 4. 结果处理器
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
说明:RequestMappingHandlerAdapter 启动时,初始化了四类组件,分别是:
- requestResponseBodyAdvice:(了解)见《Spring-MVC @ControllerAdvice 三种使用场景》。
- initBinderArgumentResolvers:(基本不用)见《Spring-MVC @InitBinder 和 @ModelAttribute 使用场景》。
- argumentResolvers:(核心)参数处理器,用于解析方法参数。比如,RequestParamMethodArgumentResolver 用于处理 @RequestParam 注解,RequestResponseBodyMethodProcessor 用于处理 @RequestBody 注解。
returnValueHandlers:(核心)结果处理器,用于处理 handler 的返回值。比如,RequestResponseBodyMethodProcessor 就是用于处理 @ResponseBody 注解。
3.2 执行
执行的调用链如下:RequestMappingHandlerAdapter#handle -> handleInternal -> invokeHandlerMethod。invokeHandlerMethod 方法配置 ServletInvocableHandlerMethod 的参数处理器和结果处理器,并委托其执行。
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 1. (极少使用)@InitBinder 和 @ModelAttribute处理
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 2. (核心逻辑)配置invocableMethod的参数处理器、结果处理器、参数名称解析器
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// 3. (如果是REST风格,只是用来标记请求是否已经处理完成)ModelAndViewContainer
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
// 4. (基本不用)异步处理
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 5. (核心逻辑)执行任务
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 6. (REST风格时返回null)返回ModelAndView
return getModelAndView(mavContainer, modelFactory, webRequest);
} finally {
webRequest.requestCompleted();
}
}
说明:RequestMappingHandlerAdapter 主要处理异步调用的问题,当业务代码返回的结果是 WebAsyncTask 时,AsyncTaskMethodReturnValueHandler 处理器会开启异步调用。异步调用使用较少,我们先忽略这块。实际上,invokeHandlerMethod 方法的核心非常简单。 ```java ServletInvocableHandlerMethod invocableMethod = new ServletInvocableHandlerMethod(handlerMethod); invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
invocableMethod.invokeAndHandle(webRequest, mavContainer);
**说明:**ServletInvocableHandlerMethod 负责参数解析和结果处理。你可能认为它很复杂,其实也非常简单,参数解析和结果处理分别委托给了 argumentResolvers 和 returnValueHandlers。
```java
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 1. 执行Handler,包括参数解析
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
} else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
// 2. 结果处理
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 1. 参数解析
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
// 2. 直接反射调用method.invoke执行
return doInvoke(args);
}
说明:ServletInvocableHandlerMethod 执行时,先委托 argumentResolvers 解析方法的参数,然后调用反射执行handler,最后委托 returnValueHandlers 对返回值的结果进行处理。
4. 参数解析
参数解析时,会调用 RequestMappingHandlerAdapter 配置的 argumentResolvers 参数解析器,逐一解析方法的参数。Spring 默认的参数解析器 HandlerMethodArgumentResolver 有近 20 个,我们会分析其中两个比较典型的参数解析器:
- RequestParamMethodArgumentResolver:解析 @RequestParam 注解。
RequestResponseBodyMethodProcessor:解析 @RequestBody 注解。
// ServletInvocableHandlerMethod
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
// 参数名称解析器parameterNameDiscoverer
// 参数解析器resolvers
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
return args;
}
4.1 @RequestParam
RequestParamMethodArgumentResolver 首先会判断参数或方法上是否有 @RequestParam 注解。如果有,解析时会调用 request.getParameterValues(name) 获取其 value 值。是不是很简单呢?
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
...
Object arg = null;
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
return arg;
}
4.2 @RequestBody
RequestResponseBodyMethodProcessor 同时处理 @RequestBody 和 @ResponseBody 两个注解。本小节先看一下 @RequestBody 是如何处理的。
RequestResponseBodyMethodProcessor 实现了 HandlerMethodArgumentResolver 接口。参数解析时,根据请求头中的 Content-Type 参数类型进行解析。resolveArgument 解析参数时,最终调用 readWithMessageConverters 方法。protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
contentType = inputMessage.getHeaders().getContentType();
Class<?> contextClass = parameter.getContainingClass();
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
if (targetClass == null) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message;
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
// HttpMessageConverter根据媒体类型来解析参数
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
// (核心代码)参数解析
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
} else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
return body;
}
说明:readWithMessageConverters 方法先获取请求头中的 Content-Type 参数类型。最终调用具体的 HttpMessageConverter 来解析该媒体类型。
5. 结果处理
Spring 默认有多个结果处理器 HandlerMethodReturnValueHandler,在这里我们只关注 REST 注解 @ResponseBody 的处理器 RequestResponseBodyMethodProcessor。
5.1 @ResponseBody
本小节继续分析 RequestResponseBodyMethodProcessor 对 @ResponseBody 注解的处理。RequestResponseBodyMethodProcessor 同时实现了 HandlerMethodReturnValueHandler 接口。调用链为 handleReturnValue -> writeWithMessageConverters。处理的过程如下:
- 解析方法返回值类型。
- 获取响应的媒体类型 MediaType。如果指定了响应的 Content-Type,则直接获取。未指定,则获取请求的 Accept 值和服务端可以生成的类型,取一个优先级最高的类型。
根据媒体类型选择合适的转换器。选择支持该 MediaType 的 HttpMessageConverter,转换成对应的格式,并发送 restful。
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
Object body;
Class<?> valueType;
Type targetType;
// 1. 解析返回值类型
if (value instanceof CharSequence) {
body = value.toString();
valueType = String.class;
targetType = String.class;
} else {
body = value;
valueType = getReturnValueType(body, returnType);
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
...
// 2. 解析Http响应的媒体类型
MediaType selectedMediaType = null;
MediaType contentType = outputMessage.getHeaders().getContentType();
if (contentType != null && contentType.isConcrete()) {
selectedMediaType = contentType;
} else {
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
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) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
} else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
}
// 3. 根据媒体类型MediaType响应请求
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
addContentDispositionHeader(inputMessage, outputMessage);
// 4. (核心代码)类型转换
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
} else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
return;
}
}
}
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
说明:writeWithMessageConverters 方法根据请求的 Accept 类型,选择合适的 HttpMessageConverter 处理数据。可以看到,SpringMVC 中使用 HttpMessageConverter 进行媒体类型转换。
6. 总结时刻
推荐阅读
每天用心记录一点点。内容也许不重要,但习惯很重要!