1.前言
Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就已包含在Spring框架中。正式名 称“ Spring Web MVC”来自其源模块的名称(spring-webmvc),但它通常被称为“ Spring MVC”。 从Servlet到SpringMVC 最典型的MVC就是JSP + servlet + javabean的模式。
弊端:
- xml下配置servlet的映射非常麻烦 开发效率低
- 必须要继承父类、重写方法 侵入性强
- 如果想在一个Servlet中处理同一业务模块的的功能分发给不同方法进行处理非常麻烦
- 参数解析麻烦:单个参数(转换类型)—>pojo对象 Json文本—>pojo对象
- 数据响应麻烦:pojo对象—>json … Content-type
- 跳转页面麻烦, 对path的控制、 如果使用其他模板也很麻烦 、设置编码麻烦…等等…
所以SpringMVC 就是在Servlet的基础上进行了封装,帮我把这些麻烦事都给我们做了。
其实SpringMVC请求原理很简单:就是用一个DispatcherServlet 封装了一个Servlet的调度中心, 由调度中心帮我们调用我们的处理方法: 在这个过程中调度中心委托给各个组件执行具体工作 ,比如帮我们映射方法请求、帮我解析参数、 调用处理方法、响应数据和页面等
2.SpringMVC的具体执行流程:
Spring MVC 是围绕前端控制器模式设计的,其中:中央 Servlet DispatcherServlet 为 请求处理流程提供统一调度,实际工作则交给可配置组件执行。这个模型是灵活的且开放的,我们可以通过自己去定制这些组件从而进行定制自己的工作流。
- DispatcherServlet: 前端调度器 , 负责将请求拦截下来分发到各控制器方法中
- HandlerMapping: 负责根据请求的URL和配置@RequestMapping映射去匹配,匹配到会返回Handler(具体控制器的方法)
- HandlerAdaper: 负责调用Handler-具体的方法-返回视图的名字Handler将它封装到 ModelAndView(封装视图名,request域的数据)
- ViewReslover: 根据ModelAndView里面的视图名地址去找到具体的jsp封装在View对象中
- View:进行视图渲染(将jsp转换成html内容 --这是Servlet容器的事情了) 最终response 到的客户端
- 用户发送请求至前端控制器DispatcherServlet
- DispatcherServlet收到请求调用处理器映射器HandlerMapping。 a. 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦 截器)一并返回给DispatcherServlet。
- DispatcherServlet根据处理器Handler获取处理器适配器 HandlerAdapter,执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
- 执行处理器Handler(Controller,也叫页面控制器)。 a. Handler执行完成返回ModelAndView b. HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器 a. ViewReslover解析后返回具体View
- DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
- DispatcherServlet响应用户。
org.springframework.web.servlet.DispatcherServlet#doDispatch 这个方法体现了整个springmvc请求流程:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 进行映射
// 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain (包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 找到最合适的HandlerAdapter
//DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter,
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler. HTTP缓存相关
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 前置拦截器
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
// 返回false就不进行后续处理了
return;
}
// 执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
// 执行处理器Handler(Controller,也叫页面控制器)。
// Handler执行完成返回ModelAndView
// HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 如果mv有 视图没有,给你设置默认视图
applyDefaultViewName(processedRequest, mv);
//后置拦截器
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 渲染视图
// DispatcherServlet将ModelAndView传给ViewReslover视图解析器
// ViewReslover解析后返回具体View
// DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
// DispatcherServlet响应用户。
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
3.Spring整合SpringMVC
特性: 说到Spring整合SpringMVC唯一的体现就是父子容器:
通常我们会设置父容器(Spring)管理Service、Dao层的Bean, 子容器 (SpringMVC)管理Controller的Bean .
子容器可以访问父容器的Bean, 父容器无法访问子容器的Bean。
在SSM框架整合的时候都曾在web.xml配置过这段:
<!‐‐spring 基于web应用的启动‐‐>
<listener>
<listener‐class>org.springframework.web.context.ContextLoaderListener</listener‐class>
</listener>
<!‐‐全局参数:spring配置文件‐‐>
<context‐param>但是它的作用是什么知道吗?
<param‐name>contextConfigLocation</param‐name>
<param‐value>classpath:spring‐core.xml</param‐value>
</context‐param>
<!‐‐前端调度器servlet‐‐>
<servlet>
<servlet‐name>dispatcherServlet</servlet‐name>
<servlet‐class>org.springframework.web.servlet.DispatcherServlet</servlet‐class>
<!‐‐设置配置文件的路径‐‐>
<init‐param>
<param‐name>contextConfigLocation</param‐name>
<param‐value>classpath:spring‐mvc.xml</param‐value>
</init‐param>
<!‐‐设置启动即加载‐‐>
<load‐on‐startup>1</load‐on‐startup>
</servlet>
<servlet‐mapping>
<servlet‐name>dispatcherServlet</servlet‐name>
<url‐pattern>/</url‐pattern>
</servlet‐mapping>
3.1 零配置(零xml)的放式来说明SpringMVC的原理!!
零配置SpringMVC实现方式: 那没有配置就需要省略掉web.xml 怎么省略呢?
在Servlet3.0提供的规范文档中可以找到2种方式:
-
注解的方式
a. @WebServlet
b. @WebFilter
c. @WebListener
但是这种方式不利于扩展,并且如果编写在jar包中tomcat是无法感知到的。 -
SPI的方式
在Serlvet3-1的规范手册中:就提供了一种更加易于扩展可用于共享库可插拔的一种方式
也就是让你在应用META-INF/services 路径下 放一个 javax.servlet.ServletContainerInitailizer ——即SPI规范
SPI 我们叫他服务接口扩展,(Service Provider Interface) 直译服务提供商接口, 不要被这个名字唬到了, 其实很好理解的一个东西: 其实就是根据Servlet厂商(服务提供商)提供要求的一个接口, 在固定的目录 (META-INF/services)放上以接口全类名为命名的文件,文件中放入接口的实现的全类名,该类由我们自己实现,按照这种约定的方式(即SPI规范),服务提供商会调用文件中实现类的方法,从而完成扩展。
4.实现基于SPI规范的SpringMVC
- 此类继承AbstractAnnotationConfigDispatcherServletInitializer
- getRootConfigClasses 提供父容器的配置类
- getServletConfigClasses 提供子容器的配置类
- getServletMappings 设置DispatcherServlet的映射
package com.hzg.javaconfig.starter;
import com.hzg.javaconfig.config.RootConfig;
import com.hzg.javaconfig.config.WebAppConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* 在tomcat源码中:
* org.apache.catalina.startup.ContextConfig#lifecycleEvent(org.apache.catalina.LifecycleEvent
* org.apache.catalina.startup.ContextConfig#configureStart
* org.apache.catalina.startup.ContextConfig#webConfig
* org.apache.catalina.startup.ContextConfig#processServletContainerInitializers
*
* //通过spi的机制加载 classpath下ServletContainerInitializer
* WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
detectedScis = loader.load(ServletContainerInitializer.class);
*
* Created by xsls on 2019/7/31.
*/
public class HzgStarterInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 方法实现说明:IOC 父容器的启动类
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
/**
* 方法实现说明 IOC子容器配置 web容器配置
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebAppConfig.class};
}
/**
* 方法实现说明
* @return: 我们前端控制器DispatcherServlet的拦截路径
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
package com.hzg.javaconfig.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.ServletContextAware;
import javax.servlet.ServletContext;
import static org.springframework.context.annotation.FilterType.ASSIGNABLE_TYPE;
/**
* @desc: 类的描述:IOC根容器,不扫描Controller的注解
* @version: 1.0
*/
@Configuration
@ComponentScan(basePackages = "com.hzg",excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,value={Controller.class}),
@ComponentScan.Filter(type = ASSIGNABLE_TYPE,value =WebAppConfig.class ),
})
public class RootConfig {
}
package com.hzg.javaconfig.config;
import com.hzg.javaconfig.interceptor.HzgInterceptor;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
/**
* @desc: web子容器
*/
@Configuration
@ComponentScan(basePackages = {"com.hzg"},includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {RestController.class, Controller.class})
},useDefaultFilters =false)
@EnableWebMvc // = <mvc:annotation-driven/>
public class WebAppConfig implements WebMvcConfigurer{
/**
* 配置拦截器
* @return
*/
@Bean
public HzgInterceptor HzgInterceptor() {
return new HzgInterceptor();
}
/**
* 文件上传下载的组件
* @return
*/
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setDefaultEncoding("UTF-8");
multipartResolver.setMaxUploadSize(1024*1024*10);
return multipartResolver;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(hzgInterceptor()).addPathPatterns("/*");
}
/**
* 方法实现说明:配置试图解析器
*/
@Bean
public InternalResourceViewResolver internalResourceViewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setSuffix(".jsp");
viewResolver.setPrefix("/WEB-INF/jsp/");
return viewResolver;
}
}
package com.hzg.javaconfig.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HzgInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("HzgInterceptor....preHandle");
return true;
}
}
5.SPI的方式SpringMVC启动原理
流程图:
SpringMVC 大致可以分为 启动 和请求
启动源码流程:
-
外置Tomcat启动的时候通过SPI 找到我们应用中的/META- INF/service/javax.servlet.ServletContainerInitializer
-
调用SpringServletContainerInitializer.onStartUp()
a. 调用onStartUp()前会先找到 @HandlesTypes(WebApplicationInitializer.class) 所有实现了WebApplicationInitializer的类,传入到OnStartup的 webAppInitializerClasses参数中,并传入Servlet上下文对象。
b. 重点关注这组类:他们组成了父子容器 -
找到所有WebApplicationInitializer的实现类后,不是接口、不是抽象则通过反射进行实例化(所以,你会发现内部实现类都是抽象的,你想让其起作用我们必须添加一个自定义实现类,在下文提供我的自定义实现类)
-
调用所有上一步实例化后的对象的onStartup方法
6. 初始化ContextLoaderListener
ContextLoaderListener加载过程比较简单:
外置tomcat会帮我们调用ContextLoaderListener#contextInitialized 进行初始化
- xml的方式下会判断容器为空时创建父容器
- 在里面会调用父容器的refresh方法加载
- 将父容器存入到Servlet域中供子容器使用
7. 初始化DispatcherServlet
外置tomcat会帮我们调用DispatcherServlet#init() 进行初始化—>重点关注: initWebApplicationContext方法
- (getServletContext())获得父容器(从之前的 Servlet域中拿到)
- cwac.setParent(rootContext);给子容器设置父容器
- 调用configureAndRefreshWebApplicationContext(cwac);