当前位置: 代码迷 >> 综合 >> Spring 注解面面通 之 @ModelAttribute 深入源码解析
  详细解决方案

Spring 注解面面通 之 @ModelAttribute 深入源码解析

热度:76   发布时间:2024-01-17 01:00:01.0

??@ModelAttribute应用请参照《Spring 注解面面通 之 @ModelAttribute 模型属性绑定全解》。

??@ModelAttribute可以注释方法和参数,那么源码分析也从这两个方向进行分析。

??源码流程

??流程图中,左侧为@ModelAttribute注释方法的处理过程,右侧为@ModelAttribute注释参数的处理过程。

??需要说明的是,流程图中,由于整个过程比较复杂,图中只是简单绘制与@ModelAttribute注解处理相关的内容,其他部分未列其内。此图并不代表完整的流程。

在这里插入图片描述

??注意:@ModelAttribute注释方法解析逻辑主要集中在ModelFactory.invokeModelAttributeMethods(...)@ModelAttribute注释方法参数解析主要集中在ModelAttributeMethodProcessor.resolveArgument(...)

??源码解析过程涉及其他类,主要是为了说明Spring MVC整个流程的处理逻辑,避免对@ModelAttribute解析部分理解出现偏差。

??源码解析

??1) DispatcherServlet.doDispatch(...)

??① 取得请求HttpServletRequest异步管理器WebAsyncManager

??② 确认请求HttpServletRequest是否为multipart/form-data类型请求。

??③ 通过请求HttpServletRequestDispatcherServlet.properties提供的默认策略中获取org.springframework.web.servlet.HandlerMapping列表,通过其取得对应的处理器。

??④ 通过请求HttpServletRequestDispatcherServlet.properties提供的默认策略中获取org.springframework.web.servlet.HandlerAdapter列表,通过其取得中处理器对应的处理器适配器。

??⑤ 当Http方法为GETHEAD时,处理Last-Modified请求标头。

??⑥ 按照执行链上拦截器的顺序调用其preHandle(...)方法,此时,若preHandle(...)返回false,会按照执行链上拦截器的反顺序调用其afterCompletion(...)方法。

???⑦ 调用处理器的handle(...)进行实际处理。

??⑧ 若是异步请求,则异步进行处理器处理,DispatcherServlet.doDispatch(...)返回。

??⑨ 若中返回的ModelAndView不包括视图,则配置默认视图。

??⑩ 按照执行链上拦截器的反顺序调用其postHandle(...)方法。

??? 调用DispatcherServlet.processDispatchResult(...)方法处理最终结果,并在方法最后按照执行链上拦截器的反顺序调用其afterCompletion(...)方法。

??? 此外,在最外层try { ... } catch (Exception ex) { ... } catch (Throwable err) { ... }catch { ... }中也会后按照执行链上拦截器的反顺序调用其afterCompletion(...)方法,但仔细查看代码会发现,这种情况发生概率几乎为零。

??DispatcherServlet.doDispatch(...)源码注释:

