RedCloud Help

Spring MVC

初探Spring MVC请求处理流程

我们先了解一下Spring MVc中的核心组件和大致处理流程:

1

2

3

4

5

6

7

main

DispatcherServlet

HandlerMapping

Controller

ModelAndView

ViewResolver

View

从上图中可以看出:

  1. 1 DispathcherServlet是SpringMvc中的前端控制器(Front Controller),负责接收Request并将Request转发给对应的处理组件。

  2. 2 HandlerMapping是Spring MVC中完成url到Controller映射的组件。DispatcherServlet接收Request,然后从HandlerMapping查找处理Request的Controller。

  3. 3 Controller处理Request,并返回ModelAndView对象,Controller是SpringMvc中负责处理Request的组件(类似于Struts2中的Action),ModelAndView是封装结果的视图的组件。

  4. 4、5、6视图解析器ModelAndView对象并返回对应的视图客户端。

在前面壮介中我们已经大致了解到,容器初始化时会建立所有url和controller中的method的对应关系,爆仓到handlerMapping中,用户请求是根据Request请求的url快速定位到Controller中的某个方法。在Spring中先将url和Controller的对应关系,保存到Map<url,Controller>中。web容器启动时会通知Spring初始化容器(加载Bean的定义信息和初始化所有单例Bean),然后SpringMVC会遍历容器中的Bean,获取每一个controller中的所有方法访问的url,然后url和controller保存到一个Map中;这样就可以根据Request快速定位到controller,因为最终处理Request的是Controller中的方法,Map中只保留了url和controller中的对应关系,所有要根据Request的url进一步确认controller中的method,这一步工作的原理就是拼接controller的url(controller上@requestmapping的值)和方法的url(method上@reqeustMapping的值),与request的url进行匹配,找到匹配的哪个方法;确认处理请求的method后,接下来的任务就是参数绑定,把request中参数绑定到方法的形参上,这一步是整个请求处理过程中最复杂的一个步骤。

Spring MVC九大组件

HandlerMappings

HandlerMapping是用于查找Handler的,也就是处理器,具体的表现形式可以是类也可以是方法。比如,标注了@RequestMapping的每个method都可以看成是一个Handler,由Handler来负责实际的请求处理。HandlerMapping在请求到达之后,它的作用便是找到请求相应的处理器Handler和Interceptors。

HandlerAdapters

从名字上看,这是一个适配器。因为SpringMVC中handler可以是任意形式的,只要能够处理请求变形,但是把请求交给Servlet的时候,由于Servlet的方法结构都是如doService(HttpServletRequest req,HttpServletResponse resp) 这样的形式,让固定的Servlet处理方法调用Handler来进行处理,这一步工作便是HandlerAdapter要做的事

HandlerExceptionResolvers

从这个组件的名字上看,这个就是用来处理Handler过程中产生的异常情况的组件。具体来说,此组件的作用是根据以后设置ModelAndView,之后再交给render()方法进行渲染,而render()便将ModelAndView渲染成页面。不过有一点,HandlerExceptionResolver只是用于解析对请求做处理阶段产生的异常,而渲染阶段的异常则不归他管了,这也是SpringMVC组件设计的一大原则分工明确互不干涉。

ViewResolvers

视图解析器,相信大家对这个应该都很熟悉了。因为通常在SpringMVC的配置文件中,都会配置上一个接口的实现类来进行视图的解析。这个组件的主要作用,便是将Spring类型的视图名和Locale解析为View类型的视图。这个接口只有一个resolveViewName()方法。从方法的定义就可以看出,Controller层返回的string类型的视图名viewName,最终会在这里被解析成为view。view是用来渲染页面的,也就是说,它会将程序返回的参数和数据填入模板中,最终生成html文件。ViewResovler在这个过程中,主要做两件大事,即,ViewResolver会找到渲染所用的模板(使用什么模板来渲染?)和所用的技术(其实也就是视图的类型,如JSP还是其他)填入参数。默认情况下,SpringMVC会为类型视图的。

RequestToViewNameTranslator

这个组件的作用,在于从Request中获取viewName。因为ViewResolver是根据ViewName查找View,但有的Handler处理完成之后,没有设置View也没有设置ViewName,便要通过这个组件来从Request中查找viewName.

LocaleResolver

在上面我们又看到ViewResolver的resolveViewName()方法,需要两个参数。呢么第二个参数Locale是从哪里来的,这就是LocaleResolver要做的事了。LocalResolver用于从reqeust中解析出Locale,在中国大陆地区,Locale当然就会zh-CN之类,用来表示一个区域。这个类也是i18n的基础。

ThemeResolver

