SpringMVC源码分析 DispatcherServlet源码分析

举报
长路 发表于 2022/11/28 19:28:23 2022/11/28
【摘要】 文章目录前言一、服务器启动过程中的操作1、AbstractHandlerMethodMapping注册url和HandlerMethod(处理url与执行方法对应关系)二、请求来临时1、为什么DispatcherServlet的doService()方法是入口?2、认识doDispatch 前言 本篇博客是对SpringMVC中的DispatcherServlet分析,若文章中出现相关问题,请指出

@[toc]

前言

本篇博客是对SpringMVC中的DispatcherServlet分析,若文章中出现相关问题,请指出!

所有博客文件目录索引:博客目录索引(持续更新)

一、服务器启动过程中的操作

1、AbstractHandlerMethodMapping注册url和HandlerMethod(处理url与执行方法对应关系)

环境Spirng、SpringMVC

执行时间:当启动tomcat服务器的过程中(接收请求前),当bean被注入到容器后会执行一系列的初始化过程。

这里探讨的是urlhandlerMethod对应关系存储的过程,所有的映射关系存储在AbstractHandlerMethodMapping类的内部类MappingRegistry里的registry属性中(是一个HashMap)。

这个过程是在AbstractHandlerMethodMapping抽象类中完成初始化的:

我准备了一个@Controller,并且包含@RequestMapping注解包含url请求地址:

image-20210524154243040

①:在bean注入后会执行初始化方法

//在初始化时执行检测方法
@Override
public void afterPropertiesSet() {
   initHandlerMethods();  //初始化执行器方法
}

②:在①中调用初始化的方法

protected void initHandlerMethods() {
   for (String beanName : getCandidateBeanNames()) { //getCandidateBeanNames()是获取到所有注册的bean名称即id名
      if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
         //看<1>,来进行执行处理对应的bean(目的就是自定义控制器类中包含url地址的类)
         processCandidateBean(beanName);
      }
   }
   handlerMethodsInitialized(getHandlerMethods());
}

image-20210524154436715

// <1> 即处理对应的bean操作,参数为上面遍历的bean的id名称
protected void processCandidateBean(String beanName) {
   Class<?> beanType = null;
   try {
      //获取指定id名称的bean实例即Class类
      beanType = obtainApplicationContext().getType(beanName);
   }
   catch (Throwable ex) {
      if (logger.isTraceEnabled()) {
         logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
      }
   }
   //判断该bean类是否符合类型,其中 isHandler(beanType)见 <2>
   if (beanType != null && isHandler(beanType)) {
      //见 <3> :一旦符合我们的要求就对这个类进行处理(来对去处理该类中的方法)
      detectHandlerMethods(beanName);
   }
}