/*** 分发请求给处理程序.* 处理程序将按照顺序处理Servlet的HandlerMappings.* HandlerAdapter将通过查询Servlet已配置的HandlerAdapter来获取,以找到第一个支持handler类的HandlerAdapter.* 所有的HTTP方法都由这个方法处理.* 由HandlerAdapter或处理程序自己决定哪些方法是可接受的.* @param request 当前请求实例.* @param response 当前响应实例.* @throws Exception .*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;// 处理器执行链.HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {
    ModelAndView mv = null;Exception dispatchException = null;try {
    // 确认是否multipart/form-data请求.processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 获得当前请求的处理器.mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {
    noHandlerFound(processedRequest, response);return;}// 获取当前请求的处理器适配器.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 处理last-modified请求头.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {
    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (logger.isDebugEnabled()) {
    logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);}if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    return;}}// 调用拦截器的applyPreHandle方法.if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;}// 实际调用处理器方法处理请求.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {
    return;}// 处理默认视图.applyDefaultViewName(processedRequest, mv);// 调用拦截器的applyPostHandle方法.mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {
    dispatchException = ex;}catch (Throwable err) {
    // 从4.3开始,同时会处理从handler方法抛出的错误,使它们可以用于@ExceptionHandler方法和其他场景.dispatchException = new NestedServletException("Handler dispatch failed", err);}// 处理转发结果.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {
    // 调用拦截器的triggerAfterCompletion方法.triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {
    // 调用拦截器的triggerAfterCompletion方法.triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {
    if (asyncManager.isConcurrentHandlingStarted()) {
    // 代替postHandle和afterCompletion.if (mappedHandler != null) {
    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {
    // 清除multipart/form-data请求使用的所有资源.if (multipartRequestParsed) {
    cleanupMultipart(processedRequest);}}}
}

??2) RequestMappingHandlerAdapter.handleInternal(...)

??① 检查给定请求,其包含的Http方法是否支持、若Session设置为必需(requireSession标志),其是否包含可用Session

???② 若Session设置为需线程同步(synchronizeOnSession)调用且Session可用时,使用synchronized取得重入锁之后,调用invokeHandlerMethod(...)

??③ 若Session设置为需线程同步(synchronizeOnSession)调用且Session不可用时,无需使用synchronized,直接调用invokeHandlerMethod(...)

??④ 若Session未设置为需线程同步(synchronizeOnSession)时,无需使用synchronized,直接调用invokeHandlerMethod(...)

??⑤ 若响应不包含Cache-Control标头,则对其进行处理。

/*** 内部处理方法.*/
@Override
protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    ModelAndView mav;// 检查给定的请求以获得支持的方法和所需的会话(如果有).checkRequest(request);// 如果需要,在synchronized块中执行invokeHandlerMethod.if (this.synchronizeOnSession) {
    HttpSession session = request.getSession(false);// 有HttpSession可用->需要互斥.if (session != null) {
    Object mutex = WebUtils.getSessionMutex(session);synchronized (mutex) {
    mav = invokeHandlerMethod(request, response, handlerMethod);}}else {
    // 没有HttpSession可用->不需要互斥.mav = invokeHandlerMethod(request, response, handlerMethod);}}else {
    // 根本不需要会话同步.mav = invokeHandlerMethod(request, response, handlerMethod);}// 响应头中不包含Cache-Control头.进行缓存处理.if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
    if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
    applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);}else {
    prepareResponse(response);}}return mav;
}

??3) RequestMappingHandlerAdapter.invokeHandlerMethod(...)

???① 使用ServletWebRequest包装HttpServletRequest请求实例。

??② 获取WebDataBinderFactory实例,主要目的是搜索由@InitBinder注释的方法。

??③ 获取ModelFactory实例,主要目的是搜索由@ModelAttribute注释,且无@RequestMapping注释的方法。

??④ 从给定的HandlerMethod定义创建ServletInvocableHandlerMethod。

??⑤ 设置默认初始化的参数解析器。

??⑥ 设置默认初始化的返回值处理器。

??⑦ 创建ModelAndViewContainer实例,用于装载模型数据和视图信息。

??⑧ 调用ModelFactory.initModel(...)进行模型数据初始化。

??⑨ 对异步请求操作进行处理。

??⑩ 调用实际处理器方法。

???? 从webRequestmodelFactory中获取ModelView设置到ModelAndView中。

