Spring MVC 面试总结

(1)  Spring  DispatcherServlet

SpringMVC的核心就是DispatcherServlet,DispatcherServlet实质也是一个HttpServlet,继承了FrameworkServlet,实现 ApplicationContextAware 接口,继承 HttpServletBean 抽象类负责初始化 Spring Servlet WebApplicationContext 容器,同时该类覆写了 doGet、doPost 等方法,并将所有类型的请求委托给 doService 方法去处理,doService 是一个抽象方法,需要子类实现,DispatcherServlet负责初始化 Spring MVC 的各个组件,以及处理客户端的请求,协调各个组件工作



(2)   Spring MVC 中的 WebApplicationContext ?

WebApplicationContext 是实现 ApplicationContext 接口的子类,专门为 WEB 应用准备的

它允许从相对于 Web 根目录的路径中加载配置文件,完成初始化 Spring MVC 组件的工作。

从 WebApplicationContext 中,可以获取 ServletContext 引用,整个 Web 应用上下文对象将作为属性放置在 ServletContext 中,以便 Web 应用环境可以访问 Spring 上下文。



(3)   WebApplicationContext  加载过程

ContextLoaderListener 继承 ContextLoader 类,实现 Servlet 容器启动和关闭时,分别初始化和销毁 WebApplicationContext 容器,WebApplicationContext 有两处


(4)  说一下Spring MVC为何没有web.xml文件了

public interface ServletContainerInitializer {

     void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;

}

 Servlet 3.0 新增的一个接口ServletContainerInitializer ,配合@HandlesTypes 注解来指定希望被处理的类,容器在启动时使用 JAR 服务 的时候会通过SPI机制发现 ServletContainerInitializer 的实现类,并且容器将 WEB-INF/lib 目录下 JAR 包中的类都交给该类的 onStartup() 方法处理,实现的 onStartup 方法中可以向 ServletContext 对象(Servlet 上下文)添加之前在 web.xml 中配置的 Filter和 Servlet,这样一来就可以去除 web.xml 文件了  

SpringMVC的实现类 SpringServletContainerInitializer  就是实现了ServletContainerInitializer接口,负责初始化web环境,SpringServletContainerInitializer把所有的初始化任务委托给WebApplicationInitializer这个接口的实现类,其中AbstractDispatcherServletInitializer便是创建DispatcherServlet 的关键类



(5)  SpringBoot中 加载 Servlet 的流程

SpringBoot 是通过 org.springframework.boot.web.embedded.tomcat.TomcatStarter 这个类进行初始化流程的,TomcatStarter同样也是实现了ServletContainerInitializer接口,通过构造方法传入的ServletContextInitializer去初始化Web环境,其中ServletWebServerApplicationContext 负责 加载Filter、Servlet、Listener

ServletWebServerApplicationContext 的 onRefresh() 方法触发配置了一个匿名的 ServletContextInitializer

这个匿名的 ServletContextInitializer 的 onStartup 方法会去容器中搜索到了所有的 RegisterBean 并按照顺序加载到 ServletContext 中

这个匿名的 ServletContextInitializer 最终传递给 TomcatStarter,由 TomcatStarter 的 onStartup 方法去触发 ServletContextInitializer 的 onStartup 方法,最终完成装配

扩展一下 关于SpringBoot 如何配置 Servlet

     注册方式一:Servlet3.0 注解(@WebServlet,@WebFilter,@WebListener) +@ServletComponentScan , 在启动类上面添加 @ServletComponentScan 注解去扫描到这些注解

      注册方式二:RegistrationBean,比如 ServletRegistrationBean 和 FilterRegistrationBean 都继成 RegistrationBean,它是 SpringBoot 中广泛应用的一个注册类,负责把 Servlet,Filter,Listener 给容器化,使它们被 Spring 托管,并且完成自身对 Web 容器的注册



(6)   Spring MVC 工作流程

用户的浏览器发送一个请求,这个请求经过互联网到达了我们的服务器。Servlet 容器首先接待了这个请求,并将该请求委托给 DispatcherServlet 进行处理。

1.  DispatcherServlet 主要通过dispatch这个方法去处理整个流程,这个流程他会先去通过HandlerMapping 的getHandler方法得到一个HandlerExecutionChain对象

HandlerMapping 就是一个接口,里面就只有一个方法就叫getHandler,他有很多实现类,但是大体主要通过两种方式获得Handler 一种是 AbstracHandlerMethodMapping AbstractUrlHandlerMapping,一个是基于 Method 进行匹配的,比如@RequestMapping注解,一个是基于 URL 进行匹配,我们平常用的最多的还是RequestMappingHandlerMapping,这个就是AbstracHandlerMethodMapping 的子类

