当前位置:   article > 正文

Spring——Spring MVC原理_大白话springmvc的运行原理

大白话springmvc的运行原理

摘要

Spring Web MVC 框架也是一个基于请求驱动的Web 框架,并且也使用了前端控制器模式来进行设计,再根据请求映射 规则分发给相应的页面控制器(动作/处理器)进行处理。博文介绍Spring mvc的原理与工作流程,帮助大家更好的理解spring mvc的思想与原理。

一、传统Spring MVC执行流程

核心架构的具体流程步骤如下:

  • 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行 处理,作为统一访问点,进行全局的流程控制;
  • DispatcherServlet——>HandlerMapping, HandlerMapping 将会把请求映射为 HandlerExecutionChain 对象(包含一 个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新 的映射策略;
  • DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器, 即适配器设计模式的应用,从而很容易支持很多类型的处理器;
  • HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处 理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名);
  • ModelAndView 的逻辑视图名——> ViewResolver,ViewResolver 将把逻辑视图名解析为具体的View,通过这种策 略模式,很容易更换其他视图技术;
  • View——>渲染,View 会根据传进来的Model 模型数据进行渲染,此处的Model 实际是一个Map 数据结构,因此 很容易支持其他视图技术;
  • 返回控制权给DispatcherServlet,由DispatcherServlet 返回响应给用户,到此一个流程结束。

上面就是一个传统的完整的 Spring MVC 流程,为什么要说这是传统的流程呢?因为这个流程是用于前后端没有分离的时候,后台直接返回页面给浏览器进行渲染,而现在大部分应用都是前后端分离,后台直接生成一个 Json 字符串就直接返回前端,不需要经过视图解析器进行处理。

二、前后端分离Spring MVC执行流程

Spring MVC主要可以分为两大过程,一是初始化,二就是处理请求。初始化的过程主要就是将我们定义好的 RequestMapping 映射路径和 Controller 中的方法进行一一映射存储,这样当收到请求之后就可以处理请求调用对应的方法,从而响应请求。

2.1 Spring MVC初始化

初始化:初始化过程的入口方法是 DispatchServletinit() 方法,而实际上 DispatchServlet 中并没有这个方法,所以我们就继续寻找父类,会发现 init 方法在其父类(FrameworkServlet)的父类 HttpServletBean 中。

HttpServletBean:init()方法

在这个方法中,首先会去家在一些 Servlet 相关配置(web.xml),然后会调用 initServletBean() 方法,这个方法是一个空的模板方法,业务逻辑由子类 FrameworkServlet 来实现。

  1. public final void init() throws ServletException {
  2. PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
  3. if (!pvs.isEmpty()) {
  4. try {
  5. // 定义资源
  6. BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
  7. // 加载servlet相关配置
  8. ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
  9. bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
  10. this.initBeanWrapper(bw);
  11. bw.setPropertyValues(pvs, true);
  12. } catch (BeansException var4) {
  13. if (this.logger.isErrorEnabled()) {
  14. this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
  15. }
  16. throw var4;
  17. }
  18. }
  19. // 核心方法 由子类FrameworkServlet实现
  20. this.initServletBean();
  21. }

FrameworkServlet-initServletBean()

这个方法本身没有什么业务逻辑,主要是初始化 WebApplicationContext 对象,WebApplicationContext 继承自 ApplicationContext,主要是用来处理 web 应用的上下文。

  1. /**
  2. * Overridden method of {@link HttpServletBean}, invoked after any bean properties
  3. * have been set. Creates this servlet's WebApplicationContext.
  4. * {@link HttpServletBean} 的重写方法,在设置任何 bean 属性后调用。创建此 servlet 的 WebApplicationContext。
  5. */
  6. @Override
  7. protected final void initServletBean() throws ServletException {
  8. getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
  9. if (logger.isInfoEnabled()) {
  10. logger.info("Initializing Servlet '" + getServletName() + "'");
  11. }
  12. long startTime = System.currentTimeMillis();
  13. try {
  14. this.webApplicationContext = initWebApplicationContext();
  15. initFrameworkServlet();
  16. }
  17. catch (ServletException | RuntimeException ex) {
  18. logger.error("Context initialization failed", ex);
  19. throw ex;
  20. }
  21. if (logger.isDebugEnabled()) {
  22. String value = this.enableLoggingRequestDetails ?
  23. "shown which may lead to unsafe logging of potentially sensitive data" :
  24. "masked to prevent unsafe logging of potentially sensitive data";
  25. logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
  26. "': request parameters and headers will be " + value);
  27. }
  28. if (logger.isInfoEnabled()) {
  29. logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
  30. }
  31. }

