美文网首页
Spring MVC 源码分析

Spring MVC 源码分析

作者: Kohler | 来源:发表于2018-08-06 16:49 被阅读11次

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

相关文章

网友评论

      本文标题:Spring MVC 源码分析

      本文链接:https://www.haomeiwen.com/subject/qlygvftx.html