从名字便可看出,这个类是用来解析主题的。主题,就是样式,图片以及他们所形成的显示效果的集合。SpringMVC中一套主题对应一个properties文件,里面存放着跟当前主题相关的所有资源,如图片,css央视等。创建主题非常简单,只需准备好资源,然后新建一个“主题名.properties”并将资源设置进去,放在classpath下,便可以在页面中使用了。SpringMVC中跟主题有关的类有ThemeResolver,ThemeSource和Theme。ThemeResolver负责从request中解析出主题名,ThemeSource则根据主题名找到具体的主题,其抽象也就是Theme,通过Theme来获取主题和具体的资源。

MultipartResolver

其实这是一个大家熟悉的组件,MultipartResolver用于处理上传请求,通过将普通的Reqeust包装成MultipartHttpServletRequest来实现。MultipartHttpServletReqeust可以通过getFile()直接获取文件,如果是多个文件上传,还可以通过调用getFileMap得到Map<FileName,File>这样的结构。MultipartResolver的作用就是用来封装普通的request,使其拥有处理文件上传的功能。

FlashMapManager

说到FlashMapManager,就得先提一下FlashMap。 FlashMap用于重定向Redirect时的参数数据传递,比如,在处理用户订单提交时,为了避免重复提交,可以处理完post请求后redirect到一个get请求,这个get请求可以用来显示订单详情之类的信息。这样做虽然可以规避用户刷新重新提交表单的问题,但是这个页面上要显示订单的信息,呢这些数据哪里去获取呢,因为redirect重定向是没有传递参数这一功能的,如果不想把参数写进url(其实也不推荐这么做,url有长度限制不说,把参数都直接暴露,敢接也不安全),呢么就可以通过flashMap来传递。只需要在redirect之前,将要传递的数据写入request(可以通过ServletRequestAttributes.getRequest()获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE中,这样在redirect之后的handler中Spring就会自动将其设置到Modle中,在现实订单信息的页面上,就可以直接从Model中取得数据了。而FlashMapManager就是用来管理FlashMap的。

SpringMVC源码分析

根据上面分析的springMVC工作机制,从三个部分来分析SpringMVC的源代码。

  1. ApplicationContext初始化使用Map保存所有url和Controller类的对应关系;

  2. 根据请求url找到对应的controller,并从controller中找到处理请求的方法。

  3. Request参数绑定到方法的形参,执行方法处理请求,并返回结果视图。

初始化阶段

我们首先找到DispatcherServlet这个类,必然是寻找init()方法。然后,我们发现其init方法其实在夫泪HttpServletBean中,其源码如下:

@Override public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // Set bean properties from init parameters. PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { // 定位资源 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); // 加载配置信息 ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); } throw ex; } } // Let subclasses do whatever initialization they like. initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } }

我们看到在这段代码中,有调用了一个重要的initServletBean()方法。进入initServletBean()方法看到以下源码:

@Override protected final void initServletBean() throws ServletException { getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'"); if (this.logger.isInfoEnabled()) { this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started"); } long startTime = System.currentTimeMillis(); try { this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException ex) { this.logger.error("Context initialization failed", ex); throw ex; } catch (RuntimeException ex) { this.logger.error("Context initialization failed", ex); throw ex; } if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + elapsedTime + " ms"); } }

这段代码中主要的逻辑就是初始化IOC容器,最终会调用refresh()方法,前面的壮介中对IOC容器的初始化细节我们已经装模,在这里不再赘述。我们看到上面的代码中,IOC容器初始化之后,最后又调用了onRefresh()方法。这个方法最终是在DisptcherServlet中实现,来看源码:

// 初始化策略 protected void initStrategies(ApplicationContext context) { // 多文件上传的组件 initMultipartResolver(context); // 初始化本地语言环境 initLocaleResolver(context); // 初始化模板处理器 initThemeResolver(context); // handlerMapping initHandlerMappings(context); // 初始化参数适配器 initHandlerAdapters(context); // 初始化异常拦截器 initHandlerExceptionResolvers(context); // 初始化视图预处理器 initRequestToViewNameTranslator(context); // 初始化视图转换器 initViewResolvers(context); // FlashMap管理器 initFlashMapManager(context); }

到这一步就完成了SpringMVC的九大组件的初始化。接下来,我们来看url和controller的关系是如何建立的呢?HandlerMapping的子类AbstractDetectingUrlHandlerMapping实现了initApplicationContext()方法,所以我们直接看子类中的初始化容器方法。

@Override public void initApplicationContext() throws ApplicationContextException { super.initApplicationContext(); detectHandlers(); } /** * Register all handlers found in the current ApplicationContext. * <p>The actual URL determination for a handler is up to the concrete * {@link #determineUrlsForHandler(String)} implementation. A bean for * which no such URLs could be determined is simply not considered a handler. * @throws org.springframework.beans.BeansException if the handler couldn't be registered * @see #determineUrlsForHandler(String) */ protected void detectHandlers() throws BeansException { ApplicationContext applicationContext = obtainApplicationContext(); if (logger.isDebugEnabled()) { logger.debug("Looking for URL mappings in application context: " + applicationContext); } // 获取applicationContext容器中所有bean的name String[] beanNames = (this.detectHandlersInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) : applicationContext.getBeanNamesForType(Object.class)); // Take any bean name that we can determine URLs for. // 便利beanNames,并找到这些bean对应的url for (String beanName : beanNames) { // 找bean上的所有url(controller上的url+方法上的url),改方法由对应的子类实现 String[] urls = determineUrlsForHandler(beanName); if (!ObjectUtils.isEmpty(urls)) { // URL paths found: Let's consider it a handler. // 保存urls和beanname的对应关系,put it to Map<urls,beanName> // 该方法在夫泪AbstractUrlHandlerMapping中实现 registerHandler(urls, beanName); } else { if (logger.isDebugEnabled()) { logger.debug("Rejected bean name '" + beanName + "': no URL paths identified"); } } } }

determineUrlsForHandler(string beanName)方法的作用是获取每个controller中的url,不同的子类有不同的实现,这是一个典型的模板设计模式。因为开发中我们用的最多的就是用注解来配置controller中的url,beanNameUrlHandlerMapping是AbstractDetectingUrlHandlerMapping的子类,处理注解性的的url映射,所有我们这里以BeanNameUrlHandlerMapping来进行分析。我们看BeanNameUrlHandlerMapping是如何查beanName上所有映射的url。

@Override protected String[] determineUrlsForHandler(String beanName) { List<String> urls = new ArrayList<>(); if (beanName.startsWith("/")) { urls.add(beanName); } String[] aliases = obtainApplicationContext().getAliases(beanName); for (String alias : aliases) { if (alias.startsWith("/")) { urls.add(alias); } } return StringUtils.toStringArray(urls); }

到这里HandlerMapping组件就已经建立所有url和controller的对应关系。

运行阶段

着一步步是请求触发的,所有入口为DispatcherServlet的核心方法为doService(),doService()中的核心逻辑由doDispatch()实现,源代码如下:

// 中央控制器,控制请求的转发 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 { // 1.检查是否文件上传的请求 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. // 2.取得处理当前请求的controller,这里也成为handler处理器, // 第一步骤的意义就在这里体现,这里并不是直接返回controller, // 而是返回的handlerExecutionChain请求处理器链对象, // 该对象封装了handler和interceptors mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. // 3. 获取处理request的处理器适配器 handler adapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. // 处理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; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. // 4.实际的处理器处理请求,返回结果视图对象 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } // 结果视图对象的处理 applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { // 请求成功响应之后的方法 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }

getHandler(processedRequest)方法实际上就是从HandlerMapping中找到url和controller的对应关系。也就是Map<url,Controller>.我们知道,最终处理Request的controller中的方法,我们现在只是知道controller,我们如何确认controller中处理request的方法呢?继续往下看。

从Map<urls,beanName>中取得controller后,经过拦截器的预处理方法,再通过反射获取该方法上的注解和参数,解析方法和参数上的注解,然后反射用方法获取ModelAndView结果视图。最后,调用的就是RequestMappingHandlerAdapter的handler()中的核心逻辑由handlerInternal(request,response,handler)实现。

@Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; checkRequest(request); // Execute invokeHandlerMethod in synchronized block if required. if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No synchronization on session demanded at all... mav = invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; }

整个处理过程中最核心的逻辑其实就是拼接controller的url和方法的url,与request的url进行匹配,找到匹配的方法。

@Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { // 如果请求url为https://localhost:8080/web/hello.json,则lookupPath=web/hello.json String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); if (logger.isDebugEnabled()) { logger.debug("Looking up handler method for path " + lookupPath); } this.mappingRegistry.acquireReadLock(); try { // 遍历controller上的所有方法,获取url匹配的方法。 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); if (logger.isDebugEnabled()) { if (handlerMethod != null) { logger.debug("Returning handler method [" + handlerMethod + "]"); } else { logger.debug("Did not find handler method for [" + lookupPath + "]"); } } return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } }

通过上面代码分析,已经可以找到处理request的controller中的方法了,现在看如何解析该方法上的参数,并反射调用该方法。

// 获取处理请求的方法,执行并返回结果视图 @Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); try { WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); 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 + "]"); } invocableMethod = invocableMethod.wrapConcurrentResult(result); } invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } }

invocableMethod.invokeAndHandle()最终要实现的目的就是:完成request中的参数和方法参数上数据的绑定。Spring MVC中提供两种request参数到方法中参数的绑定方式:

  1. 通过注解进行绑定,@RequestParam

  2. 通过参数名称进行绑定。

使用注解进行绑定,我们只要在方法参数前面声明@RequestParam("name"),就可以将request中参数name的值绑定到方法的该参数上。使用参数名称进行绑定的前提是必须要获取方法中参数的名称,java反射只提供了获取方法的参数的类型,并没有提供获取参数名称的方法。SpringMvc结果这个问题的方法是用asm框架读取字节码文件,来获取方法的参数名称。asm框架是一个字节码操作框架,关于asm更多介绍可以参考其官网。个人建议,使用注解来完成参数绑定,这样就可以省去asm框架的读取字节码操作。

@Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "' with arguments " + Arrays.toString(args)); } Object returnValue = doInvoke(args); if (logger.isTraceEnabled()) { logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "] returned [" + returnValue + "]"); } return returnValue; } /** * Get the method argument values for the current request. */ private 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]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (this.argumentResolvers.supportsParameter(parameter)) { try { args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex); } throw ex; } } if (args[i] == null) { throw new IllegalStateException("Could not resolve method parameter at index " + parameter.getParameterIndex() + " in " + parameter.getExecutable().toGenericString() + ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i)); } } return args; }