FrameworkServlet-initWebApplicationContext()

initWebApplicationContext() 方法主要就是为了找到一个上下文,找不到就会创建一个上下文,创建之后,最终会调用方法configureAndRefreshWebApplicationContext(cwac) 方法,而这个方法最终在设置一些基本容器标识信息之后会去调用 refresh() 方法。这个就是初始化IOC容器。

  1. protected WebApplicationContext initWebApplicationContext() {
  2. // 从serlvetContent 中获取父类容器 webApplicationContext()
  3. WebApplicationContext rootContext =
  4. WebApplicationContextUtils.getWebApplicationContext(getServletContext());
  5. WebApplicationContext wac = null;
  6. if (this.webApplicationContext != null) {
  7. // A context instance was injected at construction time -> use it
  8. // 声明一个子容器
  9. wac = this.webApplicationContext;
  10. if (wac instanceof ConfigurableWebApplicationContext) {
  11. ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
  12. if (!cwac.isActive()) {
  13. // The context has not yet been refreshed -> provide services such as
  14. // setting the parent context, setting the application context id, etc
  15. if (cwac.getParent() == null) {
  16. // The context instance was injected without an explicit parent -> set
  17. // the root application context (if any; may be null) as the parent
  18. cwac.setParent(rootContext);
  19. }
  20. // 调用ioc容器的refresh()方法来初始化ioc信息
  21. configureAndRefreshWebApplicationContext(cwac);
  22. }
  23. }
  24. }
  25. if (wac == null) {
  26. // No context instance was injected at construction time -> see if one
  27. // has been registered in the servlet context. If one exists, it is assumed
  28. // that the parent context (if any) has already been set and that the
  29. // user has performed any initialization such as setting the context id
  30. wac = findWebApplicationContext();
  31. }
  32. if (wac == null) {
  33. // No context instance is defined for this servlet -> create a local one
  34. wac = createWebApplicationContext(rootContext);
  35. }
  36. if (!this.refreshEventReceived) {
  37. // Either the context is not a ConfigurableApplicationContext with refresh
  38. // support or the context injected at construction time had already been
  39. // refreshed -> trigger initial onRefresh manually here.
  40. synchronized (this.onRefreshMonitor) {
  41. onRefresh(wac);
  42. }
  43. }
  44. if (this.publishContext) {
  45. // Publish the context as a servlet context attribute.
  46. String attrName = getServletContextAttributeName();
  47. getServletContext().setAttribute(attrName, wac);
  48. }
  49. return wac;
  50. }

当调用 refresh() 方法初始化 ioc 容器之后,最终会调用方法 onRefresh(),这个方法也是一个模板钩子方法,由子类实现,也就是回到了我们 Spring MVC 的入口类 DispatcherServlet

DispatchServlet-onRefresh()

onRefresh() 方法就是 Spring MVC 初始化的最后一个步骤,在这个步骤当中会初始化 Spring MVC 流程中可能需要使用到的九大组件。

  1. */
  2. @Override
  3. protected void onRefresh(ApplicationContext context) {
  4. initStrategies(context);
  5. }
  6. /**
  7. * 初始化的九大组件
  8. * Initialize the strategy objects that this servlet uses.
  9. * <p>May be overridden in subclasses in order to initialize further strategy objects.
  10. */
  11. protected void initStrategies(ApplicationContext context) {
  12. initMultipartResolver(context);//初始化多文件上传组件
  13. initLocaleResolver(context);// 初始化本地语言环境
  14. initThemeResolver(context);// 初始化模板处理器
  15. initHandlerMappings(context);// 初始化 HandlerMappings
  16. initHandlerAdapters(context);// 初始化HandlerAdapters
  17. initHandlerExceptionResolvers(context);// 初始化异常拦截器
  18. initRequestToViewNameTranslator(context);// 初始化视图预处理器
  19. initViewResolvers(context);// 初始化视图转化器
  20. initFlashMapManager(context);// 解决用户重的定向是参数丢失问题
  21. }

