Spring MVC 初体验

初探Spring MVC 请求处理流程

Spring MVC 相对于前面的章节算是比较简单的,我们首先引用《Spring in Action》上
的一张图来了解Spring MVC 的核心组件和大致处理流程:

从上图中看到
①、DispatcherServlet 是SpringMVC 中的前端控制器(Front Controller),负责接收Request 并将Request 转发给对应的处理组件。

② 、HanlerMapping 是SpringMVC 中完成url 到Controller 映射的组件。DispatcherServlet 接收Request, 然后从HandlerMapping 查找处理Request 的Controller。

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

④、⑤、⑥视图解析器解析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 上
@RequestMapping 的值),与request 的url 进行匹配,找到匹配的那个方法;确定处
理请求的Method 后,接下来的任务就是参数绑定,把Request 中参数绑定到方法的形
式参数上,这一步是整个请求处理过程中最复杂的一个步骤。

Spring MVC 九大组件

HandlerMappings

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

HandlerAdapters

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

HandlerExceptionResolvers

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

ViewResolvers

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

RequestToViewNameTranslator

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

LocaleResolver

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

ThemeResolver

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

MultipartResolver

其实这是一个大家很熟悉的组件,MultipartResolver 用于处理上传请求,通过将普通的
Request 包装成MultipartHttpServletRequest 来实现。MultipartHttpServletRequest
可以通过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 就
会自动将其设置到Model 中,在显示订单信息的页面上,就可以直接从Model 中取得
数据了。而FlashMapManager 就是用来管理FlashMap 的。

Spring MVC 源码分析

根据上面分析的Spring MVC 工作机制,从三个部分来分析Spring MVC 的源代码。
其一,ApplicationContext 初始化时用Map 保存所有url 和Controller 类的对应关系;
其二,根据请求url 找到对应的Controller,并从Controller 中找到处理请求的方法;
其三,Request 参数绑定到方法的形参,执行方法处理请求,并返回结果视图。

