Spring MVC 源码分析

DispatcherServlet 初始化

在一个简单的Spring MVC项目中,需要web.xml配置DispatcherServlet

<!--配置DispatcherServlet-->
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/spring-*.xml</param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <!--默认匹配所有请求-->
    <url-pattern>/</url-pattern>
</servlet-mapping>

contextConfigLocation 指定spring配置文件位置,除此之外无其他加载Spring ApplicationContex配置的代码。那么Spring ApplicationContext的加载肯定是随着 DispatcherServlet 初始化而加载,并且使用的是此contextConfigLocation 指定的配置文件位置。

看看DispatcherServlet继承结构

image

Servlet中有一个init方法,执行servlet初始化操作,在子类 HttpServletBean 中进行重写此方法,并调用initServletBean()方法完成环境的加载,initServletBean在子类中通过重写,完成bean加载任务。

HttpServletBean.init()

@Override
    public final void init() throws ServletException {
        // Set bean properties from init parameters.
        // 将web.xml配置的属性设置到dispatcherServlet的属性中,除了contextConfigLocation属性,
        // 在DispatcherServlet的构造函数注释中还说明有contextClass、contextInitializerClasses
        ...

        // Let subclasses do whatever initialization they like.
        // 空方法,子类重写,完成bean加载任务
        initServletBean();
    }

在子类FrameworkServlet中重写initServletBean()方法,完成创建WebApplicationContext

FrameworkServlet.initServletBean()

@Override
protected final void initServletBean() throws ServletException {
    //log
    ...
    this.webApplicationContext = initWebApplicationContext();
    initFrameworkServlet();
    //log
    ...
}

initFrameworkServlet()是个空方法,子类也并未重写,这里主要是initWebApplicationContext(),初始化WebApplicationContext

FrameworkServlet.initWebApplicationContext()

// 初始化并发布此servlet的WebApplicationContext。
// 真正创建context的工作委派给createWebApplicationContext方法。
protected WebApplicationContext initWebApplicationContext() {
   // 查找rootContext,并设置当前servlet的parent
   ...
   if (wac == null) {
      //查找是否手动设置过context
      wac = findWebApplicationContext();
   }
   if (wac == null) {
      // No context instance is defined for this servlet -> create a local one
      // 创建context
      wac = createWebApplicationContext(rootContext);
   }

   if (!this.refreshEventReceived) {
      // 刷新applicationContext,和ConfigurableApplicationContext.refresh() 方法类似,
      // 完成servlet 的 handlerMapping、viewResolver等获取
      onRefresh(wac);
   }

   if (this.publishContext) {
      // 将context作为servlet上下文属性发布。
       String attrName = getServletContextAttributeName();
       getServletContext().setAttribute(attrName, wac);
   }

   return wac;
}

重点在于创建createWebApplicationContext()onRefresh(),先看createWebApplicationContext方法

FrameworkServlet.createWebApplicationContext(@Nullable ApplicationContext parent)

// 实例化此 servlet 的 WebApplicationContext,之前有提到,在web.xml配置文件中可以配置 contextClass,
// 如果未配置则使用默认的 XmlWebApplicationContext,此处,自定义的额contexClass需要实现
// ConfigurableWebApplicationContext 接口
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
   // web.xml 配置,默认 XmlWebApplicationContext
   Class<?> contextClass = getContextClass();
    ...
   // 实例化 ApplicationContext
   ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

   wac.setEnvironment(getEnvironment());
   wac.setParent(parent);
   // web.xml 配置,spring 配置位置
   String configLocation = getContextConfigLocation();
   if (configLocation != null) {
      wac.setConfigLocation(configLocation);
   }
   // 加载并刷新,这里完成 IOC 容器的装载
   configureAndRefreshWebApplicationContext(wac);

   return wac;
}

FrameworkServlet.configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac)

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   ...
   wac.setServletContext(getServletContext());
   wac.setServletConfig(getServletConfig());
   wac.setNamespace(getNamespace());
   // 设置listener使得context刷新,servlet也跟着刷新
   wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

   // 为 post-processing or initialization 初始化配置属性
   ConfigurableEnvironment env = wac.getEnvironment();
   if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
   }

   postProcessWebApplicationContext(wac);
   // 这里会取之前提到的,在web.xml中配置的 contextInitializerClasses 初始化context
   // contextInitializerClasses 需实现 ApplicationContextInitializer 接口
   applyInitializers(wac);
   // context refresh 初始化容器、事件监听、source等
   wac.refresh();
}