HandlerExecutionChain这个对象就是包含了拦截器和处理器对象,这个处理器对象是object类型的,对象比如说,我们平常通过在方法上标记@RequestMapping注解,然后呢他这个对象就是HandlerMethod 类型, 这个 HandlerMethod封装了很多属性,在访问请求方法的时候可以方便的访问到方法、方法参数、方法上的注解、所属类等并且对方法参数封装处理,也可以方便的访问到方法参数的注解等信息。拦截器呢,就是实现了HandlerInterceptor接口的类,就是在执行方法之前和执行方法之后进行一些处理

2.  拿到这个HandlerExecutionChain对象之后呢,还要找一个HandlerAdapter 适配器,他通过遍历 HandlerAdapter 组件,判断是否支持处理该 handler 处理器,支持则返回该 HandlerAdapter 组件,这个HandlerAdapter组件负责处理执行整个流程,参数的解析,方法的执行,返回值的处理,都是在他里面完成的,

像我们平时用的最多的还是RequestMappingHandlerAdapter类,他就是执行 HandlerMethod 类型的处理器,也就是通过 @RequestMapping 等注解标注的方法

3. 然后呢通过执行HandlerAdapter的handle方法,会给我们返回一个ModelAndView 对象,这个呢就是数据和视图地址,然后呢就会调用processDispatchResult这个方法去解析视图,这里面他会通过render这个方法进行页面渲染逻辑,这里面就会交给viewResolvers去处理,但是我们平常开发都是标注了ResponseBody注解,所以在HandlerAdapter这里面会将我们返回的结果直接将数据通过HttpServletResponse.write写进去,最后直接返回



(7)  介绍下 Spring MVC 拦截器?

  Spring MVC的拦截器的实现了HandlerInterceptor接口里面主要有三个方法 preHandle,postHandle,afterCompletion,分别是前置处理,后置处理和已完成处理

  这个HandlerInterceptor呢,他会在在HanderMapping的子类AbstractHandlerMapping中的 getHandler 方法中去获取,一般是在获取完handler处理器后然后会去和HandlerInterceptor去组装成HandlerExecutionChain对象,

HandlerInterceptor拦截器呢会在AbstractHandlerMapping initApplicationContext()方法中初始化完成的,因为这个方法是重写了父类ApplicationObjectSupport的initApplicationContext()ApplicationObjectSupport最上层的接口ApplicationContextAware中的setApplicationContext,所以他会在初始化该ben的时候调用setApplicationContext这个方法,然后这个方法将 interceptors 初始化成 HandlerInterceptor 类型,添加到 adaptedInterceptors


 preHandle

    在会在HandlerAdapter执行handle之前调用HandlerExecutionChain的applyPreHandle方法,会执行请求匹配的拦截器的前置处理 ,在执行的过程中会记录通过interceptorIndex 记录执行的当前执行的下标

 postHandle

    会在HandlerAdapter执行handle之后调用HandlerExecutionChain.applyPostHandle 执行请求匹配的拦截器的后置处理 会根据interceptorIndex 下标倒序执行

 afterCompletion

    会在所有请求处理完成之后调用, 不管有没有异常,都会执行,调用HandlerExecutionChain的triggerAfterCompletion方法 也是根据interceptorIndex 下标倒序执行


(8) 说说什么是HandlerMapping ?

HandlerMapping 组件,请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors),下面先说AbstractHandlerMapping

AbstractHandlerMapping 

AbstractHandlerMapping 抽象类,一个基类,实现了“为请求找到合适的 HandlerExecutionChain 处理器执行链”对应的的骨架逻辑,而暴露 getHandlerInternal() 抽象方法,交由子类实现。

HandlerMapping 处理器组件的实现类分为两种:

AbstractUrlHandlerMapping

基于 URL 进行匹配 ,当然,目前这种方式已经基本不用了,被 @RequestMapping 等注解的方式所取代。不过,Spring MVC 内置的一些路径匹配,还是使用这种方式

AbstractHandlerMethodMapping

基于 Method 进行匹配。例如,我们所熟知的 @RequestMapping 等注解的方式

AbstractHandlerMethodMapping初始化时会扫描@Controller或者@RequestMapping注解标注的 Bean 对象,会将带有@RequestMapping注解(包括其子注解)解析成RequestMappingInfo对象。接下来,会将RequestMappingInfo、该方法对象、该方法所在类对象往MappingRegistry注册表进行注册,其中会生成HandlerMethod处理器(方法的所有信息)对象保存起来。当处理某个请求时,HandlerMapping 找到该请求对应的HandlerMethod处理器对象后,就可以通过反射调用相应的方法了



