Spring MVC源码深入剖析
源码看SpringMVC工作流程
1.1 gradle搭建源码调试环境
1)搭建gradle环境
4个步骤
1、File-New-Module
选择java和web
2、填写包信息
3、存储路径
2)增加起步依赖
依赖的项目,直接复制粘贴上去
1、对spring的依赖
2、对MVC的依赖
3、对Tomcat插件的依赖
build.gradle
group 'com.spring.test'
version '5.0.2.RELEASE'
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'com.bmuschko.tomcat' //tomcat: 插件
// tomcat: 以下配置会在第一次启动时下载插件二进制文件
//在项目根目录中执行gradle tomcatRun
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.bmuschko:gradle-tomcat-plugin:2.5'
}
}
// 配置阿里源
allprojects {
repositories {
maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
}
}
dependencies {
testCompile group: 'org.testng', name: 'testng', version: '6.14.3'
runtime 'javax.servlet:jstl:1.1.2' // Servlet容器必需
compile(project(':spring-context'))
compile(project(':spring-web'))
compile(project(':spring-webmvc'))
// tomcat: 将Tomcat运行时库添加到配置tomcat中: (此处为Tomcat9)
def tomcatVersion = '9.0.1'
tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
"org.apache.tomcat.embed:tomcat-embed-logging-juli:9.0.0.M6",
"org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}"
}
// tomcat: 一些协议设置(注意,这里必须加上,不然会抛tomcat的异常,仅限tomcat9)
tomcat {
httpProtocol = 'org.apache.coyote.http11.Http11Nio2Protocol'
ajpProtocol = 'org.apache.coyote.ajp.AjpNio2Protocol'
}
// UTF-8
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
前提:
增加WEB-INF目录和Web.xml
1、打开File - Proect Structrue
2、选中刚才的mvc项目,展开,选中web gradle , 到右边 点击加号
3、确认路径
spring-mvc-test\src\main\webapp\WEB-INF\web.xml
WEB-INF和xml创建完毕
webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- Spring MVC配置 -->
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc-servlet.xml</param-value>
<!--<param-value>/WEB-INF/mvc-servlet.xml</param-value>-->
</init-param>
<!-- load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法) -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
resources/mvc-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.spring.mvc.test"/>
<!-- 视图解析器对象 -->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/>
<!--<property name = "prefix" value="/WEB-INF/"></property>-->
<property name="suffix" value=".jsp"/>
</bean>
<!-- 开启SpringMVC框架注解的支持 -->
<mvc:annotation-driven/>
<!--静态资源(js、image等)的访问-->
<mvc:default-servlet-handler/>
</beans>
webapp/index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>SpringMvc源码深入剖析</title>
</head>
<body>
hello ! ${username}
</body>
</html>
MvcController.java
package com.spring.mvc.test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class MvcController {
@RequestMapping("/index")
public ModelAndView getModeAndView() {
//创建一个模型视图对象
System.out.println("123");
ModelAndView mav = new ModelAndView("index");
mav.getModelMap().put("username","administrator");
return mav;
}
@RequestMapping("/text")
@ResponseBody
public String text() {
return "Text...";
}
}
4)启动MVC项目
两种启动方式
方式一:外挂启动
idea环境外面启动(项目根目录下运行 gradle + task name)
Task Name | Description |
---|---|
tomcatRun | 启动Tomcat实例并将Web应用程序部署到该实例。 |
tomcatRunWar | 启动Tomcat实例并将WAR部署。 |
tomcatStop | 停止Tomcat实例 |
tomcatJasper | 运行JSP编译器并使用Jasper将JSP页面转换为Java源代码。 |
在项目根目录中执行 gradle tomcatRun
#动Tomcat实例并将Web应用程序部署到该实例
gradle tomcatRun
#停止Tomcat实例
gradle tomcatStop
控制台正常输出
方式二:集成到idea中启动
即可点击运行
运行成功
方式三:
idea右边找到gradle的task,直接双击,这个爽~
访问MVC项目
注意:spring-test-mvc是项目的名称
http://localhost:8080/spring-test-mvc/index
效果如下
5)源码调试配置
如何在gradle环境中启用调试模式
重要
想要debug,需要使用上面的方法二,因为debug启动需要设置gradle的环境变量,
即运行gradle命令的时候插入一些参数命令。
增加debug参数,对外暴露5005端口,即监听5005端口。
-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005
在配置Remote;监听5005端口
点击+号,创建Remote;默认配置即可
最后一步
1、先运行tomcat
2、在运行remote
http://localhost:8080/spring-test-mvc/index
打上断点试试!
包括我们之前ioc里的bean创建等地方,随便打。
1.2 MVC工作原理和继承关系
1)MVC底层工作原理
目标:认识SpringMVC的工作原理(对照源码),如何找到对应的Controller,进行页面渲染的
步骤:11步
源头:http://localhost:8080/spring-test-mvc/index
SpringMVC工作原理
注意请求不是直接给DispatcherServlet(调度程序)而是通过tomcat转发得到,处理完后转发给tomcat
1、DispatcherServlet(前端控制器) 是个servlet,负责接收Request 并将Request 转发给对应的处理组件。
2、 HanlerMapping (处理器映射器)是SpringMVC 中完成url 到Controller 映射的组件。DispatcherServlet 从HandlerMapping 查找处理Request 的Controller
3、HanlerMapping 返回一个执行器链(url 到Controller 映射的组件)给DispatcherServlet
4、DispatcherServlet请求处理器适配器HandlerAdapter
5、处理器适配器HandlerAdapter去访问我们的handler(controller)
6、handler(controller)返回ModelAndView给处理器适配器HandlerAdapter
7、处理器适配器HandlerAdapter返回ModelAndView给DispatcherServlet
8、DispatcherServlet请求ViewResolver视图解析器
9、ViewResolver视图解析器返回view给DispatcherServlet
10、DispatcherServlet请求view做页面解析和渲染
11、view将渲染好的数据返回给DS,DS将渲染好的字符流给client,看到了页面!
2)MVC核心类继承关系
目标:简单认识MVC的继承关系
DispatcherServlet 前端总控制器(webmvc源码)
FrameworkServlet (webmvc源码)
HttpServletBean 是的一个简单扩展类((webmvc源码)
HttpServlet(servlet API , 已经离开了spring mvc的控制范围)
1.3 Spring MVC源码深入剖析
当前源码讲解思路
1、断点调试
2、流程图对照
3、继承关系对照
1.3.1 MVC启动阶段
注意,这个阶段没法debug,我们从servlet规范去直接看源码
下面的请求阶段,再详细debug请求链路的完整过程
web.xml回顾
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- Spring MVC配置 -->
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc-servlet.xml</param-value>
<!--<param-value>/WEB-INF/mvc-servlet.xml</param-value>-->
</init-param>
<!-- load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法) -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
从上面的配置,我们可以看出,web.xml中的DS是一个servlet,那就从java web的servlet规范说起
上面类关系,我们说过,springmvc的范畴里,最顶层的是 HttpServletBean 继承的 标准HttpServlet
1、ioC Bean初始化
org.springframework.web.servlet.HttpServletBean#init
2、9大组件初始化(ioC)
org.springframework.web.servlet.HttpServletBean#init
总结:方法调用关系(伪代码)
HttpServletBean{
init(){
protected initServletBean();
}
}
FrameworkServlet extends HttpServletBean{
@Override
initServletBean(){
initWebApplicationContext(){
WebApplicationContext wac = createWebApplicationContext(rootContext);
protected onRefresh(wac);
}
}
}
DispatcherServlet extends FrameworkServlet{
onRefresh(wac){
initStrategies(wac){
//多文件上传的组件
initMultipartResolver(context);
//初始化本地语言环境
initLocaleResolver(context);
//初始化模板处理器
initThemeResolver(context);
//初始化处理器映射器
initHandlerMappings(context);
//初始化处理器适配器
initHandlerAdapters(context);
//初始化异常拦截器
initHandlerExceptionResolvers(context);
//初始化视图预处理器
initRequestToViewNameTranslator(context);
//初始化视图转换器
initViewResolvers(context);
//FlashMap 管理器
initFlashMapManager(context);
}
}
}
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware
//功能: SpringMVC初始化配置,最先被执行的方法
//目标: 设置web.xml中配置的contextConfigLocation属性
//重点关注:initServletBean();
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
//获得web.xml中的contextConfigLocation配置属性, 是spring MVC的配置(servlet)文件
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); // ===>
if (!pvs.isEmpty()) {
try { //这里是一堆的初始化操作,不管他,重点在下面!
//返回一个BeanWrapperImpl实例
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//获取服务器的各种信息
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//模板方法,可以在子类中调用,做一些初始化工作,bw代表DispatcherServelt
initBeanWrapper(bw);
//将配置的初始化值设置到DispatcherServlet中
bw.setPropertyValues(pvs, true);
} catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
//初始化操作,抽象的。子类实现; 【重点关注】 !!!!!!!(ioC 、9大组件)
initServletBean(); // ===>
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware
//初始化servlet的bean
//重点关注:initWebApplicationContext
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
// 这里初始化上下文!!【重要节点】!!!!!(ioC 、9大组件)
this.webApplicationContext = initWebApplicationContext(); // ===>
initFrameworkServlet(); // 空的,不管他
} catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
} catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware
//目标:主要的逻辑就是初始化IOC 容器和9大组件,最终会调用refresh()方法
//重点关注:createWebApplicationContext和onRefresh
protected WebApplicationContext initWebApplicationContext() {
// 调用WebApplicationContextUtils来得到根上下文,它保存在ServletContext中
// 通过servlet的application listener加载的那些 spring xml,是父容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
// 查找已经绑定的上下文,有的话直接用
wac = findWebApplicationContext();
}
if (wac == null) {
//还是没有的话,开始创建,将rootcontext作为父容器穿进去。
wac = createWebApplicationContext(rootContext); // ===>
}
if (!this.refreshEventReceived) {
//9大组件!!【关键步骤】!!!!
onRefresh(wac); // ===>
}
if (this.publishContext) {
// 把当前建立的上下文存到ServletContext中,使用的属性名是跟当前Servlet名相关的
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
接下来应该是初始化,会进createWebApplicationContext这个方法中
//重点关注configureAndRefreshWebApplicationContext
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 设置双亲上下文(也就是根上下文)
wac.setParent(parent);
// contextConfigLocation 属性!
// 也就是web.xml里配置的spring-mvc的xml文件
String configLocation = getContextConfigLocation(); // ===> 配置的xml文件
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
//配置和刷新web应用程序上下文(refresh!!!!!!!)
configureAndRefreshWebApplicationContext(wac); // ===> ioc加载上下文
return wac;
}
最终还是走到configureAndRefreshWebApplicationContext来进行初始化操作
// 完成所有bean的解析、加载和初始化
//重点关注:wac.refresh()
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
//上来一堆设置,不管他,重点在最下面!
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
// 生成WebApplicationContext的id,用于后面加载Spring-MVC的配置文件
wac.setId(this.contextId);
} else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
//设置Servlet
wac.setServletContext(getServletContext());
//设置Servlet配置信息
wac.setServletConfig(getServletConfig());
//设置命名空间
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
//后置增强,一般是空的
postProcessWebApplicationContext(wac);
//初始化,不管他
applyInitializers(wac);
//【重点】调用了ioc的刷新!参考ioc源码课程!!!!!!!!
wac.refresh(); // ===> 进入ioc的世界!完成bean的载入
}
接下来就是老伙计了IOC
// 模板方法(抽象类下定义refresh模板),核心方法
//断点查看
@Override
public void refresh() throws BeansException, IllegalStateException {
// synchronized块锁(monitorenter --monitorexit)
// 不然 refresh() 还没结束,又来个启动或销毁容器的操作
// startupShutdownMonitor就是个空对象,锁
synchronized (this.startupShutdownMonitor) {
//1、【准备刷新】,设置了几个变量,也是准备工作
prepareRefresh(); // ===>
// 2、【获得新的bean工厂】关键步骤,重点!
//2.1、关闭旧的 BeanFactory
//2.2、创建新的 BeanFactory(DefaluListbaleBeanFactory)
//2.3、解析xml/加载 Bean 定义、注册 Bean定义到beanFactory(不初始化)
//2.4、返回全新的工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // ===> 好戏开始
//3、【bean工厂前置操作 】为BeanFactory配置容器特性
// 例如类加载器、表达式解析器、注册默认环境bean、后置管理器
prepareBeanFactory(beanFactory); // ===>
try {
// 4、【bean工厂后置操作】此处为空方法,如果子类需要,自己去实现
postProcessBeanFactory(beanFactory); // ===> 空的!
//5、【调用bean工厂后置处理器】,开始调用我们自己实现的接口
//目标:
//调用顺序一:先bean定义注册后置处理器
//调用顺序二:后bean工厂后置处理器
invokeBeanFactoryPostProcessors(beanFactory); // ===> 重头戏
//6、【注册bean后置处理器】只是注册,但是还不会调用
//逻辑:找出所有实现BeanPostProcessor接口的类,分类、排序、注册
registerBeanPostProcessors(beanFactory); // ===> 关键点
// Initialize message source for this context.
//7、【初始化消息源】国际化问题i18n,参照https://nacos.io/
initMessageSource(); // ===> 就是往factory加了个single bean
// Initialize event multicaster for this context.
//8、【初始化事件广播器】初始化自定义的事件监听多路广播器
// 如果需要发布事件,就调它的multicastEvent方法
// 把事件广播给listeners,其实就是起一个线程来处理,把Event扔给listener处理
// (可以通过 SimpleApplicationEventMulticaster的代码来验证)
initApplicationEventMulticaster(); // ===> 同样,加了个bean
// 9、【刷新】这是个protected空方法,交给具体的子类来实现
// 可以在这里初始化一些特殊的 Bean
// (在初始化 singleton beans 之前)
onRefresh(); // ===> 空的!一般没人管它
//10、【注册监听器】,监听器需要实现 ApplicationListener 接口
// 也就是扫描这些实现了接口的类,给他放进广播器的列表中
// 其实就是个观察者模式,广播器接到事件的调用时,去循环listeners列表,
// 挨个调它们的onApplicationEvent方法,把event扔给它们。
registerListeners(); // ===> 观察者模式
//11、 【结束bean工厂初始化操作】
//1、初始化所有的 singleton beans,反射生成对象/填充
//2、 调用Bean的前置处理器和后置处理器
// 关键点:getBean方法里完成
finishBeanFactoryInitialization(beanFactory); // ===> 关键点!
// 12、结束refresh操作
// 发布事件与清除上下文环境
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
// 销毁已创建的Bean、以免有些 bean 会一直占用资源
destroyBeans();
// Reset 'active' flag.
// 取消refresh操作,重置容器的同步标识。
cancelRefresh(ex);
// 把异常往外抛
throw ex;
} finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
这里有一点,在初始化后调用finishRefresh()
在refresh完成后发了一个完成初始化的事件
protected void finishRefresh() {
// 1、清除缓存,其实就是一顿clear
clearResourceCaches();
// 2、LifecycleProcessor接口初始化,就是注册了个LifecycleProcessor的bean进去
// ps:当ApplicationContext启动或停止时,它会通过LifecycleProcessor
// 来与所有声明的bean的周期做状态更新
// 而在LifecycleProcessor的使用前首先需要初始化
initLifecycleProcessor();
// 3、启动所有实现了LifecycleProcessor接口的bean
//挨个调它们的start方法。一般没人用
getLifecycleProcessor().onRefresh();
// 4、发布一个 ContextRefreshedEvent事件
//宣布一下,refresh完成了,如果有需要的listener,那就去处理,一般没人关心
publishEvent(new ContextRefreshedEvent(this));
// 5、把当前容器注册到到MBeanServer,jmx会用
LiveBeansView.registerApplicationContext(this);
}
然后对应的事件被接受到了,在FrameworkServlet
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
onRefresh(event.getApplicationContext());
}
@Override
protected void onRefresh(ApplicationContext context) {
//初始化策略
initStrategies(context); // ===>
}
最终初始化
//一顿初始化……【核心步骤】
// 其实就是从前面初始化好的context里扒拉出对应类型的bean,然后
// 将DS里面的对应属性给填充上去,方便下面请求阶段的使用
// 到这里为止,其实最核心的启动阶段就完成了,需要的东西也都有了。
protected void initStrategies(ApplicationContext context) {
//多文件上传的组件
initMultipartResolver(context);
//初始化本地语言环境
initLocaleResolver(context);
//初始化模板处理器
initThemeResolver(context);
//初始化处理器映射器
initHandlerMappings(context);
//初始化处理器适配器
initHandlerAdapters(context);
//初始化异常拦截器
initHandlerExceptionResolvers(context);
//初始化视图预处理器
initRequestToViewNameTranslator(context);
//初始化视图转换器
initViewResolvers(context);
//FlashMap 管理器
initFlashMapManager(context);
}
1.3.2 MVC请求阶段
需求:我们在浏览器输入http://localhost:8080/spring-test-mvc/index,背后到底做了哪些事情
目标:MVC如何通过一个url就能找到我们的controller,并返回数据
1、断点调试 2、流程图对照 3、继承关系对照
流程图解:
代码查找的路径:
spring mvc的 FrameworkServlet ,这是我们源码跟踪的入口
项目启动
访问
http://localhost:8080/spring-test-mvc/index
入口:开启请求的大门
org.springframework.web.servlet.FrameworkServlet:
//MVC请求入口(浏览器)
//比如,我们在浏览器输入http://test.io/get,背后到底做了哪些事情
//目标:MVC如何通过一个url就能找到我们的controller,并返回数据
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//请求是否GET
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
//判断请求方式,发现不是PATCH方式就去调用父类(HttpServlet)中service()方法
if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
processRequest(request, response);
} else {
// 调用用父类中的service方法,
// 调用父类的service 然后正在调用当前类doGet!!!!!!!!!
super.service(request, response); // ===> 启程~
}
}
java web标准告诉我们,request的get会交给标准 HttpServlet的doGet方法
而这个类FrameworkServlet,是HttpServlet的子类,覆盖了上述的doGet,
所以,请求进入spring的第一入口,就在这里!!!
1)org.springframework.web.servlet.FrameworkServlet#doGet
调用到了org.springframework.web.servlet.FrameworkServlet#doGet
//get请求调用,覆盖了 父类, 也就标准 HttpServlet 的 doGet
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response); // ===> get转交给process处理
}
2)org.springframework.web.servlet.FrameworkServlet#processRequest
// 重点关注:doService
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取系统当前时间戳
long startTime = System.currentTimeMillis();
// 申明一个异常顶层对象Throwable
Throwable failureCause = null;
// 本地化、国际化处理
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 构建一个本地国际化上下文,SimpleLocaleContex
LocaleContext localeContext = buildLocaleContext(request);
// 获取当前绑定到线程的RequestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 构建ServletRequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 将localeContext和requestAttributes放入当前线程中,
// request作用域每个请求线程都不一样,参数,attribute需要上下文直接传递
// (回顾:request,session,application)
initContextHolders(request, localeContext, requestAttributes); // ===>
try {
//前面都是一堆准备工作,重点在这里!跳到DispatcherServlet 类中(子类重写) 【关键点!】
doService(request, response); // ===> 进入DS的世界!
} catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
} catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
} finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
} else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
} else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
3)org.springframework.web.servlet.DispatcherServlet#doService
//将请求交给doDispatch来处理 重点关注 doDispatch(request, response);
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects
// 扔一堆东西进request.
// web上下文,可以在后面的请求链路上通过request.getAttribute取出来使用
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); // 本地化处理器
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); // 主题处理器,2003年的产物
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); // 主题资源文件
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
//重点关注,核心!!!!!!!!!!!!!!!!!!!!
doDispatch(request, response); // ===> 没说的,进!
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
进入核心
4)org.springframework.web.servlet.DispatcherServlet#doDispatch
// Spring MVC的最核心代码
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);
// 根据当前的请求去拿一个Handler.查找处理器映射器,进入!!!!!!!!!!!
mappedHandler = getHandler(processedRequest); // ===> 图例第2-3步
if (mappedHandler == null) {
// 如果当前请求没有handler进行处理,那么就抛出异常
noHandlerFound(processedRequest, response);
return;
}
// handler适配器,找到:RequestMappingHandlerAdapter ,可以通过debug变量查看类型
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // ===> 图例第4步
// get方法
String method = request.getMethod();
//get方法为true
boolean isGet = "GET".equals(method);
//method为get
if (isGet || "HEAD".equals(method)) { // 处理last-modified , 浏览器缓存时间
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//循环调拦截器对应的 pre 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) { // ===> applyPreHandle
return;
}
// 让handlerAdapter去执行业务控制器方法, 真正进入controller的入口!
// com.spring.mvc.test.MvcController.getModeAndView
// 让它来返回modelAndView对象! debug仔细查看 mv 变量的值
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // ===> controller入口,走起~
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 设置默认视图,当返回了mav,但是又没设置具体view的时候
applyDefaultViewName(processedRequest, mv); // ===>
mappedHandler.applyPostHandle(processedRequest, response, mv); // ===> 拦截器后置调用 postHandle
} 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);
}
//1、请求视图解析器,解析成view , 实例图8-9步
//2、执行页面渲染(jsp) , 示例图 10-11步
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); // ===> go!
} 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);
}
}
}
}
getHandler
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//不止一个,比如BeanNameHandlerMapping、SimpleUrlHandlerMapping,
// 还有我们需要的RequestHandlerMapping
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
//这个就是执行器链,里面包含了需要处理这个请求的controller
HandlerExecutionChain handler = hm.getHandler(request); // ===> 【关键点】怎么根据url取到controller的?
if (handler != null) {
return handler;
}
}
}
return null;
}
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request); // ===> 找到controller和方法的地方!
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) { // 如果是字符串,从factory取出对应的bean来
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
//所谓的handler链就是这里!其实就是包装一下,加上拦截器列表,详情看 HandlerExecutionChain 的源码结构!
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); // ===>
if (CorsUtils.isCorsRequest(request)) { //如果是跨域请求(从header里加了Origin的话)
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链返回给 DS
}
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); // 请求的url值
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
this.mappingRegistry.acquireReadLock(); //加一把读锁,也就是说mapping是可以被修改的!
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); // ===> 藏在这里!
if (logger.isDebugEnabled()) {
if (handlerMethod != null) {
logger.debug("Returning handler method [" + handlerMethod + "]");
}
else {
logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
//return之前处理一下。如果是string的话,找到对应的bean返回去
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); // ===>
}
finally {
this.mappingRegistry.releaseReadLock(); //释放掉!
}
}
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
//定义一个集合,存储所有匹配上的方法
List<Match> matches = new ArrayList<>();
// mappingRegistry , 映射关系 component-scan扫进去的!debug一下,留心里面的 mappingLookUp……
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) { // directPathMatches,所有匹配上的,可能有多个符合条件的
addMatchingMappings(directPathMatches, matches, request); // 把他们整理一下,塞到matches这个list里
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator); //根据定义的比较器,对多个匹配的method排序
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
lookupPath + "] : " + matches);
}
Match bestMatch = matches.get(0); // 取匹配优先级最高的那个
if (matches.size() > 1) {
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) { // 如果第1 第 2 两个匹配优先级相同
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
request.getRequestURL() + "': {" + m1 + ", " + m2 + "}"); // 扔出异常,提示重复
}
}
handleMatch(bestMatch.mapping, lookupPath, request); // ===> 没干啥事,映射关系在request里设置了个attribute
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
拦截器这一块
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
//返回handler链,其实就是包装了handler和MappedInterceptor类型的拦截器们到一个对象里
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
getHandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
//初始化9大组件里的 initHandlerAdapters, 本类的onRefresh方法里
if (this.handlerAdapters != null) {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
调用业务Controller(核心)
执行我们的业务控制器方法,com.spring.mvc.test.MvcController.getModeAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInterna
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return true;
}
// 开始调用业务控制器
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 开始调用业务控制器!!!!!!!!!!!!!!!!!!!!!
mav = invokeHandlerMethod(request, response, handlerMethod); // ==> go!
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// 巴拉巴拉一堆前期set准备操作,不管他,往下看!
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
//反射调用业务控制器开始!!!【关键点】!!!!!!
invocableMethod.invokeAndHandle(webRequest, mavContainer); // ===> 反射调用
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//反射调用
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // ===>
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
}
processDispatchResult(视图解析和渲染)
org.springframework.web.servlet.DispatcherServlet#
//1、请求视图解析器,解析成view
//2、执行页面渲染(jsp)
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
//如果有异常,处理异常,也就是我们平时搞的默认错误页
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
} else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);// 其实就是交给系统默认handler去处理
errorView = (mv != null);
}
}
if (mv != null && !mv.wasCleared()) {
//视图解析器+渲染!!!【关键点】
render(mv, request, response); // ===> 进去!
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale); // 本地化信息
View view;
//viewName=index,也就是controller返回的 mav 里的 路径字符串
String viewName = mv.getViewName();
if (viewName != null) {
// 获取视图解析器、解析成view (jstlview),下面渲染,
view = resolveViewName(viewName, mv.getModelInternal(), locale, request); // ===> 准备好了吗?层级很深
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
} else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value()); // 如果有异常码,设置给response
}
//执行页面渲染, AbstractView.render 【关键点】
view.render(mv.getModelInternal(), request, response); // ===> 图例10-11步,渲染视图,扔给response
} catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}
具体解析
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
// 注意,调试的时候,如果请求过相同的视图,会有缓存,那怎么办呢?
// 我们给他在最前面加一行,让他永远create,
// 注意,下面这行是我们为了调试自己加的!原始代码从if开始……
createView(viewName, locale); // ===> 自己加的,debug 从这里进去
if (!isCache()) {
return createView(viewName, locale);
}
else {
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}
@Override
protected View createView(String viewName, Locale locale) throws Exception {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
if (!canHandle(viewName, locale)) {
return null;
}
// Check for special "redirect:" prefix.
if (viewName.startsWith(REDIRECT_URL_PREFIX)) { // redirect:开头的路径,进行重定向操作!
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
String[] hosts = getRedirectHosts();
if (hosts != null) {
view.setHosts(hosts);
} // 设置一堆的重定向信息,url,host等
return applyLifecycleMethods(viewName, view);
}
// Check for special "forward:" prefix.
if (viewName.startsWith(FORWARD_URL_PREFIX)) { // 同样,forward: 开头的
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
return super.createView(viewName, locale); // ===> 如果都不是,进行create,这才是常规操作!
}
进到最后一个create中
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
Class<?> viewClass = getViewClass();
Assert.state(viewClass != null, "No view class");
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
view.setUrl(getPrefix() + viewName + getSuffix()); // 关键点! 把你设置的前缀 + 名字 + 后缀,作为模板路径!
// 后面一堆的set各种属性……,
// 不管他!设置完成之后,最下面return回去
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
if (exposeContextBeansAsAttributes != null) {
view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
}
String[] exposedContextBeanNames = getExposedContextBeanNames();
if (exposedContextBeanNames != null) {
view.setExposedContextBeanNames(exposedContextBeanNames);
}
return view;
}
接下来是渲染render
@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
//整理合并模型的数据,收集到一个map里,
// 断点查看 mergedModel 的值,找到controller里定义的数据~~~
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response); //设置了俩header,不管他
//执行,重点在这里!【关键点】
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); // ===> 进!
}
最后一步,转发给给servlet
@Override
protected void renderMergedOutputModel(
// 这里把所有的相关信息都收集到一个Map里
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
//把响应数据设置到request对象的attribute中,数据就交给了servlet内置容器
// debug查看 model 的数据试试!会发现我们绑定过的数据~~~
exposeModelAsRequestAttributes(model, request); // ===> 【关键点:数据绑定】
// Expose helpers as request attributes, if any.
exposeHelpers(request); // ===> 就设置了俩header
//获取到跳转的地址,预处理路径
// 判断是否跳转路径死循环
String dispatcherPath = prepareForRendering(request, response); // ===>
//servlet api,得到转发路径。
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType()); // content-type设置
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(request, response);
} else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
//forward转发!
// 后面的步骤就交给了servlet容器。jsp解析,返回给前台浏览器。
rd.forward(request, response);
}
}
总结
- tomcat接收到请求后发给DispatcherServlet
- DispatcherServlet接收到请求后去handle中查询对应的url地址,找到匹配度最高的一条
- 组装成执行链路(具体执行的业务和拦截器),返回给DispatcherServlet
- 处理器映射器执行拦截器中的pre方法->处理器反射调用业务控制器处理业务->处理器映射器执行拦截器中的postHandle(后处理器),并将结果返回给DispatcherServlet
- DispatcherServlet交给视图解析器进行路径拼接和数据绑定,设置响应信息和一些检查(否跳转路径死循环)
- 最终forward转发给tomcat,通过tomcat响应给客户端
源码中视图解析和渲染放在一块做了,所以这里写一块了。