2.2 Spring MVC九大组件

  1. MultipartResolver:这个组件比较熟悉,主要就是用来处理文件上传请求,通过将普通的 Request 对象包装成 MultipartHttpServletRequest 对象来进行处理。
  2. LocaleResolver:LocaleResolver 用于初始化本地语言环境,其从 Request 对象中解析出当前所处的语言环境,如中国大陆则会解析出 zh-CN 等等,模板解析以及国际化的时候都会用到本地语言环境。
  3. ThemeResolver:这个主要是用户主题解析,在 Spring MVC 中,一套主题对应一个 .properties 文件,可以存放和当前主题相关的所有资源,如图片,css样式等。
  4. HandlerMapping:用于查找处理器(Handler),比如我们 Controller 中的方法,这个其实最主要就是用来存储 url 和 调用方法的映射关系,存储好映射关系之后,后续有请求进来,就可以知道调用哪个 Controller 中的哪个方法,以及方法的参数是哪些。
  5. HandlerAdapter:这是一个适配器,因为 Spring MVC 中支持很多种 Handler,但是最终将请求交给 Servlet 时,只能是 doService(req,resp) 形式,所以 HandlerAdapter 就是用来适配转换格式的。
  6. HandlerExceptionResolver:这个组件主要是用来处理异常,不过看名字也很明显,这个只会对处理 Handler 时产生的异常进行处理,然后会根据异常设置对应的 ModelAndView,然后交给 Render 渲染成页面。
  7. RequestToViewNameTranslator:这个主键主要是从 Request 中获取到视图名称。
  8. ViewResolver:这个组件会依赖于 RequestToViewNameTranslator 组件获取到的视图名称,因为视图名称是字符串格式,所以这里会将字符串格式的视图名称转换成为 View 类型视图,最终经过一系列解析和变量替换等操作返回一个页面到前端。
  9. FlashMapManager:这个主键主要是用来管理 FlashMap,那么 FlashMap 又有什么用呢?要明白这个那就不得不提到重定向了,有时候我们提交一个请求的时候会需要重定向,那么假如参数过多或者说我们不想把参数拼接到 url 上(比如敏感数据之类的),这时候怎么办呢?因为参数不拼接在 url 上重定向是无法携带参数的。FlashMap 就是为了解决这个问题,我们可以在请求发生重定向之前,将参数写入 request 的属性 OUTPUT_FLASH_MAP_ATTRIBUTE 中,这样在重定向之后的 handler 中,Spring 会自动将其设置到 Model 中,这样就可以从 Model 中取到我们传递的参数了。

2.3 Spring MVC的请求处理

