spring MVC 基础

组件和概念

处理器:Handler

1,对应 MVC 三层中的 C,也就是控制器,找不到 handler 抛出 404
2,具体表现形式很多,使用 @RequestMapping 注解是做常用的,还有实现 org.springframework.web.servlet.mvc.Controller 接口;实现 org.springframework.web.HttpRequestHandler 接口等
3,只要能实际处理请求的就可以是 handler,正因为实现 handler 的方式不同,所以需要合适的适配器去执行 handler

处理器执行链:HandlerExecutionChain

1,处理器 + 拦截器列表(有顺序的,顺序执行,包括自定义的)
2,其实不难理解,比如项目中配置了拦截器,执行请求时也会执行拦截器,所以当请求过来找到了处理器并不是直接九执行,还要找到 处理器执行链 再执行

前端控制器:DispatcherServlet

1,整个 springmvc 的核心,请求的响应和处理都是它完成的
2,继承关系:DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet
3,实际上就是一个 Servlet,所以 DispatcherServlet 初始化的时候会执行 HttpServletBean 的 init() 方法。这是 DispatcherServlet 初始化的入口

DispatcherServlet 九大组件

处理器映射器:HandlerMapping

1,DispatcherServlet 初始化时完成配置,作用是然后根据 request 找到 HandlerExecutionChain
2,HandlerMapping 是一个接口,很多子接口和实现类。其中获取 handler 的方式是模板方式,由子类完成查找 handler(源码是遍历所有子类去查找)
3,RequestMappingHandlerMapping:存储 @RequestMapping 实现的 handler
4,BeanNameUrlHandlerMapping:存储 Controller 类实现 Controller 或 HttpRequestHandler 接口实现的 handler

处理器适配器:HandlerAdapter

1,通过处理器映射器找到了 HandlerExecutionChain,其实也就找到了 Handler
2,Handler 的实现方式是多种,所以要找到合适当前 handler 的执行工具,这个工具就是 HandlerAdapter
3,RequestMappingHandlerAdapter 执行 @RequestMapping 注解实现的 handler

处理器异常解析器:HandlerExceptionResolver

1,当 handler 执行出现了异常时,DispatcherServlet 会调用 HandlerExceptionResolver 来处理。具体是由 DefaultHandlerExceptionResolver 处理,全局异常见下表格
2,自定义全局异常有两种方式:@ControllerAdvice 结合 @ExceptionHandler;继承 HandlerExceptionResolver 并重写 resolveException 方法
Exception HTTP Status Code
HttpRequestMethodNotSupportedException 405 (SC_METHOD_NOT_ALLOWED)
HttpMediaTypeNotSupportedException 415 (SC_UNSUPPORTED_MEDIA_TYPE)
HttpMediaTypeNotAcceptableException 406 (SC_NOT_ACCEPTABLE)
MissingPathVariableException 500 (SC_INTERNAL_SERVER_ERROR)
MissingServletRequestParameterException 400 (SC_BAD_REQUEST)
ServletRequestBindingException 400 (SC_BAD_REQUEST)
ConversionNotSupportedException 500 (SC_INTERNAL_SERVER_ERROR)
TypeMismatchException 400 (SC_BAD_REQUEST)
HttpMessageNotReadableException 400 (SC_BAD_REQUEST)
HttpMessageNotWritableException 500 (SC_INTERNAL_SERVER_ERROR)
MethodArgumentNotValidException 400 (SC_BAD_REQUEST)
MissingServletRequestPartException 400 (SC_BAD_REQUEST)
BindException 400 (SC_BAD_REQUEST)
NoHandlerFoundException 400 (SC_BAD_REQUEST)
AsyncRequestTimeoutException 404 (SC_NOT_FOUND)
AsyncRequestTimeoutException 503 (SC_SERVICE_UNAVAILABLE)

试图解析器:ViewResolver

ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。  
这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?  
这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成

RequestToViewNameTranslator

 ViewName是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。  
 RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现

LocaleResolver

解析视图需要两个参数:一是视图名,另一个是Locale。  
视图名是处理器返回的,Locale是从哪里来的?  
这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。  
SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候

ThemeResolver

用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。  
SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。  
SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource和Theme。  
主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了

MultipartResolver

用于处理上传请求。  
处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File  
如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源

FlashMapManager

用来管理FlashMap的,FlashMap主要用在redirect中传递参数

DispatcherServlet 初始化

第一步:HttpServletBean 初始化

HttpServletBean 作用是扩展 HttpServlet
// DispatcherServlet 构造方法
public DispatcherServlet() {
    super();
    setDispatchOptionsRequest(true);
}
// FrameworkServlet 构造方法
public FrameworkServlet() {
    // 省略了 supper()
}
// HttpServletBean.init()
public final void init() throws ServletException {
    // ... 这部分的作用是扩展 HttpServlet,具体不展开主要是下面这个方法,目的是创建 IOC 容器
    // 这个方法是个空方法,子类实现
    initServletBean();
}

第二步:创建 IOC 容器

/**
 * FrameworkServlet 重写了 initServletBean
 * 作用就是创建 WebApplicationContext(应用上下文),其实就是 MVC 的 IOC 容器
 */
@Override
protected final void initServletBean() throws ServletException {
    try {
        // 创建 IOC 容器
        this.webApplicationContext = initWebApplicationContext();
        // 子类实现
        initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        throw ex;
    }
}
/**
 * 具体怎么创建 IOC 容器
 * 
 */
protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    // 当前对象是否有 wac,有就用它
    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    // 如果 wac 不存在就到 ServletContext 查找是否有初始化过的 wac
    if (wac == null) {
        wac = findWebApplicationContext();
    }
    // 还是不存在就创建一个
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        wac = createWebApplicationContext(rootContext);
    }
    // 到这里有了 wac,如果没有初始化就初始化
    if (!this.refreshEventReceived) {
        synchronized (this.onRefreshMonitor) {
            onRefresh(wac);
        }
    }

    if (this.publishContext) {
        // 把 wac 设置到 ServletContext 的 attrbiute 里去
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

第三步:配置 DispatcherServlet 属性

1,这一步其实就是初始化九大对象,这一步走完就意味着 DispatcherServlet 初始化完成
2,每个组件的初始化不一一展开,分析下处理器映射器的初始化
@Override
protected void onRefresh(ApplicationContext context) {
    // 调用了 initStrategies()
    initStrategies(context);
}
// 初始化九大组件
protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}
  • 处理器映射器初始化
private void initHandlerMappings(ApplicationContext context) {
    // 初始化映射器为空
    this.handlerMappings = null;
    // 据属性 detectAllHandlerMappings 决定是检测所有的 HandlerMapping 对象还是使用指定名称的 HandlerMapping 对象
    if (this.detectAllHandlerMappings) {
        // 从容器及其祖先容器查找所有类型为 HandlerMapping 的 HandlerMapping 对象,记录到   handlerMappings 并排序
        Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // 进行定义时所使用的 order 属性,顺序属性很关键,因为它涉及到 HandlerMapping 使用时的优先级
            // 比如在根据请求查找 handler 的时候 @RequestMapping 实现的 handler 使用 RequestMappingHandlerMapping,而不是 BeanNameUrlHandlerMapping
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    } else {
        // 获取名称为  handlerMapping 的 HandlerMapping bean 并记录到 handlerMappings
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }

    // 如果 handlerMapping 为空就通过 DispatcherServlet.properties 配置文件构建
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    }

    for (HandlerMapping mapping : this.handlerMappings) {
        if (mapping.usesPathPatterns()) {
            this.parseRequestPath = true;
            break;
        }
    }
}

DispatcherServlet 请求处理流程

进入 DispatcherServlet.doDispatch 方法

  • DispatcherServlet 接收到请求,先是调用 doService 来处理,doService 调用 doDispatch 来处理

获取 HandlerExecutionChain(处理器执行链)

/**
 * doDispatch 会调用 getHandler 方法来获取处理器执行链,子类实现
 */
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        // DispatcherServlet 初始化时后会准备好一批处理器映射器
        // 遍历映射器,找到 handler
        for (HandlerMapping mapping : this.handlerMappings) {
            // 由抽象子类 AbstractHandlerMapping 实现
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}
/**
 * AbstractHandlerMapping.getHandler
 */
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 先根据 request 查找 handler
    // 断点可以看到是根据 uri @RequestMapping() 值去找的方法:HandlerMethod.getHandlerInternal
    Object handler = getHandlerInternal(request);

    // 省略...
    // 把 handler 和所有的拦截器封装成 HandlerExecutionChain(也可以自定义拦截器)
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
        CorsConfiguration config = getCorsConfiguration(handler, request);
        if (getCorsConfigurationSource() != null) {
            CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
            config = (globalConfig != null ? globalConfig.combine(config) : config);
        }
        if (config != null) {
            config.validateAllowCredentials();
        }
        // executionChain 虽然再进行改变,最终 handler 肯定是不变的,所以改变的是拦截器,这里很明显是添加了跨域相关的拦截器
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }

    return executionChain;
}

寻找能处理 handler 的处理器适配器

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        // 遍历所有处理器适配器
        for (HandlerAdapter adapter : this.handlerAdapters) {
            // DispatcherServlet 初始化的时候维护了一批适配器
            // @RequestMapping 实现的 handler 都是 RequestMappingHandlerAdapter 这个适配器去执行
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
        // 找不到能执行的抛出异常
    }
}

处理器适配器执行 handler,返回一个 ModelView

// ha 就是 HandlerAdapter 即 RequestMappingHandlerAdapter
// 根据断点 handle 方法是由其子类 AbstractHandlerMethodAdapter 实现
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// AbstractHandlerMethodAdapter.handle
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
    // 又调用了 handleInternal 来处理,返回一个 ModelAndView
    return handleInternal(request, response, (HandlerMethod) handler);
}

// AbstractHandlerMethodAdapter.handleInternal
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    checkRequest(request);

    // Execute invokeHandlerMethod in synchronized block if required.
    // 默认是 false
    if (this.synchronizeOnSession) {
        // ...
    }
    else {
        // No synchronization on session demanded at all...
        // 执行处理器(就是执行哪个 controller 的具体的方法),结果是一个 ModelView
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }

    // ...

    return mav;
}

ViewReslover 解析 ModelView

这一步根据 ModelView 解析出具体 View 和 Model

DispatcherServlet 渲染 View

DispatcherServlet 对 View 进行渲染,将 model 渲染到 view

DispatcherServlet 响应用户

posted @ 2021-08-17 11:00  huanggy  阅读(27)  评论(0编辑  收藏  举报