(9) 说一说HandlerAdapter 适配器?

            因为其中处理器的实现有多种,例如通过实现 Controller 接口、HttpRequestHandler 接口,或者使用 @RequestMapping 注解将方法作为一个处理器等。Spring MVC 就无法直接执行这个处理器,所以这里需要一个处理器适配器,由它去执行处理器,HandlerAdapter 接口也就三个方法,一个是判断是否支持该处理器的,一个是执行处理器返回ModelAndView的,还有一个是返回请求的最新更新时间的

HttpRequestHandlerAdapter:执行实现了 HttpRequestHandler 接口的处理器

SimpleControllerHandlerAdapter:执行实现了 Controller 接口的处理器

SimpleServletHandlerAdapter:执行实现了 Servlet 接口的处理器

RequestMappingHandlerAdapter: 执行 HandlerMethod 类型的处理器,也就是通过 @RequestMapping 等注解标注的方法,主要说说RequestMappingHandlerAdapter的执行流程:

1. 会先创建ServletInvocableHandlerMethod并且设置一些属性,这个是对HandlerMethod 处理器的封装,然后通过这个对象的invokeAndHandle方法去执行

2  然后需要通过 HandlerMethodArgumentResolver 对象进行参数解析

3  在通过反射执行对应的 Method 方法对象

4   最后通过 HandlerMethodReturnValueHandler 对象对执行结果进行处理,设置到 response 响应中,生成对应的 ModelAndView 对象

说一说ServletInvocableHandlerMethod 

ServletInvocableHandlerMethod 封装 HandlerMethod 处理器对象,它还包含 HandlerMethodArgumentResolver 参数解析器和 HandlerMethodReturnValueHandler 返回值处理器等组件

说一说HandlerMethodArgumentResolver 

参数解析器,先说说他是怎么处理的吧, 在ServletInvocableHandlerMethod解析参数会先用到一个HandlerMethodArgumentResolverComposite对象,这也是一个实现HandlerMethodArgumentResolver接口的对象,他呢是一个组合对象,里面就包含了很多的参数解析器,他会遍历所有的参数解析器去调用supportsParameter这个方法去看是否支持解析该参数,如果支持呢他就会返回这个参数解析器,不支持就会给返回一个IllegalStateException的异常,然后呢就会调用resolveArgument去解析这个参数,像解析参数处理器的就非常多了,常见的有:

 RequestParamMethodArgumentResolver:基于 @RequestParam 注解的 也可以不带

 PathVariableMethodArgumentResolver :基于 @PathVariable 注解的

ServletModelAttributeMethodProcessor:基于ModelAttribute注解的,处理实体类,也可以不带

 RequestResponseBodyMethodProcessor:基于@RequestBody注解的

在转换的过程中呢还有个参数转换器,比如啊我们从浏览器发过来的请求都是text类型的,所以必须要把这这些类型转换成我们的目标类型,比如Date,Number啊这些类型,都需要通过转换器去完成,WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中,拿到这些处理好的值,直接通过反射调用我们写好的方法获得拿到需要返回的值就好了

说一说HandlerMethodReturnValueHandler

  HandlerMethodReturnValueHandler 返回值处理器将返回结果设置到响应中,或者设置相应的 Model 和 View 用于后续的视图渲染。这个接口也就两个方法,一个是判断是否支持该返回类型的,还有一个就是处理返回值的

RequestResponseBodyMethodProcessor:  负责处理@ResponseBody注解的返回值,现在前后端分离,后端基本是提供 Restful API,最常用的就是他,RequestResponseBodyMethodProcessor中还有个最重要的就是MessageConverters内容协商,利用 MessageConverters将返回的数据 写为json,他 =会根据请求头Accept去看浏览器接收什么样的数据类型,在根据MessageConverters实现类的MediaType这个类去和你匹配看自己能支持那些内容类型的数据,那你浏览器需要json就会得到MappingJackson2HttpMessageConverter这个对象,然后把对象转成json写到response中

ViewNameMethodReturnValueHandler: 适用于前后端未分离,Controller 返回视图名的场景,例如 JSP、thymeleaf模板引擎就用这个



(10)说一说HandlerExceptionResolver ?

处理器异常解析器,将处理器执行请求时发生的异常,解析成对应的 ModelAndView 结果,大致就是通过@ControllerAdvice+@ExceptionHandler注解处理全局异常,底层就是HandlerExceptionResolver 接口支持的,其中一个实现类ExceptionHandlerExceptionResolver会在初始化的时候去扫描 扫描 @ControllerAdvice 注解的 Bean 们然后找到有 @ExceptionHandler 注解的方法,则添加到 exceptionHandlerAdviceCache 中,执行的时候他里面也有参数解析器和返回值处理器,处理完了之后也会返回ModelAndView对象