在九大组件初始化完成之后,Spring MVC的初始化就完成了,接下来就是接收并处理请求了,那么处理请求的入口在哪里呢?处理请求的入口方法就是 DispatcherServlet 中的 doService 方法,而 doService 方法又会调用 doDispatch 方法。

  1. @Override
  2. protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
  3. logRequest(request);
  4. // Keep a snapshot of the request attributes in case of an include,
  5. // to be able to restore the original attributes after the include.
  6. Map<String, Object> attributesSnapshot = null;
  7. if (WebUtils.isIncludeRequest(request)) {
  8. attributesSnapshot = new HashMap<>();
  9. Enumeration<?> attrNames = request.getAttributeNames();
  10. while (attrNames.hasMoreElements()) {
  11. String attrName = (String) attrNames.nextElement();
  12. if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
  13. attributesSnapshot.put(attrName, request.getAttribute(attrName));
  14. }
  15. }
  16. }
  17. // Make framework objects available to handlers and view objects.
  18. request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
  19. request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
  20. request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
  21. request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
  22. if (this.flashMapManager != null) {
  23. FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
  24. if (inputFlashMap != null) {
  25. request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
  26. }
  27. request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
  28. request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
  29. }
  30. RequestPath previousRequestPath = null;
  31. if (this.parseRequestPath) {
  32. previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
  33. ServletRequestPathUtils.parseAndCache(request);
  34. }
  35. try {
  36. // 核心的方法
  37. doDispatch(request, response);
  38. }
  39. finally {
  40. if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
  41. // Restore the original attribute snapshot, in case of an include.
  42. if (attributesSnapshot != null) {
  43. restoreAttributesAfterInclude(request, attributesSnapshot);
  44. }
  45. }
  46. if (this.parseRequestPath) {
  47. ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
  48. }
  49. }
  50. }
  1. @SuppressWarnings("deprecation")
  2. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  3. HttpServletRequest processedRequest = request;
  4. HandlerExecutionChain mappedHandler = null;
  5. boolean multipartRequestParsed = false;
  6. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  7. try {
  8. ModelAndView mv = null;
  9. Exception dispatchException = null;
  10. try {
  11. // 检查是否有文件上传请求
  12. processedRequest = checkMultipart(request);
  13. multipartRequestParsed = (processedRequest != request);
  14. // Determine handler for the current request.
  15. //获取处理器,也就是controller中的方法
  16. mappedHandler = getHandler(processedRequest);
  17. if (mappedHandler == null) {
  18. noHandlerFound(processedRequest, response);
  19. return;
  20. }
  21. // Determine handler adapter for the current request.
  22. // 获取当前请求的处理程序适配器。
  23. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  24. // Process last-modified header, if supported by the handler.
  25. String method = request.getMethod();
  26. boolean isGet = HttpMethod.GET.matches(method);
  27. if (isGet || HttpMethod.HEAD.matches(method)) {
  28. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  29. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
  30. return;
  31. }
  32. }
  33. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  34. return;
  35. }
  36. // Actually invoke the handler.
  37. // 实际上调用处理程序。
  38. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  39. if (asyncManager.isConcurrentHandlingStarted()) {
  40. return;
  41. }
  42. applyDefaultViewName(processedRequest, mv);
  43. mappedHandler.applyPostHandle(processedRequest, response, mv);
  44. }
  45. catch (Exception ex) {
  46. dispatchException = ex;
  47. }
  48. catch (Throwable err) {
  49. // As of 4.3, we're processing Errors thrown from handler methods as well,
  50. // making them available for @ExceptionHandler methods and other scenarios.
  51. dispatchException = new NestedServletException("Handler dispatch failed", err);
  52. }
  53. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  54. }
  55. catch (Exception ex) {
  56. triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  57. }
  58. catch (Throwable err) {
  59. triggerAfterCompletion(processedRequest, response, mappedHandler,
  60. new NestedServletException("Handler processing failed", err));
  61. }
  62. finally {
  63. if (asyncManager.isConcurrentHandlingStarted()) {
  64. // Instead of postHandle and afterCompletion
  65. if (mappedHandler != null) {
  66. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
  67. }
  68. }
  69. else {
  70. // Clean up any resources used by a multipart request.
  71. if (multipartRequestParsed) {
  72. cleanupMultipart(processedRequest);
  73. }
  74. }
  75. }
  76. }

这个方法最关键的就是调用了 getHandler 方法,这个方法就是会获取到前面九大组件中的 HandlerMapping,然后进行反射调用对应的方法完成请求,完成请求之后后续还会经过视图转换之类的一些操作,最终返回 ModelAndView,不过现在都是前后端分离,基本也不需要用到视图模型,在这里我们就不分析后续过程,主要就是分析 HandlerMapping 的初始化和查询过程。

DispatcherServlet-getHandler()

