web.xml

它的作用是配置初始化信息,如web页面、servlet、servlet-mapping、filter、listener、启动加载级别等。

SpringMVC 通过servlet拦截所有的URL来达到控制的目的,所以它必须要有web.xml

比较关键的配置是:

  • contextConfigLocation 配置spring配置文件地址

  • DispatcherServlet 前端控制器

程序入口

ContextLoaderListener.initWebApplicationContext().createWebApplicationContext()

加载ApplicationContext 并注入Servlet容器

先判断contextClass 属性是否配置,否则加载默认的:XmlWebApplicationContext

protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
// 默认值:org.springframework.web.context.support.XmlWebApplicationContext 可继承它修改容器配置
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}

ContextLoader 中的static 静态语句块可以知道加载的配置文件是: ContextLoader.properties

static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
// DEFAULT_STRATEGIES_PATH = ContextLoader.properties
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}

以上为容器加载阶段,详细细节之前章节已经讲述,不再赘述

ContextLoader 的 initWebApplicationContext 方法中,发现如下代码:

    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

servlet容器持有该引用

servlet

Servlet是按照servlet规范使用Java编写的程序,基于HTTP协议运行在服务器端,它的声明周期分为:初始化、运行、销毁。

初始化

  • servlet容器加载servlet类,把它的.class字节码文件读取到内存中
  • servlet容器创建一个ServletConfig对象,它包含该servlet的初始化配置信息
  • servlet容器创建一个 servlet 对象
  • servlet容器调用servlet对象的init() 方法进行初始化

运行阶段

  • servlet容器接收到请求时,会根据请求创建一个servletRequest(请求信息) 对象和servletResponse(封装返回信息) 对象,

    调用service方法并处理请求,通过servletResponse相应请求后销毁这两个对象。

销毁阶段

  • Web应用终止,servlet容器调用servlet对象的destory方法,然后销毁servlet对象以及相关的 servletConfig对象。

DispatcherServlet

它是SpringMVC的核心, 是servlet的一个实现类

初始化

在它的父类HttpServletBean中找到了init方法的调用

该方法只是初始的配置信息加载

public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
} // Set bean properties from init parameters.
// 解析 <init-param> 并验证
// requiredProperties 配置必须要的参数,否则抛出异常
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// servlet 转为 BeanWrapper 从而可以像spring那样 对 init-param 的值进行注入
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);// 钩子函数
bw.setPropertyValues(pvs, true);// 注册到自定义属性编辑器
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
} // Let subclasses do whatever initialization they like.
initServletBean();// 钩子函数 if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}

继续,在父类 FrameworkServlet 中找到了钩子函数:initServletBean方法的具体实现:

有模板方法模式那味儿了,很遗憾这里还是准备工作

@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis(); try {
// 对WebApplicationContext进一步初始化和补充
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
} if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}

然后 initWebApplicationContext() 方法对容器进一步初始化和补充

protected WebApplicationContext initWebApplicationContext() {// 对WebApplicationContext进一步初始化和补充
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());// 从 容器 servletContext 中获取
WebApplicationContext wac = null; if (this.webApplicationContext != null) { // webApplicationContext 是否在构造函数中被注入 (未解析过) new DispatcherServlet()->.super(WebApplicationContext)
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// WebApplicationContext 是否被 contextAttribute 属性注入
wac = findWebApplicationContext();
}
if (wac == null) {
// 既无构造器注入,也无contextAttribute属性注入,那么通过初始化的 WebApplicationContext 构造新的容器
wac = createWebApplicationContext(rootContext);
} if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
onRefresh(wac);// 加载配置 钩子,由子类 DispatcherServlet 实现,用于 Spring Web功能的 相关解析器的初始化
}
} if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
} return wac;
}

跟进 方法configureAndRefreshWebApplicationContext() 发现了我们的老朋友 refresh() 方法,是不是很眼熟?

ApplicationContext 容器加载过程中 它近乎是一切的起点了,查看默认的容器类XmlWebApplicationContext 的类图不难证实这点

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
...
......
.........
wac.refresh();// ConfigurableApplicationContext.refresh() <- AbstractApplicationContext.refresh() <- XmlWebApplicationContext
}

然后看initWebApplicationContext()方法内调用的,onRefresh()方法

FrameworkServlet 类中找到的onRefresh() 又是空方法,不解释,钩子函数它又来了,最后回到DispatcherServlet类,发现了该方法的具体定义:

该方法的主要功能是刷新Spring在Web功能实现中所必须使用的全局变量的初始化

从配置文件:DispatcherServlet.properties 可得知部分全局变量所使用的默认值