(11)说一说ViewResolver ?

视图解析器,根据视图名和国际化,获得最终的视图 View 对象。Spring MVC 执行完处理器后生成一个 ModelAndView 对象,如果该对象不为 null 并且有对应的 viewName,那么就需要通过 ViewResolver 根据 viewName 解析出对应的 View 对象



(12)说一说MultipartResolver文件上传

MultipartResolver 组件,内容类型( Content-Type )为 multipart/* 的请求的解析器,主要解析文件上传的请求。例如,MultipartResolver 会将 HttpServletRequest 封装成 MultipartHttpServletRequest 对象,

(13)Spring MVC 的核心组件大概描述一下

 DispatcherServlet  

Spring MVC 的核心组件,是请求的入口,负责协调各个组件工作

MultipartResolver

  内容类型( Content-Type )为 multipart/* 的请求的解析器,例如解析处理文件上传的请求,便于获取参数信息以及上传的文件

HandlerMapping

  请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors)

HandlerAdapter

 处理器的适配器。因为处理器 handler 的类型是 Object 类型,需要有一个调用者来实现 handler 是怎么被执行。Spring 中的处理器的实现多变,比如用户处理器可以实现 Controller 接口、HttpRequestHandler 接口,也可以用 @RequestMapping 注解将方法作为一个处理器等,这就导致 Spring MVC 无法直接执行这个处理器。所以这里需要一个处理器适配器,由它去执行处理器

HandlerExceptionResolver

 处理器异常解析器,将处理器( handler )执行时发生的异常,解析( 转换 )成对应的 ModelAndView 结果

RequestToViewNameTranslator

视图名称转换器,用于解析出请求的默认视图名

LocaleResolver

本地化(国际化)解析器,提供国际化支持

ThemeResolver

 主题解析器,提供可设置应用整体样式风格的支持

ViewResolver

视图解析器,根据视图名和国际化,获得最终的视图 View 对象

FlashMapManager

FlashMap 管理器,负责重定向时,保存参数至临时存储(默认 Session)


其他

@Controller 注解有什么用?

@Controller 注解标记一个类为 Spring Web MVC控制器Controller。Spring MVC 会将扫描到该注解的类,然后扫描这个类下面带有 @RequestMapping 注解的方法,根据注解信息,为这个方法生成一个对应的处理器对象,在上面的 HandlerMapping 和 HandlerAdapter组件中讲到过。

当然,除了添加 @Controller 注解这种方式以外,你还可以实现 Spring MVC 提供的 Controller 或者 HttpRequestHandler 接口,对应的实现类也会被作为一个处理器对象

@RequestMapping 注解有什么用?

@RequestMapping 注解,在上面已经讲过了,配置处理器的 HTTP 请求方法,URI等信息,这样才能将请求和方法进行映射。这个注解可以作用于类上面,也可以作用于方法上面,在类上面一般是配置这个控制器的 URI 前缀

@RestController 和 @Controller 有什么区别?

@RestController 注解,在 @Controller 基础上,增加了 @ResponseBody 注解,更加适合目前前后端分离的架构下,提供 Restful API ,返回例如 JSON 数据格式。当然,返回什么样的数据格式,根据客户端的 ACCEPT 请求头来决定。

@RequestMapping 和 @GetMapping 注解的不同之处在哪里?

@RequestMapping:可注解在类和方法上;@GetMapping 仅可注册在方法上

@RequestMapping:可进行 GET、POST、PUT、DELETE 等请求方法;@GetMapping 是 @RequestMapping 的 GET 请求方法的特例,目的是为了提高清晰度。

@RequestParam 和 @PathVariable 两个注解的区别

两个注解都用于方法参数,获取参数值的方式不同,@RequestParam 注解的参数从请求携带的参数中获取,而 @PathVariable 注解从请求的 URI 中获取

返回 JSON 格式使用什么注解?

可以使用@ResponseBody注解,或者使用包含 @ResponseBody 注解的@RestController注解。

当然,还是需要配合相应的支持 JSON 格式化的 HttpMessageConverter 实现类。例如,Spring MVC 默认使用 MappingJackson2HttpMessageConverter

ServletRegistrationBean 和 FilterRegistrationBean 都继成 RegistrationBean,它是 SpringBoot 中广泛应用的一个注册类,负责把 Servlet,Filter,Listener 给容器化,使它们被 Spring 托管,并且完成自身对 Web 容器的注册

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

推荐阅读更多精彩内容