关于asm框架获取方法参数的部分,这里就不再进行分析了。感兴趣的小伙伴可以继续深入了解这个处理过程。

到这里,方法的参数值列表也获取到了,就可以直接进行方法的调用了。整个请求过程中最复杂的一步就是在这里了。到这里整个请求处理郭广昌的关键步骤都已经了解了。理解了Spring MVC中的请求处理流程,整个代码还是比较清晰的。最后我们再来梳理一下Spring MVC核心组件的关联关系:

6根据ModelAndView使用ViewResolver进行解析

7ViewResolver解析得到View

response,freemarker,jsp,json,xml

request

1遍历HandlerMapping集合

2找到对应的HandlerMapping并得到HandlerExecutionChain包括拦截器

3使用handlerExecutionChain中的Handler\n便利HandlerAdapter集合找到支持此handlerAdapter

4使用HandlerAdapter得到ModelAndView

核心实现类

5异常处理 前面4个步骤可能会发生异常 使用HandlerExceptionResolver策略解决

DispatcherServlet

ViewResolver

View

client

HandlerMapping

HandlerAdapter

HandlerMethod

a>HandlerMethod是一个封装了Method以及Parameter的helper class 在HandlerMapping中被构造并在HandlerAdapter中被使用

RequestMappingHandlerAdapter