这个方法里面会遍历 handllerMappings,这个 handllerMappings 是一个 List 集合,因为 HandlerMapping 有多重实现,也就是 HandlerMapping 不止一个实现,其最常用的两个实现为 RequestMappingHandlerMappingBeanNameUrlHandlerMapping

  1. @Nullable
  2. protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  3. if (this.handlerMappings != null) {
  4. // 遍历的map 中的Handler处理 HandlerMapping 中有多重实现 多重类型
  5. for (HandlerMapping mapping : this.handlerMappings) {
  6. HandlerExecutionChain handler = mapping.getHandler(request);
  7. if (handler != null) {
  8. return handler;
  9. }
  10. }
  11. }
  12. return null;
  13. }

AbstractHandlerMapping-getHandler()

AbstractHandlerMapping 是一个抽象类,其 getHandlerInternal 这个方法也是一个模板方法:

  1. @Nullable
  2. public Object getDefaultHandler() {
  3. return this.defaultHandler;
  4. }

getHandlerInternal 方法最终其会调用子类实现,而这里的子类实现会有多个,其中最主要的就是 AbstractHandlerMethodMappingAbstractUrlHandlerMapping 两个抽象类,那么最终到底会调用哪个实现类呢?这时候如果拿捏不准我们就可以看一下类图,上面我们提到,HandlerMapper 有两个非常主要的实现类:RequestMappingHandlerMappingBeanNameUrlHandlerMapping。那么我们就分别来看一下这两个类的类图关系:

可以看到,这两个实现类的抽象父类正好对应了 AbstractHandlerMapping 的两个子类,所以这时候具体看哪个方法,那就看我们想看哪种类型了。

  • RequestMappingHandlerMapping:主要用来存储 RequestMapping 注解相关的控制器和 url 的映射关系。

  • BeanNameUrlHandlerMapping:主要用来处理 Bean name 直接以 / 开头的控制器和 url 的映射关系。

其实除了这两种 HandlerMapping 之外,Spring 中还有其他一些 HandllerMapping,如 SimpleUrlHandlerMapping 等。提到的这几种 HandlerMapping,对我们来说最常用,最熟悉的那肯定就是 RequestMappingHandlerMapping ,在这里我们就以这个为例来进行分析。

AbstractHandlerMethodMapping-getHandlerInternal()

这个方法本身也没有什么逻辑,其主要的核心查找 Handler 逻辑在 lookupHandlerMethod 方法中,这个方法主要是为了获取一个 HandlerMethod 对象,前面的方法都是 Object,而到这里变成了 HandlerMethod 类型,这是因为 Handler 有各种类型,目前我们已经基本跟到了具体类型之下,所以类型就变成了具体类型,而如果我们看的的另一条分支线,那么返回的就会是其他对象,正是因为支持多种不同类型的 HandlerMapping 对象,所以最终为了统一执行,才会需要在获得 Hanlder 之后,DispatcherServlet 中会再次通过调用 getHandlerAdapter 方法来进一步封装成 HandlerAdapter 对象,才能进行方法的调用。

  1. /**
  2. * Look up a handler method for the given request.
  3. * 查找给定请求的处理程序方法。
  4. */
  5. @Override
  6. @Nullable
  7. protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
  8. String lookupPath = initLookupPath(request);// 查找请求的路径
  9. this.mappingRegistry.acquireReadLock();
  10. try {
  11. // 寻找handler
  12. HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
  13. return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
  14. }
  15. finally {
  16. this.mappingRegistry.releaseReadLock();
  17. }
  18. }

AbstractHandlerMethodMapping-lookupHandlerMethod()