初始化阶段

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

 @Overridepublic final void init() throws ServletException {// 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();}

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

 @Overrideprotected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");if (logger.isInfoEnabled()) {logger.info("Initializing Servlet '" + getServletName() + "'");}long startTime = System.currentTimeMillis();try {this.webApplicationContext = initWebApplicationContext();initFrameworkServlet();}catch (ServletException | RuntimeException ex) {logger.error("Context initialization failed", ex);throw ex;}if (logger.isDebugEnabled()) {String value = this.enableLoggingRequestDetails ?"shown which may lead to unsafe logging of potentially sensitive data" :"masked to prevent unsafe logging of potentially sensitive data";logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +"': request parameters and headers will be " + value);}if (logger.isInfoEnabled()) {logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");}}

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

 /*** This implementation calls {@link #initStrategies}.*/@Overrideprotected void onRefresh(ApplicationContext context) {initStrategies(context);}/*** Initialize the strategy objects that this servlet uses.* <p>May be overridden in subclasses in order to initialize further strategy objects.*///初始化策略protected void initStrategies(ApplicationContext context) {//多文件上传的组件initMultipartResolver(context);//初始化本地语言环境initLocaleResolver(context);//初始化模板处理器initThemeResolver(context);//handlerMappinginitHandlerMappings(context);//初始化参数适配器initHandlerAdapters(context);//初始化异常拦截器initHandlerExceptionResolvers(context);//初始化视图预处理器initRequestToViewNameTranslator(context);//初始化视图转换器initViewResolvers(context);initFlashMapManager(context);}

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

 @Overridepublic void initApplicationContext() throws ApplicationContextException {super.initApplicationContext();detectHandlers();}protected void detectHandlers() throws BeansException {ApplicationContext applicationContext = obtainApplicationContext();// 获取ApplicationContext 容器中所有bean 的NameString[] beanNames = (this.detectHandlersInAncestorContexts ?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :applicationContext.getBeanNamesForType(Object.class));// Take any bean name that we can determine URLs for.//遍历beanNames,并找到这些bean 对应的urlfor (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);}}if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());}}

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

 /*** Checks name and aliases of the given bean for URLs, starting with "/".*/@Overrideprotected 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,这里也称为hanlder,处理器,// 第一个步骤的意义就在这里体现了.这里并不是直接返回Controller,// 而是返回的HandlerExecutionChain 请求处理器链对象,// 该对象封装了handler 和interceptors.mappedHandler = getHandler(processedRequest);// 如果handler 为空,则返回404if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.//3. 获取处理request 的处理器适配器handler adapterHandlerAdapter 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 (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 afterCompletionif (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 的
handle()中的核心逻辑由handleInternal(request, response, handler)实现。

 @Overrideprotected 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 necessarymav = 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 进行匹配,找到匹配的方法。

 @Overrideprotected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {// 如果请求url 为,http://localhost:8080/web/hello.json, 则lookupPath=web/hello.jsonString lookupPath = getUrlPathHelper().getLookupPathForRequest(request);request.setAttribute(LOOKUP_PATH, lookupPath);this.mappingRegistry.acquireReadLock();try {// 遍历Controller 上的所有方法,获取url 匹配的方法HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);}finally {this.mappingRegistry.releaseReadLock();}}

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

 @Nullableprotected 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();LogFormatUtils.traceDebug(logger, traceOn -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});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 框架的读取字节码
的操作。

 @Nullablepublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}return doInvoke(args);}/*** Get the method argument values for the current request, checking the provided* argument values and falling back to the configured argument resolvers.* <p>The resulting array will be passed into {@link #doInvoke}.* @since 5.1.2*/protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}catch (Exception ex) {// Leave stack trace for later, exception may actually be resolved and handled...if (logger.isDebugEnabled()) {String exMsg = ex.getMessage();if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw ex;}}return args;}

时序图


关于asm 框架获取方法参数的部分,这里就不再进行分析了。感兴趣的小伙伴可以继续深
入了解这个处理过程。
到这里,方法的参数值列表也获取到了,就可以直接进行方法的调用了。整个请求过程
中最复杂的一步就是在这里了。到这里整个请求处理过程的关键步骤都已了解。理解了
Spring MVC 中的请求处理流程,整个代码还是比较清晰的。最后我们再来梳理一下
Spring MVC 核心组件的关联关系(如下图):

Spring MVC 使用优化建议

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

1、Controller 如果能保持单例,尽量使用单例

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

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

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

3、缓存URL

阅读源码的过程中,我们发现Spring MVC 并没有对处理url 的方法进行缓存,也就是
说每次都要根据请求url 去匹配Controller 中的方法url,如果把url 和Method 的关系
缓存起来,会不会带来性能上的提升呢?

有点恶心的是,负责解析url 和Method 对应关系的ServletHandlerMethodResolver 是一个private 的内部类,不能直接继承该类
增强代码,必须要该代码后重新编译。当然,如果缓存起来,必须要考虑缓存的线程安全问题。

一步一步手绘Spring MVC运行时序图相关推荐

  1. 一步一步手绘Spring MVC运行时序图(Spring MVC原理)

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程) 一步一步手绘Spring IOC运行时序图二(基于XM ...

  2. 一步一步手绘Spring AOP运行时序图(Spring AOP 源码分析)

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程) 一步一步手绘Spring IOC运行时序图二(基于XM ...

  3. 一步一步手绘Spring DI运行时序图(Spring 自动装配之依赖注入)

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程) 一步一步手绘Spring IOC运行时序图二(基于XM ...

  4. 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程)

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程) 一步一步手绘Spring IOC运行时序图二(基于XM ...

  5. 一步一步手绘Spring IOC运行时序图三(基于Annotation的IOC容器初始化)

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程) 一步一步手绘Spring IOC运行时序图二(基于XM ...

  6. 一步一步手绘Spring IOC运行时序图二(基于XML的IOC容器初始化)

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程) 一步一步手绘Spring IOC运行时序图二(基于XM ...

  7. 21.手绘Spring IOC运行时序图

    1.再谈IOC与 DI IOC(lnversion of Control)控制反转:所谓控制反转,就是把原先我们代码里面需要实现的对象创 建.依赖的代码,反转给容器来帮忙实现.那么必然的我们需要创建一 ...

  8. 手写 Spring MVC

    学习自<Spring 5核心原理与30个类手写实战>作者 Tom 老师 手写 Spring MVC 不多说,简历装 X 必备.不过练好还是需要求一定的思维能力. 一.整体思路 思路要熟练背 ...

  9. JAVA项目代码手写吗_一个老程序员是如何手写Spring MVC的

    见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十多 ...

最新文章

  1. python获取代码行号
  2. 将用户添加到sudoers_在CentOS系统中将用户添加到Sudoers文件的方法
  3. jQuery操作标签--样式、文本、属性操作, 文档处理
  4. Python学习入门基础教程(learning Python)--6.4 Python的list与函数
  5. iis php5.3配置,windows+IIS 环境下以Fastcgi方式配置php 5.3.3的方法
  6. Impala之加载HBase数据
  7. fitnesse - 安装部署
  8. 机器学习基础(五十六)—— tanh、sign 与 softsign
  9. socket error:10053
  10. HTRAN 2.4终极使用
  11. WES7 定制界面完整过程(去除所有windows标识)
  12. python爬虫+selenium模拟点击+网页内容需要点击打开
  13. ubuntu安装vmware12出现cannot ope dev/vmmon及modprobe vmmon提示密钥无效的解决办法
  14. 如何对一个水杯进行测试?(详细)
  15. shell批处理 FFmpeg 批量转换格式 webm转MP4
  16. 栈(简单介绍及其应用)
  17. 新手!亚马逊店铺刚下来就被封,什么原因
  18. 【厚积薄发系列】Python项目总结2—Python的闭包
  19. 5G专利第一再惹争议,诺基亚声称第一,华为咋办?
  20. 第五届新疆省ACM-ICPC程序设计竞赛(Java版)

热门文章

  1. Python:pyinstaller如何将一个项目的.py和相关资源文件打包生成.exe文件
  2. 办公录音工具:Backtrack Mac
  3. go select 的default
  4. Linux命令 修改系统时间日期---date和timedatectl
  5. 小红书引流脚本,小红书引流加粉
  6. access to同义替换_常用高级词汇替换
  7. Python打印日历
  8. 数学漫谈:一元三次方程求解
  9. 若将瑞星比Borland 刘旭略胜Anders
  10. 将整个网站变为黑白色