/*** 如果需要视图解析,则调用RequestMapping处理程序方法准备ModelAndView.*/
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    // 包装请求实例.ServletWebRequest webRequest = new ServletWebRequest(request, response);try {
    // 获取WebDataBinderFactory实例.WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);// 获取ModelFactory实例.ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);// 从给定的HandlerMethod定义创建ServletInvocableHandlerMethod.ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);// 设置参数解析器.if (this.argumentResolvers != null) {
    invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}// 设置返回值解析器.if (this.returnValueHandlers != null) {
    invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}// 设置WebDataBinderFactory.invocableMethod.setDataBinderFactory(binderFactory);// 设置参数名称发现器.invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);// ModelAndView容器.ModelAndViewContainer mavContainer = new ModelAndViewContainer();// 设置属性.mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));// 初始化数据.modelFactory.initModel(webRequest, mavContainer, invocableMethod);// 设置重定向时忽略默认数据.mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);// 设置异步请求超时时间.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();if (logger.isDebugEnabled()) {
    logger.debug("Found concurrent result value [" + result + "]");}// ServletInvocableHandlerMethod包装.invocableMethod = invocableMethod.wrapConcurrentResult(result);}// 调用和处理请求.invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {
    return null;}// 获取ModelAndView.return getModelAndView(mavContainer, modelFactory, webRequest);}finally {
    webRequest.requestCompleted();}
}

??4) ModelFactory.invokeModelAttributeMethods(...)

??注意: 此方法是处理@ModelAttribute注释方法的重点方法,完成了方法调用,且将处理过的键值对设置到ModelMap中。

??① 判断是否存在符合条件的方法,由@ModelAttribute注释,且无@RequestMapping注释的方法。

??② 若存在符合条件的方法,则循环遍历方法列表进行处理。

??③ 若当前ControllerModelAndView已包含@ModelAttribute注解的name属性值,且@ModelAttribute注解的binding值为false时,跳过当前遍历。

??④ 调用@ModelAttribute注释的方法,获取其返回值。

???⑤ 若返回值类型不为void,则通过getNameForReturnValue(...)方法获取值作为键,中返回值作为值,设置到ModelAndViewContainerModelMap中。

/*** 调用Model属性方法来填充Model.* 仅当模型中不存在属性时才添加属性.*/
private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container)throws Exception {
    // 存在@ModelAttribute注释的方法.while (!this.modelMethods.isEmpty()) {
    // 获取下一个待处理的ModelMethod.InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();// 取得ModelMethod的@ModelAttribute注解.ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);Assert.state(ann != null, "No ModelAttribute annotation");// 若当前Controller的ModelAndView已包含@ModelAttribute注解的name属性值,// 且@ModelAttribute注解的binding值为false时,跳过当前遍历.if (container.containsAttribute(ann.name())) {
    if (!ann.binding()) {
    container.setBindingDisabled(ann.name());}continue;}// 调用@ModelAttribute注释的方法.Object returnValue = modelMethod.invokeForRequest(request, container);// @ModelAttribute注释的方法返回值类型不是void类型.if (!modelMethod.isVoid()){
    // 获取返回值的名称.String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());// 若注解@ModelAttribute的binding属性为false,设置ModelAndViewContainer// 的bindingDsiabled属性为returnValueName.if (!ann.binding()) {
    container.setBindingDisabled(returnValueName);}// 若当前Controller的ModelAndView不包含returnValueName,则设置到Controller的ModelAndView中.if (!container.containsAttribute(returnValueName)) {
    container.addAttribute(returnValueName, returnValue);}}}
}

??5) ServletInvocableHandlerMethod.invokeAndHandle(...)

??① 调用处理方法请求并获取返回值。

??② 设置请求的响应状态。

??③ 处理@ResponseStatus注解,并映射到响应状态。

??④ 调用HandlerMethodReturnValueHandlerComposite处理返回值。

/*** 调用方法并通过配置的HandlerMethodReturnValueHandler处理返回值.* @param webRequest 当前请求实例.* @param mavContainer 请求的ModelAndViewContainer.* @param providedArgs 与类型匹配的给定参数(未解析).*/
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
    // 调用方法请求并获取返回值.Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);// 设置响应状态.setResponseStatus(webRequest);// 返回值为空.if (returnValue == null) {
    // 1.请求是否满足"未被修改"条件.// 2.@ResponseStatus设置的响应状态代码不为空.// 3.请求是否已在处理程序中完全处理.// 满足以上三个条件,设置请求已处理完成,直接返回.if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
    mavContainer.setRequestHandled(true);return;}}// @ResponseStatus设置的响应状态原因不为空,设置请求已处理完成,直接返回.else if (StringUtils.hasText(getResponseStatusReason())) {
    mavContainer.setRequestHandled(true);return;}// 设置请求未处理完成.mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {
    // 调用HandlerMethodReturnValueHandlerComposite处理返回值.this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}catch (Exception ex) {
    if (logger.isTraceEnabled()) {
    logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);}throw ex;}
}