这个方法主要会从 mappingRegistry 中获取命中的方法,获取之后还会经过一系列的判断比较判断比较,因为有些 url 会对应多个方法,而方法的请求类型不同,比如一个 GET 方法,一个 POST 方法,或者其他一些属性不相同等等,都会导致最终命中到不同的方法,这些逻辑主要都是在addMatchingMappings 方法去进一步实现,并最终将命中的结果加入到 matches 集合内。

  1. @Nullable
  2. protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  3. List<Match> matches = new ArrayList<>();
  4. List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
  5. if (directPathMatches != null) {
  6. // 找到合适的Mapping 这一步进一步根据属性查找准确的Hadnler
  7. addMatchingMappings(directPathMatches, matches, request);
  8. }
  9. if (matches.isEmpty()) {
  10. // 如果没有找打 那就遍历mapping中的HandlerMapping
  11. addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
  12. }
  13. if (!matches.isEmpty()) {
  14. Match bestMatch = matches.get(0);
  15. if (matches.size() > 1) {
  16. Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
  17. matches.sort(comparator);
  18. bestMatch = matches.get(0);
  19. //将HandlerMethod进一步进行包装
  20. if (logger.isTraceEnabled()) {
  21. logger.trace(matches.size() + " matching mappings: " + matches);
  22. }
  23. if (CorsUtils.isPreFlightRequest(request)) {
  24. for (Match match : matches) {
  25. if (match.hasCorsConfig()) {
  26. return PREFLIGHT_AMBIGUOUS_MATCH;
  27. }
  28. }
  29. }
  30. else {
  31. // 获取第二个 Handler 进行进一步的比较。
  32. Match secondBestMatch = matches.get(1);
  33. if (comparator.compare(bestMatch, secondBestMatch) == 0) {
  34. Method m1 = bestMatch.getHandlerMethod().getMethod();
  35. Method m2 = secondBestMatch.getHandlerMethod().getMethod();
  36. String uri = request.getRequestURI();
  37. throw new IllegalStateException(
  38. "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
  39. }
  40. }
  41. }
  42. request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
  43. handleMatch(bestMatch.mapping, lookupPath, request);
  44. return bestMatch.getHandlerMethod();
  45. }
  46. else {
  47. return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
  48. }
  49. }

在这个方法中,有一个对象非常关键,那就是 mappingRegistry,因为最终我们根据url到这里获取到对应的 HandlerMtthod,所以这个对象很关键:

  1. class MappingRegistry {
  2. private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
  3. private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();
  4. private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
  5. private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
  6. private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  7. /**
  8. * Return all registrations.
  9. * @since 5.3
  10. */
  11. public Map<T, MappingRegistration<T>> getRegistrations() {
  12. return this.registry;
  13. }
  14. /**
  15. * Return matches for the given URL path. Not thread-safe.
  16. * @see #acquireReadLock()
  17. */
  18. @Nullable
  19. public List<T> getMappingsByDirectPath(String urlPath) {
  20. return this.pathLookup.get(urlPath);
  21. }
  22. /**
  23. * Return handler methods by mapping name. Thread-safe for concurrent use.
  24. */
  25. public List<HandlerMethod> getHandlerMethodsByMappingName(String mappingName) {
  26. return this.nameLookup.get(mappingName);
  27. }
  28. /**
  29. * Return CORS configuration. Thread-safe for concurrent use.
  30. */
  31. @Nullable
  32. public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {
  33. HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();
  34. return this.corsLookup.get(original != null ? original : handlerMethod);
  35. }

看这个对象其实很明显可以看出来,这个对象其实只是维护了一些 Map对象,所以我们可以很容易猜测到,一定在某一个地方,将 urlHandlerMapping 或者 HandlerMethod 的映射关系存进来了,这时候其实我们可以根据 getMappingsByUrl 方法来进行反推,看看 urlLookup 这个 Map 是什么时候被存入的,结合上面的类图关系,一路反推,很容易就可以找到这个 Map 中的映射关系是 AbstractHandlerMethodMapping 对象的 afterPropertiesSet 方法实现的。(AbstractHandlerMethodMapping 实现了 InitializingBean 接口),也就是当这个对象初始化完成之后,我们的 urlHandler 映射关系已经存入了 MappingRegistry 对象中的集合 Map 中。

AbstractHandlerMethodMapping 的初始化

afterPropertiesSet 方法中并没有任何逻辑,而是直接调用了 initHandlerMethods

AbstractHandlerMethodMapping-initHandlerMethods()

initHandlerMethods 方法中,首先还是会从 Spring 的上下文中获取所有的 Bean,然后会进一步从带有 RequestMapping 注解和 Controller 注解中的 Bean 去解析并获得 HandlerMethod

  1. /**
  2. * Scan beans in the ApplicationContext, detect and register handler methods.
  3. * 扫描 ApplicationContext 中的 bean,检测并注册 handler方法。
  4. * @see #getCandidateBeanNames()
  5. * @see #processCandidateBean
  6. * @see #handlerMethodsInitialized
  7. */
  8. protected void initHandlerMethods() {
  9. for (String beanName : getCandidateBeanNames()) {
  10. if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
  11. processCandidateBean(beanName);
  12. }
  13. }
  14. handlerMethodsInitialized(getHandlerMethods());
  15. }

