springmvc是一个基于servlet容器的轻量灵活的mvc框架,在它整个请求过程中,为了能够灵活定制各种需求,所以提供了一系列的组件完成整个请求的映射,响应等等处理。这里我们来分析下springMVC的源码。

首先,spring提供了一个处理所有请求的servlet,这个servlet实现了servlet的接口,就是DispatcherServlet。把它配置在web.xml中,并且处理我们在整个mvc中需要处理的请求,一般如下配置:

<servlet>
<servlet-name>spring-servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>spring-servlet</servlet-name>
<url-pattern>/</url-pattern>
可以看到这里就触发了spring的对web支持的容器初始化,这里使用的容器为WebApplicationContext.接下来我们就来分析一下整个容器的初始化过程:

protected WebApplicationContext initWebApplicationContext() {
// 查看是否在servlet上下文有所有容器需要继承的根Web容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 如果容器已经加载
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
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)www.yuheng119.com/ {
// 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) {
// 创建容器
wac = createWebApplicationContext(rootContext);
}

if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}

if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled(www.xucaizxyl.com)) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}

return wac;

如果已经有容器被创建那就初始化。否则创建容器,创建逻辑:

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '"027yeshenghuowang.com + 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(www.yigouyule2.cn/));
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
// 这里是获取配置文件和完成初始化web容器的入口
configureAndRefreshWebApplicationContext(wac);

return wac;

这里完成了 web容器的相关准备工作,开始正式读取配置文件加载和初始化容器。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
// 给容器设置一个id
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) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}

wac.setServletContext(getServletContext());
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);
// 这里就开始web容器的初始化

容器的初始化在AbstractApplicationContext,无论是其他的容器,最终都会调用到refresh()函数,这个函数基本定义了整个容器初始化的整个脉络,这里不展开讲,本博客之后应该会详细分析这块的逻辑,这里大概的注释一下每一个函数完成的操作:

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 这里主要加载了容器当中一些从其他配置文件读取的变量
prepareRefresh();

// 获取容器本身
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// 这里完成一些基础组件的依赖
prepareBeanFactory(beanFactory);

try {
// 添加 容器初始化之前的前置处理
postProcessBeanFactory(beanFactory);

// 调用 前置处理器,其中包含invokeBeanDefinitionRegistryPostProcessors与invokeBeanFactoryPostProcessors两类前置处理器的调用
invokeBeanFactoryPostProcessors(beanFactory);

// 注册bean被创建之前的前置处理器
registerBeanPostProcessors(beanFactory);

// 初始化容器的编码源
initMessageSource();

// 初始化一些事件监听器
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// 注册容器监听器
registerListeners();

// 初始化所有非懒加载的beans
finishBeanFactoryInitialization(beanFactory);

// Last step: 事件通知关心容器加载的相关组件
finishRefresh();
}

// 部分代码省略
自此加载完毕核心容器,然后回到FramewordServlet的initWebApplicationContext函数,在调用createWebApplicationContext完成一系列上面的操作之后,需要mvc servlet组件,入口就是onRefresh(ApplocationContext context)方法。它会调用另一个方法initStrategies(ApplicationContext context)。该方法如下:

protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
// 获取所有的RequestMappings
initHandlerMappings(context);
// 不同handler的适配器
initHandlerAdapters(context);
// 异常解析器
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
这里我们重点讲解initHandlerMappings与initHandlerAdapters函数,因为这两个是处理servlet请求的入口。

在spring mvc中任何类都可以处理request请求,因为DispacherServlet也是实现了HttpServlet的接口,所以处理请求也是doService里。doService会将请求交给doDispatcher函数处理。然后doDispacher的源码:

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, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}

/* 因为handler可以是任何类,
但是我们的DispacherServlet需要一个统一的处理接口,这个接口就是HandlerAdapter,
不同的HandlerMapping可以获取到各种各样的Handler,
这个handler最后都必须得到HandlerAdapter的支持才能被DispacherServlet所调用。
扩充一个新的HandlerMapping只需要添加一个新的HandlerAdatper即可,有点抽象工厂模式的味道*/
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
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;
}
}

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
return;
}

applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, 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);

但是我们发现获取到的handler并不是Object而是一个HandlerExecutionChain,这个类可以进去查看发现是一堆拦截器和一个handler,主要就是给每一个请求一个前置处理的机会,这里值得一提的是一般来说拦截器和过滤器的区别就是拦截器可以终止后续执行流程,而过滤器一般不终止。过滤器一般是容器级别的,这个handler前置拦截器可以做到更细级别的控制,例如过滤器只定义了init和doFilter,但是这个handler拦截器定义了preHandle和postHandle还有afterCompletion函数,不难理解分别对应不同请求阶段的拦截粒度,更加灵活。

获取处理handler的getHandler代码:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 这里获取多个映射方式,一旦找到合适的处理请求的handler即返回
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
// HandlerExecutionChain包含了
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
HandlerAdapter处理后返回统一被封装成ModelAndView对象,这个就是包含试图和数据的对象,在经过适当的处理:

processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)
1
2
1
2
将页面与返回的数据返回给浏览器完成整个请求过程。以上就是springMVC大概的启动过程和请求的处理过程,之后本博客还会陆续分析核心的spring源码,博主一直认为精读著名框架源码是在短时间提升代码能力的捷径,因为这些轮子包含太多设计思想和细小的代码规范,这些读多了都会潜移默化的形成一种本能的东西。设计模式也不断贯穿其中。

springMVC容器加载源码分析的更多相关文章

  1. Java高并发程序设计学习笔记(五):JDK并发包(各种同步控制工具的使用、并发容器及典型源码分析(Hashmap等))

    转自:https://blog.csdn.net/dataiyangu/article/details/86491786#2__696 1. 各种同步控制工具的使用1.1. ReentrantLock ...

  2. springmvc工作原理以及源码分析(基于spring3.1.0)

    springmvc是一个基于spring的web框架.本篇文章对它的工作原理以及源码进行深入分析. 一.springmvc请求处理流程 二.springmvc的工作机制 三.springmvc核心源码 ...

  3. SpringMVC执行流程及源码分析

    SpringMVC流程及源码分析 前言 ​ 学了一遍SpringMVC以后,想着做一个总结,复习一下.复习写下面的总结的时候才发现,其实自己学的并不彻底.牢固.也没有学全,视频跟书本是要结合起来一起, ...

  4. 2.3 spring5源码系列---内置的后置处理器PostProcess加载源码

    本文涉及主题 1. BeanFactoryPostProcessor调用过程源码剖析 2. 配置类的解析过程源码 3. 配置类@Configuration加与不加的区别 4. 重复beanName的覆 ...

  5. springMvc的执行流程(源码分析)

    1.在springMvc中负责处理请求的类为DispatcherServlet,这个类与我们传统的Servlet是一样的.我们来看看它的继承图 2. 我们发现DispatcherServlet也继承了 ...

  6. composer 自动加载源码解析

    一直在用 composer,最近想看一下具体的原理是什么,就仔细阅读了一下源码,一下是个人理解.在看该文章前最好了解一下 PSR-4 自动加载规范 引入类自动加载文件 # 加载类自动加载文件 requ ...

  7. web应用启动的时候SpringMVC容器加载过程

    <!-- 配置DispatcherServlet --> <servlet> <servlet-name>springmvc</servlet-name> ...

  8. 【easyui】treegrid逐级加载源码

    当初看这源码的目的是: 1.treegrid是怎么实现逐级加载树结构的. 解: 见demo,主要就是点击节点的时候会请求后台. 2.treegrid加载后,第二次展开节点会不会再次请求后台. 解:第二 ...

  9. mybatis配置加载源码概述

    Mybatis框架里,有两种配置文件,一个是全局配置文件config.xml,另一个是对应每个表的mapper.xml配置文件.Mybatis框架启动时,先加载config.xml, 在加载每个map ...

随机推荐

  1. Windows Server 2008 R2 备份与恢复详细实例

    Windows Server 2008 R2中Windows Server Backup备份与恢复 本实验是在虚拟机操作,因公司的需求,将备份存储到另一台服务器,于是我在现有linux备份服务器搭建了 ...

  2. linux命令学习笔记(11):nl命令

    nl命令在linux系统中用来计算文件中行号.nl 可以将输出的文件内容自动的加上行号!其默认的结果与 等等的功能. .命令格式: nl [选项]... [文件]... .命令参数: -b :指定行号 ...

  3. eclipse导入java web项目,项目出现红叉而其他地方没有红叉的问题解决方法

    eclipse导入别人的Java web项目时会出现这种情况:仅项目名出现红叉而其他地方没有红叉的问题.这可能是以下几种情况导致的,其解决方法如下: 1.导入项目之前,请确认工作空间编码已设置为utf ...

  4. 【leetcode】Construct Binary Tree from Inorder and Postorder Traversal

    Given inorder and postorder traversal of a tree, construct the binary tree. Note:You may assume that ...

  5. Debian for ARM install python 3.5.x

    /********************************************************************************** * Debian for ARM ...

  6. 1143. Lowest Common Ancestor (30)

    The lowest common ancestor (LCA) of two nodes U and V in a tree is the deepest node that has both U ...

  7. Oracle 12c 新特性之 数据库内归档(In-Database Archiving)

    Oracle Database 12c中引入了 In-Database Archiving的新特性, 该特性允许用户通过对表上的数据行标记为inactive不活跃的,以归档数据. 这些inactive ...

  8. mysql基础之三:char与varchar区别,varchar(M)能存多少

    char与varchar区别 char (13)长度固定, 如'1234567890' 存储需要空间 10个字符; varchar(13) 可变长 如'1234567890' 需要存储空间 11字符; ...

  9. Python-Redis的Set操作

    集合为不重复的列表 无序集合 sadd(name,values):在name对应的集合中添加元素 smembers(name):获取name对应的集合的所有成员 127.0.0.1:6379> ...

  10. uva 512

    1. 问题 不知道怎么存储操作 看代码注释,else if等 2. 代码 #include <iostream> #include <stdio.h> #include < ...