SpringMVC源码分析 DispatcherServlet源码分析

简介: SpringMVC源码分析 DispatcherServlet源码分析

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


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

环境Spirng、SpringMVC


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


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


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


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



①:在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());
}



// <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);
  });
  }
}




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


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



这个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的相关内容,为后面反射做铺垫:



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


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



二、请求来临时


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

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



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



①证明DispatcherServlet是Servlet。


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


下面是DispatcherServlet的继承图:



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



既然其是一个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方法,该方法调用的就是HttpServlet的Service()方法(也就是原生的Servlet中的方法):



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


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



DispatcherServlet类部分:


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



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


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




2、认识doDispatch

doDispatch:执行请求的分发


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


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



③前置处理拦截器。


④真正的调用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);
         }
      }
   }
}


相关文章
|
12月前
|
XML 前端开发 Java
源码分析系列教程(05) - 手写SpringMVC
源码分析系列教程(05) - 手写SpringMVC
36 0
|
2天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
8 1
|
3天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
11 1
|
缓存 容器
从源码分析SpringMVC核心处理流程
从源码分析SpringMVC核心处理流程
108 0
从源码分析SpringMVC核心处理流程
|
XML 设计模式 JSON
图文源码分析Spring MVC请求映射原理、执行流程
图文源码分析Spring MVC请求映射原理、执行流程
251 0
图文源码分析Spring MVC请求映射原理、执行流程
|
设计模式 缓存 前端开发
web九大组件之---HandlerAdapter适配器模式实践源码分析【享学Spring MVC】
web九大组件之---HandlerAdapter适配器模式实践源码分析【享学Spring MVC】
web九大组件之---HandlerAdapter适配器模式实践源码分析【享学Spring MVC】
|
Web App开发 前端开发 Java
SpringMVC源码分析2:SpringMVC设计理念与DispatcherServlet
转自:https://my.oschina.net/lichhao/blog SpringMVC作为Struts2之后异军突起的一个表现层框架,正越来越流行,相信javaee的开发者们就算没使用过SpringMVC,也应该对其略有耳闻。
|
Java
SpringMVC之请求处理源码分析从service到doDispatch之doDispatch分析(二)
说明:本文所用的SpringMVC版本为4.3.4.RELEASE,应用服务器为TomCat8.0.33。 在上一篇文章中我们分析了请求service方法到doDispatch的一部分,今天我们先简单的分析doDispatch这个方法,以后还会有很多围绕着这个方法进行分析的内容。
1431 0