AbstractHandlerMethodMapping-detectHandlerMethods()

这个方法中,其实就是通过反射获取到 Controller 中的所有方法,然后调用 registerHandlerMethod 方法将相关信息注册到 MappingRegistry 对象中的各种 Map 集合之内:

  1. /**
  2. * 在指定的处理程序 bean 中查找处理程序方法
  3. * @param handler either a bean name or an actual handler instance
  4. * @see #getMappingForMethod
  5. */
  6. protected void detectHandlerMethods(Object handler) {
  7. Class<?> handlerType = (handler instanceof String ?
  8. obtainApplicationContext().getType((String) handler) : handler.getClass());
  9. if (handlerType != null) {
  10. Class<?> userType = ClassUtils.getUserClass(handlerType);
  11. Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
  12. (MethodIntrospector.MetadataLookup<T>) method -> {
  13. try {
  14. return getMappingForMethod(method, userType);
  15. }
  16. catch (Throwable ex) {
  17. throw new IllegalStateException("Invalid mapping on handler class [" +
  18. userType.getName() + "]: " + method, ex);
  19. }
  20. });
  21. if (logger.isTraceEnabled()) {
  22. logger.trace(formatMappings(userType, methods));
  23. }
  24. else if (mappingsLogger.isDebugEnabled()) {
  25. mappingsLogger.debug(formatMappings(userType, methods));
  26. }
  27. // 遍历Controller中所有的方法
  28. methods.forEach((method, mapping) -> {
  29. Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
  30. // 注册HandlerMethod
  31. registerHandlerMethod(handler, invocableMethod, mapping);
  32. });
  33. }
  34. }

AbstractHandlerMethodMapping-register()

registerHandlerMethod 方法中会直接调用 AbstractHandlerMethodMapping 对象持有的 mappingRegistry 对象中的 regidter 方法,这里会对 Controller 中方法上的一些元信息进行各种解析,比如参数,路径,请求方式等等,然后会将各种信息注册到对应的 Map 集合中,最终完成了整个初始化。

  1. protected void registerHandlerMethod(Object handler, Method method, T mapping) {
  2. this.mappingRegistry.register(mapping, handler, method);
  3. }
  1. public void register(T mapping, Object handler, Method method) {
  2. this.readWriteLock.writeLock().lock();
  3. try {
  4. HandlerMethod handlerMethod = createHandlerMethod(handler, method);
  5. validateMethodMapping(handlerMethod, mapping);
  6. Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
  7. for (String path : directPaths) {
  8. this.pathLookup.add(path, mapping);
  9. }
  10. String name = null;
  11. if (getNamingStrategy() != null) {
  12. name = getNamingStrategy().getName(handlerMethod, mapping);
  13. addMappingName(name, handlerMethod);
  14. }
  15. CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
  16. if (corsConfig != null) {
  17. corsConfig.validateAllowCredentials();
  18. this.corsLookup.put(handlerMethod, corsConfig);
  19. }
  20. this.registry.put(mapping,
  21. new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
  22. }
  23. finally {
  24. this.readWriteLock.writeLock().unlock();
  25. }
  26. }

博文参考

SpringMVC源码分析系列(精简) - 掘金

SpringMVC源码解析 - 掘金

带你一步一步手撕Spring MVC源码加手绘流程图 - 掘金

SpringMVC源码解析 - 掘金

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/67723
推荐阅读
相关标签
  

闽ICP备14008679号