Spring-MVC介绍
Spring Web Mvc 属于表现层框架,用于与客户端交互,Springmvc 是基于 servlet 规范来完成的一个请求响应模块,也是spring 中比较大的一个模块。
架构
架构流程
1、 用户发送请求至前端控制器DispatcherServlet
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
5、 执行处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9、 ViewReslover解析后返回具体View
10、 DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户
组件说明
以下组件通常使用框架提供实现:
DispatcherServlet:前端控制器
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
HandlerMapping:处理器映射器
HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
Handler:处理器
Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。
HandlAdapter:处理器适配器
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
View Resolver:视图解析器
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。
View:视图
springmvc框架提供了很多的View视图类型的支持,包括:jstlView、freemarkerView、pdfView等。比如最常用的视图就是jsp。
springMVC源码
现在基本上都是零 xml 配置了,采用的是约定大于配置的方式,所以我们的 springmvc 也是采用这种零 xml 配置的方式。要完成这种过程,要解决两个问题
1、取代 web.xml 配置;
2、取代 springmvc.xml 配置
取代 web.xml 配置
传统配置模式:
spring-web jar 包 META-INF/service下面也会有一个 javax.servlet.ServletContainerInitializer 文件,通过SPI的方式。
该类在启动的时候会被 servlet 容器实例化,然后调用 onStartup 方法,并且 servlet 容器会收集实现了@HandlesTypes 注解里面的接口 的类,并且做为入参传入到 onStartup 方法中,我们拿到 set 容器中的类就可以反射调用接口里面的方法了,这是 servlet 规范,该规范就能保 证 servlet 容器在启动的时候就会完成这些操作。Springmvc 就借助这一点完成了取代 web.xml 的工作
servlet规范:
当我们第一次访问Servlet时,服务器会创建Servlet,并调用一次init()方法;每当我们访问Servlet时,service()方法会被调用处理这次请求,并且做出响应,当我们关闭服务器或者项目从服务器中移除时。Servlet会被销毁,destroy()方法会被调用
Tomcat 就会加载这个类,调用其 onStartup 方法。
收集WebApplicationInitializer的实现类,并调用启onStartup方法###initializer.onStartup(servletContext);
super.onStartup(servletContext)===》registerContextLoaderListener(ServletContext servletContext):
如下图:
1、this.createRootApplicationContext(),创建Spring上下文,注册Spring容器,此为父容器;
this.getRootConfigClasses():一个钩子方法,这里如果类上有@ComponentScan等注解是会被Sprign解析并处理的。
2、然后然后通过 Spring 上下文创建 ContextLoaderListener 并把此 Listener 添加到 ServletContext 上下文中去。
创建并注册DispatcherServlet
this.registerDispatcherServlet(servletContext),实例化并注册DispatcherServlet
1、createServletApplicationContext():创建SpringMVC上下文环境,并注册MvcContainer,此时为子容器,
同父容器创建过程类似:
2、 this.createDispatcherServlet(servletAppContext):创建 DispatcherServlet 对象以后把它加入到 servletContxt 的上下文中,添加名称为 dispatcher 的 servlet 对象
接着会有两个方法:
this.getServletMappings():一个钩子方法,返回全限定类名数组
this.getServletFilters():收集自定义的Filter
到这里就完成了dispatcherServlet的创建过程
注意,此时上下文对象还没有调用 refresh 方法,没有启动 spring 容器
启动Spring容器
根据servlet规范,容器启动会调用servlet的init()方法,可以在dispatcherServlet父类HttpServletBean找到:
如下图这里的rootContext为spring容器,既父容器
1、this.configureAndRefreshWebApplicationContext(cwac):调用refresh方法完成 spring 容器的启动。需要注意的是这里:
会从 servletContext 中获取父容 器,就是由 listener 负责启动的容器,然后把父容器设置到了自己的上下文对象中,所以这里监听器启动的容器是父容器,dispatcherServlet 启动的容器是子容器,两者是父子关系。这里就用 servlet 规范完成了取代 web.xml 的工作,并启动了容器。Spring 的加载规则,其实就是优先从父容器中取,取不到才会从子容器中取
2、this.onRefresh()===>initStrategies()
DispatcherServlet初始化操作
这里会初始化很多组件,如核心组件HandlerAdapter、HandlerMapping等,以供后续DispatcherSerlvet请求处理使用如下图:
取代spring-mvc.xml
传统方式
0配置则用一个@EnableWebMvc 就可以完全取代 xml 配置,其实两者完成的工作是一样的,都是为了创建必要组件的实例:
Spring启动过程中会解析到
DelegatingWebMvcConfiguration会导入一个核心类(父类) WebMvcConfigurationSupport,在这个类里面会完成很多组件的实例化@Bean导入,如 HandlerMapping,HandlerAdapter 等等。
然后在实例化过程中会涉及到很多钩子方法的调用,而这些钩子方法就是我们需要去实现的,比如获取拦截器的钩子方法,获取静态资源处理的钩子方法等等。
RequestMappingHandlerMapping==》getInterceptors==>this.addInterceptors(registry),自定义拦截器的获取。
至此,Springmvc.xml方式就完全可以被取代,实现0配置。
建立映射关系
在 HandlerMapping 类实例化的时候就会完成 url 和 method 的映射关系,其目的是要根据一个请求能够唯一到找到一个类和一个方法。
这个是 RequestMappingHandlerMapping 的实例化,通过这个类的实例化后会调用此类的 afterPropertiesSet 方法,在此方法中会调用父 类 AbstractHandlerMethodMapping 中实现的 InitializingBean 接口,然后看一下父类的 afterPropertiesSet 方法。在这个方法里面完成 了映射关系的建立
super.afterPropertiesSet()—>this.initHandlerMethods();
for 循环—>processCandidateBean(beanName)
1、isHandler():判断类上面是否有@Controller 注解和@RequestMapping 注解,只有这种类才需要建立映射关系
2、this.detectHandlerMethods(beanName):对需要建立映射的bean生成映射关系;
2.1、遍历类中所有的方法,selectMethods()
2.2、收集方法上面的@RequestMapping 注解,把注解里面的配置信息封装到类里面,该类就是 RequestMappingInfo 类,并且跟类上面的 @RequestMapping 注解封装类 RequestMappingInfo 合并,比如类上面是/common,方法上面是/queryUser。这两者合并后就是 /common/queryUser。这样的 url 才是完整的。合并完就是这样的 url
ReflectionUtils.doWithMethods(currentHandlerType,method)循环所有方法,判断有没有@RequestMapping,有则metadataLookup.inspect(specificMethod)会调用到匿名函数getMappingForMethid()
methodMap.put(specificMethod, result):建立方法对象和注解对象的映射关系,返回methodMap
getMappingForMethid()
this.createRequestMappingInfo(method):寻找有注解@RequestMapping的方法,并封装注解对象
this.createRequestMappingInfo(handlerType):寻找类上有注解@RequestMapping,并封装注解对象
typeInfo.combine(info):合并类和方法的注解对象
2.3、registerHandlerMethod()===》register()
AbstractHandlerMethodMapping.this.createHandlerMethod(handler, method):创建HandlerMethod对象;
validateMethodMapping(handlerMethod, mapping):检验映射关系;
this.mappingLookup.put(mapping, handlerMethod):建立URI对象与HandlerMethod的映射关系
this.urlLookup.add(url, mapping):建立URL与RequestMappingInfo的映射关系
this.corsLookup.put(handlerMethod, corsConfig):封装跨域的处理。
此时映射关系就已经建立好,这样根据请求 url 就可以唯一的找到一个 RequestMappingInfo 对象,然后通过 ReuqestMappingInfo 对象再找到对应的 HandlerMethodHandlerMethod 对象了,注意这个对象中还不能进行反射调用,还差参数。
DispatcherServlet 处理请求
根据servlet规范,在servlet 的请求时,应该首先会请求到 servlet 中的 service 方法,通过 DispatcherServlet 或者父类的 service 方法,如下图所示:FramworkServlet##service()
super.service()会调用父类httpServlet的service方法,重点是this.processRequest(request, response)
—>Dispatcher#doservice()----.>this.doDispatch(request, response)
1、根据请求 url 获取 HandlerExecutionChain 对象–getHandler()
1.1、this.checkMultipart(request):文件处理
1.2、获取Handler
this.getHandler(processedRequest):
遍历所有的HandlerMapping,===>HandlerMapping##getHandler()
getHandlerInternal():根据URL获取对应的HandlerMethod,从前面映射好的容器中获取
getHandlerExecutionChain()将URL和HandlerMethod封装成HandlerExecutionChain对象,其实就是将所有的HandlerInterceptor的实现类封装成一个执行链
2、获取HandlerAdapter–getHandlerAdapter()
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
根据handler匹配所有的handlerAdapter,找到一个支持适配的HandlerAdapter
3、前置过滤器–preHandle()
遍历所有的Intercepter,顺序链式调用preHandler()方法,如果有一个返回false,则不会往后执行
4、方法执行–通过适配器处理
ha.handle(processedRequest, response, mappedHandler.getHandler())—》RequestMappingHandlerAdapter##handleInternal()
this.invokeHandlerMethod(request, response, handlerMethod)
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);进行controller方法的调用
this.invokeForRequest(webRequest, mavContainer, providedArgs),
this.getMethodArgumentValues(request, mavContainer, providedArgs):
参数解析,首先获取方法的参数列表,并且把参数封装成 MethodParameter 对象,这个对象记录了参数在参数列表中的索引,参数类型,参数上面的 注解数组等等信息。然后循环参数列表,一个个参数来处理,这里是一个典型的策略模式的运用,根据参数获取一个处理该参数的类。处理参数的 解析类有 26 个。
this.doInvoke(args):调用方法并返回结果
5、返回值处理–handleReturnValue()
当反射调用成功后,有可能方法会有返回值,而返回值处理也是一个比较重要的事情,根据什么样的方式把返回值响应回去,返回值响应时有 可能是数据有可能是界面,而如果返回数据的话,要把返回值解析成对应的格式,例如如果返回值是一个 list 对象,就需要解析这个 list 对象把 list 对象解析成 json 格式。返回值解析套路跟入参解析基本上类似。
1、把返回值封装成对象,对象跟 MethodParameter 对象差不多,里面包括参数名称、类型、参数注解等等信息
2、根据返回值类型用策略模式找到一个解析类
3、用这个解析类解析
不同的返回结果类型有不同的解析方式,以常见的带@ResponseBody 注解的,一个是直接返回字符串响应一个界面的,里面涉及到 ModelAndViewContainer 容器,这个容器会把视图名称设置到里面,以及 Model 数据,包括响应到界面的数据也会放到这个容器中。
3.1、直接返回字符串 ModelAndViewContainer —ModelAndViewMethodReturnValueHandler
其实就是mavContainer.setRequestHandled(true)–标识视图、setViewName-设置视图、设置属性、返回视图
3.2、带@ResponseBody 注解
6、中置过滤器–postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv),在执行完方法并返回视图后,这里可以修改视图:interceptor.postHandle(request, response, this.handler, mv)
7、视图渲染–render()
就是响应界面,如果返回值没有加@ResponseBody 注解时,这时候是需要响应一个界面给前端的,视图渲染借助了 servlet 中的 api:
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException)===》render()
获取视图,渲染
–>InternalResourceView##renderMergedOutputModel()
8、后置过滤器–afterComletion()
链式调用HandelrInterceptor#afterComletion(),一般用于资源销毁、释放等
9、异常处理–prcessHandlerException()
类上面加上@ControllerAdvice(“com.xx.xx”)注解,这个包定义就是只对这个包里面的 Controller 生效。然后类里面的方法加上 @ExceptionHandler({ArrayIndexOutOfBoundsException.class}) @ExceptionHandler({NullPointerException.class}) 表示这个方法当调用过程中出现注解里面定义的异常时会被调用到,这些方法就是对异常处理的方法。
ExceptionHandlerExceptionResolver##doResolveHandlerMethodException()
遍历所有有
@ControllerAdvice的类,然后再匹配@ExceptionHandler对应的方法,发射调用
源码总结:
取代web.xml
利用SPI机制,启动实例化并注册配置类,并进行初始化处理
取代spring-mvc.xml
利用Spring对@import的处理,实例化并注册mvc容器和相关处理类
映射关系建立
收集@RequestMapping注解的类和方法,封装成RequestMappingInfo对象和HandlerMethod对象,并建立url和其映射关系,便于后续请求处理时找到对应的执行controller方法
请求处理
1、根据请求 url 获取 HandlerExecutionChain 对象–getHandler()
2、获取HandlerAdapter–getHandlerAdapter()
3、前置过滤器–preHandle()
4、方法执行–通过适配器处理-ha.handle()
5、返回值处理–handleReturnValue()
6、中置过滤器–postHandle()
7、视图渲染–render()
8、后置过滤器–afterComletion()
9、异常处理–prcessHandlerException()