@Override
protected void onRefresh(ApplicationContext context) {// 用于 Spring Web功能的 相关解析器的初始化
initStrategies(context);
} /**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {// 各个全局功能解析器初始化 initMultipartResolver(context);// 处理文件上传 initLocaleResolver(context);// 国际化配置? 基于:url session cookie 支持国际化 initThemeResolver(context);// Theme主题控制网页风格 // 可以有多个HandleMapping,根据优先级访问,直到获取到可用的Handle 为止 (Ordered 接口控制)
initHandlerMappings(context);// 处理客户端发起的Request请求,根据WebApplicationContext的配置来,回传给 DispatcherServler 对应的Controller // DispatcherServlet 通过处理器映射(HandleMapping)配置,得到处理器(Handle),之后会轮询处理器(Handle)的<配适器模块>
// 并查找能够处理当前HTTP请求的处理器(Handle),的配适器实现(Adapter)
// org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter 对Http请求处理器进行配适 OtherClass <- HttpAdapter <- HttpHandle
// org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter 将Http请求配饰到一个Controller 的实现进行处理
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter 用于执行Controller方法的配适器,对注解方式的支持
initHandlerAdapters(context);// 配适器 initHandlerExceptionResolvers(context);// 异常处理 initRequestToViewNameTranslator(context);// 当Controller没有返回任何View对象或者逻辑视图名称,并在该方法中没有向response的输出流里面写任何数据,那么spring会使用约定方式提供一个逻辑视图名称。 // resolverViewName 方法 根据 viewName创建合适的View 实现
initViewResolvers(context);// Controller 计算结束后将结果封装到ModleAndView,DispatcherServlet 会根据ModleAndView 选择合适的视图进行渲染 initFlashMapManager(context);// SpringMVC Flash attributes 提供了属性存储功能,可够重定向时其它请求使用
}

DispatcherServlet 的逻辑处理

看 HttpServlet 类的结构 看关键的doGet和doPost,在FrameworkServlet类中找到了如下方法实现:

    @Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { processRequest(request, response);
} @Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { processRequest(request, response);
} protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 主要目的:提取请求参数,用于重定向
long startTime = System.currentTimeMillis();
Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();// 为了后续请求能使用提取属性
LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();// 为了后续请求能使用提取属性
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); // 初始化
initContextHolders(request, localeContext, requestAttributes);
try {
// 准备工作 具体实现由 DispatcherServlet 提供
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);// 设置提取的请求参数,用于重定向
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
publishRequestHandledEvent(request, response, startTime, failureCause);// 事件通知
}
}

忽略准备工作:doService().doDispatch(HttpServletRequest request, HttpServletResponse response)

如下为核心代码逻辑,之前提到的全部变量配置将登上舞台了

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 {
// 检查全局变量
// 如果请求类型为:multipartContent 将 HttpServletRequest 转为 MultipartHttpServletRequest (包装器 ? 策略模式)
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);// 检查原类型 包装 / 代理 // Determine handler for the current request.
// 根据URL 匹配
mappedHandler = getHandler(processedRequest);// 按优先级从各个HandleMapping 中获取Handle
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 根据handle 获取匹配的配适器 Adapter
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {// last-modified(缓存处理机制) 请求头处理 <最后修改时间>
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
// 未变化? 最后修改时间未变? 过滤重复请求???
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
} // 调用拦截器的preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
} // Actually invoke the handler.
// 激活Handle 并返回视图 (由配适器的 handle方法完成 )
// 查看配置文件DispatcherServlet.properties 可以知道 HandlerAdapter ha 的具体实现类,跟踪handle方法:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) {
return;
} applyDefaultViewName(processedRequest, mv);// 视图名称处理
// 调用所有拦截器的postHandle 方法,如果存在的话
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
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()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

查看配置文件 DispatcherServlet.properties 可以知道 HandlerAdapter ha 的具体实现类,跟踪handle方法:

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,

org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

  1. getHandle()

    AbstractHandlerMapping.getHandel().[ AbstractUrlHandlerMapping.getHandlerInternal() ]

    根据请求url获取handel
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);// 截取 url
Object handler = lookupHandler(lookupPath, request);// 根据 url 获取 handle
if (handler == null) {// 获取到的解析器为空
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {// 请求路径仅为根路径: 则使用RootHandle处理
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();// 否则设置默认的 Handle
}
if (rawHandler != null) { // 默认 Handle 可能为空
// Bean name or resolved handler?
if (rawHandler instanceof String) { // 若查找的 Handle 类型为String 则为beanName 否则为 Handle 本身
String handlerName = (String) rawHandler;
rawHandler = obtainApplicationContext().getBean(handlerName);// 从容器中获取
}
validateHandler(rawHandler, request);// 校验钩子函数 // 初始化 Handle ??? HandlerExecutionChain 对 Handle 进行包装
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Mapping [" + lookupPath + "] to " + handler);
}
else if (handler == null && logger.isTraceEnabled()) {
logger.trace("No handler mapping found for [" + lookupPath + "]");
}
return handler;
}

2.getHandlerAdapter

根据handel获取配饰器Adapter

SimpleControllerHandlerAdapter.suport

public boolean supports(Object handler) {
return (handler instanceof Controller);
}

看到controller主要解析就完成了,剩下的事情就是处理请求,并绑定视图放回,以及当发生异常时对异常视图进行处理。

Spring源码之springMVC的更多相关文章

  1. Spring源码解析 - springMVC核心代码

    一.首先来讲解下springMVC的底层工作流程 1.首先我们重点放在前端控制器(DispatcherServlet) 其类图: 因为从流程图看,用户的请求最先到达就是DispatcherServle ...

  2. spring源码学习之springMVC(一)

    个人感觉<Spring技术内幕:深入解析Spring架构与设计原理(第2版)>这本书对spring的解读要优于<Spring源码深度解析(第2版)>这本书的,后者感觉就是再陈述 ...

  3. Spring源码学习之:FactoryBean的使用

    转载:http://book.51cto.com/art/201311/419081.htm ==========个人理解========================= FactoryBean和B ...

  4. Spring源码情操陶冶-AbstractApplicationContext#finishRefresh

    承接前文Spring源码情操陶冶-AbstractApplicationContext#finishBeanFactoryInitialization 约定web.xml配置的contextClass ...

  5. Spring源码情操陶冶-自定义节点的解析

    本文承接前文Spring源码情操陶冶-DefaultBeanDefinitionDocumentReader#parseBeanDefinitions,特开辟出一块新地来啃啃这块有意思的骨头 自定义节 ...

  6. Spring源码阅读笔记

    前言 作为一个Java开发者,工作了几年后,越发觉力有点不从心了,技术的世界实在是太过于辽阔了,接触的东西越多,越感到前所未有的恐慌. 每天捣鼓这个捣鼓那个,结果回过头来,才发现这个也不通,那个也不精 ...

  7. Spring 源码学习系列

    前言 Spring框架之于 JavaEE 程序员来说,犹如锄头之于农民.Java 程序员每天都要使用Spring框架,Spring框架也确实是个可手的工具. 最初使用Spring的时候,我们需要配置m ...

  8. Spring 源码分析-1-启动

    Spring 源码分析-1-启动 在web项目中使用spring的时候,我们会在web.xml中加入如下配置: <listener> <listener-class>org.s ...

  9. 深入源码分析SpringMVC底层原理(二)

    原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...

  10. spring 源码解析

    1. [文件] spring源码.txt ~ 15B     下载(167) ? 1 springн┤┬вио╬Ш: 2. [文件] spring源码分析之AOP.txt ~ 15KB     下载( ...

随机推荐

  1. 银河麒麟上面 ntopng的安装与使用

    银河麒麟上面 ntopng的安装与使用 背景 一直想用Grafana监控网络流量 但是断断续续尝试了一周的时间都没有搞定. 发现这一块已经进入了瓶颈. 比较无奈的情况下回到了原来的iftop/iptr ...

  2. [转帖]docker(一):docker pull指定运行平台架构

    https://zhuanlan.zhihu.com/p/539888862 1.概述 大家好,我是欧阳方超.某日要在服务器上部署docker服务,其中要用到nginx,nginx经过pull.sav ...

  3. 【转帖】Ethernet 与 Infiniband的网络特性对比

    一.两者定位 以太网(Ethernet): 应用最广泛,是最成熟的网络互联技术,也是整个互联网络大厦的基石,兼容性非常好,可实现不同的系统之间的互连互通 IB(Infiniband): 领域很专,作为 ...

  4. [转帖]基于 Skywalking 部署应用性能监控

    https://www.jianshu.com/p/50627b9ab0be 今天我们就着重讲一讲如何基于 Skywalking 来快速搭建一套应用性能监控平台   walkingfunny.com. ...

  5. [转帖]12.计算机网络---iptables防火墙管理工具

    文章目录 一.防火墙基础知识 1.1 防火墙是什么? 1.2 iptables基础知识 1.3 netfilter和iptables的关系: 1.4 新型防火墙工具:firewalld 二.iptab ...

  6. Beyond Compare 的比较以及导出的简单设置方法

    最近需要对文件进行对比 但是发现对比的工作量比较难搞. 用到了beyond compare 的工具 感觉挺好用的 但是需要注意事项比较多这里记录一下 1.  session setting 里面进行设 ...

  7. 计划任务方式定期获取jvm dump的方法

    说明 产品最近有一些问题,想着能够每隔一段时间抓取一下dump文件. 需求 可以定期抓取, 需要注意磁盘空间的使用. 实现方法 定时任务使用 crontab 计划任务来做 预定义获取jvm dump的 ...

  8. 【踩了一个坑】为什么 golang struct 中的 slice 无法原子赋值

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 有这样一个结构体: type MySt struct{ F ...

  9. 设计模式学习-使用go实现命令模式

    命令模式 定义 优点 缺点 适用范围 代码实现 命令模式对比策略模式 参考 命令模式 定义 命令模式(Command):将一个请求封装成一个对象,从而是你可用不同的的请求对客户进行参数化:对请求排队或 ...

  10. C++ Qt开发:运用QJSON模块解析数据

    Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用QJs ...