??6) HandlerMethodArgumentResolverComposite.getArgumentResolver(...)

??① 从缓存中获取默认初始化的HandlerMethodArgumentResolver

??② 遍历argumentResolvers,找到适合当前MethodParameterHandlerMethodArgumentResolver

??③ 调用具体HandlerMethodArgumentResolver进行参数解析处理。

/*** 查找支持给MethodParameter的解析程序.*/
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    // 从缓存中获取HandlerMethodArgumentResolver.HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);if (result == null) {
    // 遍历argumentResolvers,找到适合当前MethodParameter的HandlerMethodArgumentResolver.for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
    if (logger.isTraceEnabled()) {
    logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +parameter.getGenericParameterType() + "]");}// 调用具体的解析程序处理.if (methodArgumentResolver.supportsParameter(parameter)) {
    result = methodArgumentResolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;
}

??7) ModelAttributeMethodProcessor.resolveArgument(...)

??① 当参数被@ModelAttribute注释或annotationNotRequiredtrue且参数非基本类型时才会调用此方法参数解析器。

???② 直接根据@ModelAttribute注解的value属性值或参数名,从ModelAndViewContainerModelMap中取值。

??③ 进行参数绑定和校验,即处理@InitBinder注解。

???④ 将经过绑定和校验的参数值添加到ModelAndViewContainerModelMap中。

/*** 从Model模型中解析参数,如果找不到参数,则使用其默认值(如果可用)将其实例化. * 然后通过数据绑定用请求值填充Model属性,如果参数上存在@java.validation.Valid,* 则可以选择验证该属性.*/
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");// 根据@ModelAttribute参数注释(如果存在)// 或根据参数类型确定给定方法参数的模型属性名称.String name = ModelFactory.getNameForParameter(parameter);// 方法参数中查找@ModelAttribute注解.ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);if (ann != null) {
    mavContainer.setBinding(name, ann.binding());}Object attribute = null;BindingResult bindingResult = null;// ModelAndViewContainer包含此名称,则从ModelAndViewContainer中取得属性的值.if (mavContainer.containsAttribute(name)) {
    attribute = mavContainer.getModel().get(name);}else {
    // 创建属性实例.try {
    attribute = createAttribute(name, parameter, binderFactory, webRequest);}catch (BindException ex) {
    if (isBindExceptionRequired(parameter)) {
    // No BindingResult parameter -> fail with BindExceptionthrow ex;}// Otherwise, expose null/empty value and associated BindingResultif (parameter.getParameterType() == Optional.class) {
    attribute = Optional.empty();}bindingResult = ex.getBindingResult();}}// 绑定过程无异常.if (bindingResult == null) {
    // Bean属性绑定和验证,绑定失败时跳过.WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);if (binder.getTarget() != null) {
    if (!mavContainer.isBindingDisabled(name)) {
    bindRequestParameters(binder, webRequest);}validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
    throw new BindException(binder.getBindingResult());}}// Value type adaptation, also covering java.util.Optionalif (!parameter.getParameterType().isInstance(attribute)) {
    attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);}bindingResult = binder.getBindingResult();}// 在模型末尾添加解析属性和BindingResult.Map<String, Object> bindingResultModel = bindingResult.getModel();mavContainer.removeAttributes(bindingResultModel);mavContainer.addAllAttributes(bindingResultModel);return attribute;
}

??总结

??整体的流程还是比较复杂的,原因在于所有的处理都掺杂在主流程的处理过程中,这使得解析过程看起来有些繁杂。

???源码解析基于spring-framework-5.0.5.RELEASE版本源码。

??若文中存在错误和不足,欢迎指正!

  相关解决方案