// <2> 判断是否是我们要找的处理器,及是否包含注解
@Override
protected boolean isHandler(Class<?> beanType) {
    //看到这里就很明白了,可以猜测上面的循环就是遍历找出含有@Controller、@RequestMapping的注解类,之后来进行获取注解中的url
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

// <3> 找到了执行器,那么就将该执行器中的所有方法来进行遍历取到映射地址(url以及对应handlerMethod绑定)
protected void detectHandlerMethods(Object handler) {
    //获取到执行器类
	Class<?> handlerType = (handler instanceof String ?
			obtainApplicationContext().getType((String) handler) : handler.getClass());
	if (handlerType != null) {
		Class<?> userType = ClassUtils.getUserClass(handlerType);
        //将方法与映射地址存储到map集合中去,这一步就做完了操作
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				(MethodIntrospector.MetadataLookup<T>) method -> {
					try {
						return getMappingForMethod(method, userType);
					}
					catch (Throwable ex) {
						throw new IllegalStateException("Invalid mapping on handler class [" +
								userType.getName() + "]: " + method, ex);
					}
				});
		if (logger.isTraceEnabled()) {
			logger.trace(formatMappings(userType, methods));
		}
		else if (mappingsLogger.isDebugEnabled()) {
			mappingsLogger.debug(formatMappings(userType, methods));
		}
        //遍历map集合中的映射键值对(执行方法与url地址)
		methods.forEach((method, mapping) -> {
			Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            //关键!!!将执行器方法与url进行注册(也就是存储到AbstractHandlerMethodMapping.MappingRegistry.registry这个HashMap集合中来)
            //见3中的代码解析
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}

image-20210524160205012

③这些Method类以及url地址应该统一进行保存,当请求来临时就会从一个HashMap中根据url去拿到这个方法类。前面也看到了显示遍历bean容器中的bean,接着筛选真正的执行器并对其中的方法来进行遍历拿到注解中的url地址存储到methods中。

最终是存储到AbstractHandlerMethodMapping.MappingRegistry.registry这个HashMap集合中去的,通过调用registerHandlerMethod()方法来进行统一存储的。

image-20210524161352246

  • 这个handler实际上就是存储着bean的id以及hash值
//注册执行器方法
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    // 见<1>
   this.mappingRegistry.register(mapping, handler, method);
}

//<1>,进行再处理
public void register(T mapping, Object handler, Method method) {
	this.readWriteLock.writeLock().lock();
	try {
        //拿着handler以及method类创建HandlerMethod实例(其中包含了该方法的bean的相关内容)
		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		validateMethodMapping(handlerMethod, mapping);
		Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
		for (String path : directPaths) {
			this.pathLookup.add(path, mapping);
		}
		String name = null;
		if (getNamingStrategy() != null) {
			name = getNamingStrategy().getName(handlerMethod, mapping);
			addMappingName(name, handlerMethod);
		}
		CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
		if (corsConfig != null) {
			corsConfig.validateAllowCredentials();
			this.corsLookup.put(handlerMethod, corsConfig);
		}
        //重点:真正放入到registry这个hashmap集合中的是映射地址以及包含了指定方法的一系列信息
		this.registry.put(mapping,
				new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
	}
	finally {
		this.readWriteLock.writeLock().unlock();
	}
}

HandlerMethod对象中包含了bean的相关内容,为后面反射做铺垫:

image-20210524161520916

当①中的执行完之后注册url以及handlerMethod的过程就结束了!

总结:在bean容器装配好之后,会进行url以及执行器的装配工作,这些工作都是在AbstractHandlerMethodMapping这个抽象类中完成, afterPropertiesSet()作为入口,简单来说就是遍历在IOC容器中注册的所有bean,筛选出具有@Controller@RequestMapping注解的类,筛选过后会对指定类的方法依次去遍历搜集并对url以及method进行配对,最终统一存储到AbstractHandlerMethodMapping抽象类中的一个内部类里的register属性(hashmap集合)中,之后方便请求来临时,根据url来去拿到指定的方法!



二、请求来临时

1、为什么DispatcherServlet的doService()方法是入口?

首先明确一点DispatcherServlet它是一个Servlet,在web.xml被读取时创建的Servlet,看下我们初始阶段进行配置的内容

image-20210521174045943

当项目运行前DispatcherServlet会在被读取web.xml时进行实例化,也就是说该servlet已经被注册到tomcat的容器里了。


①证明DispatcherServlet是Servlet。

这里进行一下说明为什么DispatcherServlet本质就是一个Servlet呢?

下面是DispatcherServlet的继承图:

image-20210521174613032

如何证明?注意DispatcherServlet的父类HttpServletBean继承了HttpServlet类(该类是servlet-api这个jar包中拥有的,也就是tomcat中使用的servlet的jar包)

image-20210521174647798

既然其是一个Servlet并且加载web.xml阶段就会被tomcat中的servlet容器所管理!


②发送请求触发DispatcherServlet

之前可以看到web.xml中配置DispatcherServlet的映射地址为/,也就是所有的请求。

这里介绍一下Servlet的生命周期:初始化、init()、service()、destory()。

DispatcherServlet中也重写了其中的方法。

  • HttpServletBean中重写了init()方法。
  • FrameworkServlet中重写了Service()方法。关键点

这和我发送一个请求过来触发DispatcherServlet有没有什么关系呢?发送一个请求过来首先会解析请求路径,接着就会去找对应路径的servlet,接着去执行servlet中的Service()方法。

FrameworkServlet类部分

FrameworkServlet中的Servcie()方法中,若是一般的get或post请求会走下面黄色框的service方法,该方法调用的就是HttpServletService()方法(也就是原生的Servlet中的方法):

image-20210524135201343

执行HttpServletService()方法中,会根据其中的方法来进行调用指定的doGet()doPost()方法,很凑巧FrameworkServlet中重写了多个doXXX()方法,那么就会走这些重写的方法:其实重写后的方法调用的都是processRequest()方法image-20210524135633853

该方法同样属于FrameworkServlet类中的方法,把注意力放在其中的doService()方法,重头来了

image-20210524135942690

DispatcherServlet类部分

实际上执行该方法时就会进入到DispatcherServlet方法中的doService()方法:

image-20210521180013694

OK,一下子完整舒服了,绕了一大圈依旧是从service()方法中进来最后进入到doService()中去,之后我们再慢慢分析在doService()方法中到底做了些什么事情?

一切开始从中央处理器的doServcie()开始



2、认识doDispatch

doDispatch:执行请求的分发

  • ①获取请求对应的HandlerExecutionChain处理器链。

  • ②根据这个处理器得到指定的Handler对象。根据请求路径来配对对应的方法,将这个方法信息进行储存。

    • image-20210524143502989
  • ③前置处理拦截器。

  • ④真正的调用handler方法,并返回视图。

  • 包含HandlerExecutionChain(处理器链)

doDispatch()是在doService()方法中执行的:主要用于请求的分发

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 {
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         //1、获得请求对应的 HandlerExecutionChain 对象(即执行器处理链,包含一个执行器与拦截器)
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         //2、获得当前 handler 对应的 HandlerAdapter 对象
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.
         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;
            }
         }

         //3、前置处理拦截器
         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         //4、真正的调用handler方法,执行其中的业务操作,以及获取到modelAndView对象
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }
         //5、添加视图
         applyDefaultViewName(processedRequest, mv);
          
         //6、后置处理拦截器
         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);
         }
      }
   }
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。