至此容器初始化完成,但是容器与 servlet 之间并未关联起来,现在回到FrameworkServlet.initWebApplicationContext()方法,

protected WebApplicationContext initWebApplicationContext() {
   
   if (wac == null) {
      wac = createWebApplicationContext(rootContext);
   }

   if (!this.refreshEventReceived) {
      // context不是ConfigurableApplicationContext或者已经刷新过,则手动刷新,将容器中相关的handlerMapping、viewResolver关联到servlet
      onRefresh(wac);
      // 之前 configureAndRefreshWebApplicationContext 方法中配置listener可以监听context的刷新事件,并自动调用servlet 的 onRefresh 方法
   }

   return wac;
}

DispatcherServlet.onRefresh(ApplicationContext context)

@Override
protected 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);
    // handlerMapping
   initHandlerMappings(context);
    // handlerAdapter
   initHandlerAdapters(context);
    // 异常
   initHandlerExceptionResolvers(context);
    // 请求 -> 视图
   initRequestToViewNameTranslator(context);
    // 视图解析
   initViewResolvers(context);
    // flashMapManager
   initFlashMapManager(context);
}

这几个方法都是从容器中将所需要的相应的bean放到 DispatcherServlet中,以下属性中:

private MultipartResolver multipartResolver;
private LocaleResolver localeResolver;
private ThemeResolver themeResolver;
private List<HandlerMapping> handlerMappings;
private List<HandlerAdapter> handlerAdapters;
private List<HandlerExceptionResolver> handlerExceptionResolvers;
private RequestToViewNameTranslator viewNameTranslator;
private FlashMapManager flashMapManager;
private List<ViewResolver> viewResolvers;

这样容器中的bean就可以为 DispatcherServlet 所用了。这里以initHandlerMappings(ApplicationContext context)为例

private void initHandlerMappings(ApplicationContext context) {
   this.handlerMappings = null;

   //将ApplicationContext中HandlerMappings找出来并放在handlerMappings中
   ...

   // 确保至少有一个HandlerMapping, 否则配置一个默认的HandlerMapping
   if (this.handlerMappings == null) {
      this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
      }
   }
}

在未找到 HandlerMapping 时,会在配置文件中读取默认配置,默认的配置在DispatcherServlet.properties中,可以看到默认的HandlerMapping有两个

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
   org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

同理我们的DispatcherServlet也拿到了其他的 bean,至此,servlet环境是初始化完成了,接下来看DispatcherServlet怎么处理请求的。

DispatcherServlet 处理请求

在处理请求之前先看一下Spring MVC处理请求的流程:

image

DispatcherServlet接收到请求后,将Request交由 HandlerMapping 查找相应的 Controller ,然后由HandlerAdapter去执行具体的 Controller 的方法,处理返回 ModelAndView ,然后根据视图名称查找页面,此后就是页面渲染,返回页面了。

DispatcherServlet 中处理Http请求的方法,doGet、doPost等,都是由父类 FrameworkServlet 实现 ,最终调用到 DispatcherServlet.doDispatch() 方法,这个是真正处理请求的方法。

DispatcherServlet.doDispatch()

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ...
    // 确定当前请求的handler。
    mappedHandler = getHandler(processedRequest);
    // 确定当前请求的handler adapter。
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    // PreHandle (interceptor.preHandle),预拦截器
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
    }
    // 实际调用处理程序。
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    // 确定视图名称
    applyDefaultViewName(processedRequest, mv);
    // 后拦截器
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    // 处理视图 
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    ...
}

这里看重点看一下getHandler方法

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   for (HandlerMapping hm : this.handlerMappings) {
      HandlerExecutionChain handler = hm.getHandler(request);
      if (handler != null) {
         return handler;
      }
   }
   return null;
}