RequestMappingHandlerAdapter.-解析方法的参数RequestMappingHandlerAdapter内部使用HandlerMethodArgumentResolverComposite对象处理该对象内部拥有HandlerMethodArgumentResolver集合

HandlerMethodArgumentResolver

处理方法的返回值RequestMappingHandlerAdapter内部使用HandlerMethodReturnValueHandlerComposite对象处理该对象内部拥有HandlerMethodReturnValueHandler集合

HandlerMethodReturnValueHandler

实现类之一处理ReqeustParam注解修饰的参数

ReqeustParamMethodArgumentResolver

实现类之一处理返回值类型为ModelAndView的方法

ModelAndViewMethodReturnValueHandler

实现类之一处理RequestBody注解修饰的参数

RequestResponseBodyMethodProcessor

实现类之一处理ResponseBody注解修饰的返回值

Spring MVC使用优化建议

上面我们已经对Spring MVC的工作原理和源码进行分析,在这个过程发现了几个优化点:

  1. Controller如果能保持单例,尽量保持单例

    这样可以减少创建对象和回收对象的开销。也就是说,如果controller的类变量和实力变量可以以方法形参生命的尽量以方法的形参声明,不要以类变量和实例变量声明,这样可以避免线程安全问题。

  2. 处理Reqeust的方法中的形参务必加上@RequestParam注解

    这样可以避免SpringMVC使用asm框架读取class文件获取方法参数名的过程。即便SpringMVC对读取的方法参数名进行缓存,如果不要读取class文件当然是更好。

  3. 缓存URL

    阅读源码的过程中,我们发现SpringMVC并没有处理url的方法进行缓存,也就是说每次都要根据请求url去匹配controller中的方法url,如果把url和method的关系缓存起来,会不会带来性能上的提升呢?有点恶心的是,负责解析url和Mehtod对应的关系的ServletHandlerMethodResolver是一个private的内部类,不能直接继承该类增强代码,必须要改代码后重新编译。当然,如果缓存起来,必须要考虑缓存的线程安全问题。

03 May 2025