这里遍历handlerMappings 直到能根据request匹配到handler,getHandler 方法的实现在AbstractHandlerMapping 类中

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   // 获取handler
   Object handler = getHandlerInternal(request);
   if (handler == null) {
      handler = getDefaultHandler();
   }
   if (handler == null) {
      return null;
   }
   // 实现 Controller 接口的controller
   // Bean name or resolved handler?
   if (handler instanceof String) {
      String handlerName = (String) handler;
      handler = obtainApplicationContext().getBean(handlerName);
   }

   // 根据 handler 生成 ExecutionChain ,主要是添加拦截器
   HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
   // Cors
   if (CorsUtils.isCorsRequest(request)) {
      CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
      CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
      CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
      executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
   }
   return executionChain;
}

关于获取handler,这里以 RequestMappingHandlerMapping 的实现讲解,因为RequestMappingHandlerMapping 是匹配 @Controller注解的controller,日常使用较多。RequestMappingHandlerMapping 使用的父类AbstractHandlerMethodMappinggetHandlerInternal方法。

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
   String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);

   this.mappingRegistry.acquireReadLock();
   try {
      HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
      return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
   }
   finally {
      this.mappingRegistry.releaseReadLock();
   }
}

调用lookupHandlerMethod()方法获取 HandlerMethod,这个 HandlerMethod 就是具体需要执行的某个 controller 中的某个方法,只要反射调用其中的 method 就能获得调用结果。

lookupHandlerMethod()是怎么获得 HandlerMethod 的呢?该方法主要是根据AbstractHandlerMethodMapping内部的变量 MappingRegistry mappingRegistry 获取的,在 MappingRegistry 内部,我们可看到如下变量

private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

也就是说 HandlerMapping 中已经有了 请求路径 -> handler 的映射的集合,只要根据相应的方法获取即可。那么问题来了,如果我们写一个@Controller 注解的 controller,那这个 controller 是如何处理被映射到 HandlerMapping 中的呢?

HandlerMapping 注册请求映射

RequestMappingHandlerMapping实现的接口中,其中一个是InitializingBean,在 Bean 实例化的过程中有一步便是检测是否有InitializingBean接口,若有,则执行其afterPropertiesSet方法。(单一Bean的实例化主要在AbstractAutowireCapableBeanFactory#doCreateBean方法)。那么回到RequestMappingHandlerMappingafterPropertiesSet()方法,该方法主要是调用AbstractHandlerMethodMappinginitHandlerMethods()方法完成请求映射。

// AbstractHandlerMethodMapping#initHandlerMethods
protected void initHandlerMethods() {
   String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
         BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
         getApplicationContext().getBeanNamesForType(Object.class));

   // 遍历所有的bean name
   for (String beanName : beanNames) {
      if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
         Class<?> beanType = null;
         try {
            beanType = getApplicationContext().getType(beanName);
         }
         catch (Throwable ex) {
         }
         // 判断是否为Handler,RequestMappingHandlerMapping是根据是否有Controller或RequestMapping注解
         if (beanType != null && isHandler(beanType)) {
            // 根据beanName,装载handlerMethod
            detectHandlerMethods(beanName);
         }
      }
   }
   handlerMethodsInitialized(getHandlerMethods());
}
// RequestMappingHandlerMapping#isHandler,根据是否有Controller或RequestMapping注解
protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
// AbstractHandlerMethodMapping#detectHandlerMethods
protected void detectHandlerMethods(final Object handler) {
    // handlerType,可能为proxy
    Class<?> handlerType = ...
    // 实际类型
    final Class<?> userType = ClassUtils.getUserClass(handlerType);

    // 找到 Medhod -> RequestMappingInfo的映射,RequestMappingHandlerMapping会根据是否有RequestMapping注解判断
    Map<Method, T> methods = ...
        
    for (Map.Entry<Method, T> entry : methods.entrySet()) {
        Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
        T mapping = entry.getValue();
        // 注册到 mappingRegistry 中
        registerHandlerMethod(handler, invocableMethod, mapping);
    }
}

那么至此,整个 spring mvc 初始化、处理请求的流程也就结束了。

结语

DispatcherServlet 的初始化会伴随Spring 容器的初始化,并获取容器中的bean完成servlet处理请求所需要的配置装载。最后以一张流程图总结

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容