SpringMVC加载流程

 这节介绍SpringMVC,SpringMVC是一种基于Java的实现MVC设计模式的请求驱动类型的轻量级Web框架。本章会介绍相关概念,流程,再从源码进行讲解。

1. MVC

 MVC(Model View Controller)是一种软件设计的框架模式,它采用模型(Model)-视图(View)-控制器(controller)的方法把业务逻辑、数据与界面显示分离。MVC框架模式是一种复合模式,MVC的三个核心部件分别是

  • Model(模型):所有的用户数据、状态以及程序逻辑,独立于视图和控制器
  • View(视图):呈现模型,类似于Web程序中的界面,视图会从模型中拿到需要展现的状态以及数据,对于相同的数据可以有多种不同的显示形式(视图)
  • Controller(控制器):负责获取用户的输入信息,进行解析并反馈给模型,通常情况下一个视图具有一个控制器

2. SpringMVC流程

 基本上大家都会在网上看到这张图:

file

这个图描述了SpringMVC处理一个Http请求的基本流程,对应的流程为:

  • 用户发送请求至前端控制器DispatcherServlet。
  • DispatcherServlet收到请求调用HandlerMapping处理器映射器。
  • 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  • DispatcherServlet调用HandlerAdapter处理器适配器。
  • HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
  • Controller执行完成返回ModelAndView。
  • HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
  • DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
  • ViewReslover解析后返回具体View.
  • DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
  • DispatcherServlet响应用户。

 还有大家都会接触到的demo:

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">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

在applicationContext.xml,指定包的扫描访问并添加标签

<context:component-scan base-package="xxx" />

<mvc:annotation-driven />

添加Controller

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String excute() {
        return "hello";
    }
}

上面表示的意思为:

  • 开启ContextLoaderListener加载Spring根Context,对应的配置文件为applicationContext.xml
  • 开启DispatcherServlet监听/*下的所有请求,加载WebContext,对应的配置文件为dispatcher-servlet.xml
  • 指定扫描包路径,并开启mvc注解支持
  • 添加对应的Controller,使用@RestController标记为Controller对象并使用@RequestMapping标记处理的请求路径

3. SpringMVC加载流程

 SpringMVC的加载是依赖Servlet切入的,主要依赖两个技术点:Listener和Servlet。

3.1. ContextLoaderListener的加载

 从web.xml中可以知道,ContextLoaderListener依赖于Servlet的Listener技术。Listener是在servlet2.3中加入的,主要用于对session、request、context等进行监控。使用Listener需要实现相应的接口。触发Listener事件的时候,tomcat会自动调用相应的Listener的方法。常用的监听接口包括:

  • HttpSessionListener:监听session的创建和销毁。
  • ServletRequestListener:监听request的创建和销毁
  • ServletContextListener:监听context的创建和销毁。

这里主要使用了ServletContextListener,用于在Servlet初始化前执行自定义动作。

 ContextLoaderListener的定义如下:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }
    
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

该类继承了ContextLoader,并通过contextInitialized方法执行了初始化(传入ServletContext),通过contextDestroyed方法进行资源销毁回收。重点看ContextLoader方法。

 ContextLoader在初始化时,会先执行内部的一个静态代码块:

private static final Properties defaultStrategies;

static {
    try {
        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());
    }
}

这一步会加载classpath下的配置文件ContextLoader.properties,该文件将作为默认配置用于初始化Properties对象defaultStrategies。

3.1.1. contextInitialized

 contextInitialized方法的主要内容如下:

file

过程为:

(1) 判断当前Context是否已经初始化过

 通过判断ServletContext中是否存在key为org.springframework.web.context.ROOT的值

  • 初始化WebApplicationContext:从ContextLoader.properties中查找WebApplicationContext的具体实现,如下:

    org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

即XmlWebApplicationContext,然后初始化该类

(2) 配置并刷新该XMLWebApplicationContext

 XMLWebApplicationContext继承简图如下:

file

层级比较明显,本身也是一个RefreshableConfigApplicationContext(具体内容可以看往期内容)。其父类保存了ServletContext和ServletConfig两个Web Context相关的对象,其本身也维持了一些默认属性,如

DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";

这个属性就是默认的Spring配置文件的路径。

 需要指出的是XMLWebApplicationContext重写了父类的loadBeanDefinitions方法

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // Create a new XmlBeanDefinitionReader for the given BeanFactory.
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

    // Configure the bean definition reader with this context's
    // resource loading environment.
    beanDefinitionReader.setEnvironment(getEnvironment());
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

    // Allow a subclass to provide custom initialization of the reader,
    // then proceed with actually loading the bean definitions.
    initBeanDefinitionReader(beanDefinitionReader);
    loadBeanDefinitions(beanDefinitionReader);
}

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            reader.loadBeanDefinitions(configLocation);
        }
    }
}
    
@Override
protected String[] getDefaultConfigLocations() {//Tip:返回配置文件路径
    if (getNamespace() != null) {
        return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
    }
    else {
        return new String[] {DEFAULT_CONFIG_LOCATION};
    }
}

这里用了XmlBeanDefinitionReader来解析Bean定义,且指定了配置文件的加载逻辑,getConfigLocations方法:如果父类的configLocations不为空,则返回该值,否则返回getDefaultConfigLocations的值。而getDefaultConfigLocations方法逻辑为:如果存在命名空间,则返回/WEB_INF/namespace.xml作为配置文件,否则返回/WEB-INF/applicationContext.xml。对应上面的demo,将返回配置中的文件(同默认值相同)。

 XMLWebApplicationContext的初始化步骤为:

  • 读取contextId配置,进行设置
  • 读取contextConfigLocation配置,使用指定的配置文件,若没有则使用上面提到的默认配置文件DEFAULT_CONFIG_LOCATION
  • 加载contextInitializerClasses指定的class,用于在context刷新前执行自定义处理
  • 调用XMLWebApplicationContext的refresh方法

(3)标记已经初始化

 通过将该根Context存在ServletContext中,并设置值为org.springframework.web.context.ROOT,用于第(1)步的判断

3.1.2. contextDestroyed

 销毁过程比较简单,首先调用WebApplicationContext的close方法销毁该Context,然后移除ServletContex中的org.springframework.web.context.ROOT属性值,最后清除ServletContext中所有org.springframework.开头的属性值。

3.2. DispatcherServlet的加载

 同ContextLoaderListener类似,DispatcherServlet依赖于Servlet进行扩展。DispatcherServlet的结构如下:

file

如上,DispatcherServlet继承自HttpServlet,并重写了doService方法,用于处理http请求,其中:

3.2.1. HttpServletBean

 在HttpServlet的继承上增加了ConfigurableEnvironment属性,用于存放Servlet的配置项。通过重写init方法,在初始化时将servlet配置项添加到上下文环境变量中,并在该方法中开放了initBeanWrapper和initServletBean方法给子类。

3.2.2. FrameworkServlet

 基于Servlet实现的Web框架,每个Servlet内部都对应一个XmlWebApplicationContext对象,且namespace格式为ServletName-servlet。上面说了在没显示设定配置文件路径的情况下,且存在namespace时,会使用/WEB-INF/namespace.xml作为Spring配置文件,对应到demo即为/WEB-INF/dispatcher-servlet.xml。FrameworkServlet重写了父类的intServletBean方法,对XmlWebApplicationContext的初始化工作。Servlet在初始化XmlWebApplicationContext时,会尝试从ServletContext中获取根Context(上面提到的,会将根Ccontext放到ServletContext中以标记已经初始化过)并设置为当前Context的父Context,然后再按照雷士根Contextde 的初始化过程对其进行初始化。不同的是,会在refresh前开放口子进行扩展,包括:

  • 对内通过重写子类的postProcessWebApplicationContext方法
  • 对外通过加载并执行globalInitializerClasses中配置的ApplicationContextInitializer类

FrameworkServlet还重写了父类的各doXXX方法,都交给processService方法,以处理Http请求。processService最终委托给了doService方法。

3.2.3. DispatchdrServlet

 是SpringMVC处理Http请求的主要实现,主要完成了两件事:

3.1. 重写了onRefresh方法

 初始化时设置了众多默认处理策略,包括:文件处理策略、HandlerMapping处理策略、HandlerAdapter处理策略、HandlerException处理策略、View解析策略等。SpringMVC在处理Http的每个步骤上,都提供了类似filter的机制,每个步骤都能够注册多个策略处理器,按照顺序选择出能够处理当前请求的策略并交给其处理。而大部分的默认策略来至于spring-mvc模块下的org/springframework/web/servlet/DispatcherServlet.properties文件,如下:

file

下面为本人demo(SpringBoot)运行时DispatcherServlet各属性以及注册的各策略的情况

file

主要关注handlerMappings中的RequestMappingHandlerMapping和handlerAdapters中的RequestMappingHandlerAdapter。这两个都不是在DispatcherServlet.properties文件中指定的,而是在开启<mvc:annotation-driven />后自动注册的,这个后面会介绍。

3.1.1 RequestMappingHandlerMapping初始化

 RequestMappingHandlerMapping主要用于查找@RequestMapping注解的handler,其继承关系如下:

file

  • AbstractHandlerMapping:实现了HandlerMapping接口,提供了获取handler的主要实现。getHandler方法的实现为,将具体handler的查找委托给了子类的getHandlerInternal方法,然后跟当前请求路径相关的interceptor一起包装为一个HandlerExecutionChain返回。interceptor为所有实现了MappedInterceptor接口的bean,会在AbstractHandlerMapping初始化的时候遍历上下文进行查找。
  • AbstractHandlerMethodMapping:在AbstractHandlerMapping的基础上,主要提供了根据请求查找对应handler method的实现,即getHandlerInternal方法。该类会在初始化时遍历上下文中所有的Bean,然后符合条件的Bean(通过isHandler方法),遍历当前Bean符合条件的方法(通过getMappingForMethod方法),每个方法都有一个对应的path,称为lookUpPath。getHandlerInternal实现上也是通过请求的HttpServletRequest得到对应的lookUpPath,然后从内存缓存中获取对应的handler。
  • RequestMappingHandlerMapping:@RequestMapping的实现,主要实现了 isHandler和getMappingForMethod。

    • isHandler:判断是否出现@Controller注解或者@RequestMapping注解
    • getMappingForMethod:根据@RequestMapping注解返回RequestMappingInfo实例。

3.1.2 RequestMappingHandlerAdapter初始化

  RequestMappingHandlerAdapter主要完成HandlerMethod的执行,,其继承关系如下:

file

  • AbstractHandlerMethodAdapter:用于判断是否支持Handler的执行,需要传入的handler是否为HandlerMethod实例,同时将handler的执行委托给子类的handleInternal方法。
  • RequestMappingHandlerAdapter:真正执行handler对应的Method对象,会调用各种resolvers解析参数,用于在反射时作为入参传入;调用各种converter用于对结果进行加工等操作。

3.2. 重写doService方法

 实现了Http请求的处理过程,具体流程如下图,即开头提及的SpringMVC处理Http请求的过程,前面已经介绍过流程,这里不再赘述。

file

3.3. mvc:annotation-driven

 按照之前说的,先看resource/META-INF/spring.handlers文件,这个配置在spring-webmvc模块下,内容为:

http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler

支持的标签如下:

file

annotation-driven的解析类为:AnnotationDrivenBeanDefinitionParser,该类主要自动做了如下动作:

  • 注入了RequestMappingHandlerMapping和BeanNameUrlHandlerMapping两个HandlerMapping实现
  • 注入了RequestMappingHandlerAdapter、HttpRequestHandlerAdapter和SimpleControllerHandlerAdapter三个HandlerAdapter实现。需要指出的是对于RequestMappingHandlerAdapter,如果没有配置message-converters标签指定消息处理器的话,会根据classpath中存在的包自动注入处理器,包括:

    • ByteArrayHttpMessageConverter
    • StringHttpMessageConverter
    • ResourceHttpMessageConverter
    • SourceHttpMessageConverter
    • AllEncompassingFormHttpMessageConverter
    • 如果存在com.rometools.rome.feed.WireFeed类,则增加AtomFeedHttpMessageConverter、RssChannelHttpMessageConverter
    • 如果存在com.fasterxml.jackson.dataformat.xml.XmlMapper类,则增加MappingJackson2XmlHttpMessageConverter
    • 如果存在javax.xml.bind.Binder类,则增加Jaxb2RootElementHttpMessageConverter
    • 如果存在com.fasterxml.jackson.databind.ObjectMapper和com.fasterxml.jackson.core.JsonGenerator,则增加MappingJackson2HttpMessageConverter
    • 如果存在com.google.gson.Gson,则增加GsonHttpMessageConverter
  • 注入了ExceptionHandlerExceptionResolver用于实现@ExceptionHandler注解、注入了ResponseStatusExceptionResolver用于实现@ResponseStatus和DefaultHandlerExceptionResolver
  • 注入了AntPathMatcher和UrlPathHelper用于路径解析

 上面介绍了SpringMVC大体流程的实现,当然还有很多细节没有进行说明,如@Param,HttpServletRequest等各种参数的解析和注入,响应结果转为json等各种结果的加工,详细内容可以根据上面介绍再进行深入。

4. WebApplicationInitializer

 Servlet3.0+提供了ServletContainerInitializer接口,用于在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等。

 每个框架要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类。spring-web模块下便存在该配置:

file

内容为:

org.springframework.web.SpringServletContainerInitializer

SpringServletContainerInitializer的主要功能是加载classpath下的所有WebApplicationInitializer实现类(非接口、非抽象类),按照@Order进行排序后依次执行WebApplicationInitializer的onStartup方法。

 spring-web模块提供的抽象类实现AbstractContextLoaderInitializer能够不用web.xml配置增加RootContext;提供的抽象类实现AbstractDispatcherServletInitializer能够不用web.xml配置增加DispatcherServlet。当然更重要的实现是SpringBoot中的实现,这个后续介绍SpringBoot时再提。

更多原创内容请搜索微信公众号:啊驼(doubaotaizi)
Image placeholder
Leonado
未设置
  74人点赞

没有讨论,发表一下自己的看法吧

推荐文章
搭建 Spring+SpringMVC+MyBatis 框架

SSM框架整合 pom中添加依赖 添加编辑Spring配置文件 添加编辑SpringMVC配置文件 添加编辑Mybatis配置文件 配置web.xml 1、pom中添加依赖 junit ju

面试问烂的 Spring AOP 原理、SpringMVC 过程

  正文  SpringAOP,SpringMVC,这两个应该是国内面试必问题,网上有很多答案,其实背背就可以。但今天笔者带大家一起深入浅出源码,看看他的原理。以期让印象更加深刻,面试的时候游刃有余。

0103-springmvc的基本流程

背景现在的it研发,已经从管理系统时代迈入了互联网系统时代。页面开发已经从基于JSP+struts转变为为前后端分离的方式(springMVC+JS);思想MVCmvc框架不仅适用于java的开发,也

Spring-SpringAOP原理,手写Spring事务框架

一、Spring核心知识Spring是一个开源框架,Spring是于2003年兴起的一个轻量级的Java开发框架,由RodJohnson在其著作ExpertOne-On-OneJ2EEDevelopm

阿里提出针对多目标优化的全新算法框架,同时提升电商推荐场景 GMV 和 CTR

在推荐系统中,多目标优化一直是热门话题,阿里巴巴的XiaoLin、HongjieChen等人针对推荐中的多目标优化问题提出了一种基于帕累托效率的优化算法框架,并应用在电商推荐场景中,对GMV和CTR

使用 Vue.js 和 Iris 共建一个简单的 Todo MVC 应用

本文用Golang的Iris框架作为后端服务,vuejs渲染前端UI,用websocket通信。基于监听hash变化director.js库实现简单路由,axios库与后方沟通,netoffos.j

使用 Vue.js 和 Iris 共建一个简单的 Todo MVC 应用

数据服务 packagetodo import"sync" //Item条目数据 typeItemstruct{ SessionIDstring`json:"-"` IDint64`json:"i

SpringBoot个人应用开发框架(SpringBoot版本2.1)+IDEA

前言: 此笔记为本人首个SpringBoot项目框架学习实践记录,期间参考了许多大神的笔记和心得。 参考文档如下: 项目git地址: 一、创建SpringBoot工程 1.1创建父POM工程结

再见 Spring Boot 1.X ,Spring Boot 2.X 走向舞台中心

2019年8月6日,Spring官方在其博客宣布,SpringBoot1.x停止维护,SpringBoot1.x生命周期正式结束。其实早在2018年7月30号,Spring官方就已经在博客进行过预告,

BAT大牛推荐开发人员必备Spring源码剖析文档,深度剖析Spring

为什么学习读源码我们每天都和代码打交道。经过数年的基础教育和职业培训,大部分程序员都会「写」代码,或者至少会抄代码和改代码。但是,会读代码的并不在多数,会读代码又真正读懂一些大项目的源码的,少之又少。

gMIS吉密斯十年执念:Lower Costs较低成本Better Productivity较高效率

Hello2020!元旦快乐!今起揭开21世纪20年代的篇章.1.gMIS吉密斯十周年2010-2020,十年转眼已成历史,gMIS吉密斯——通用管理信息系统(generalManagementInf

MVVM原理(Object.defineProperty和订阅者模式)

想着去了解vue的mvvm数据驱动是怎么实现的,百度中看了这篇文章,demo很好。其他文章只是讲到defineProperty的set,get。彻底理解Vue中的Watcher、Observer、De

gMIS吉密斯i18n多语言国际化更新

gMIS吉密斯部署和使用范围日益扩大,跨国多语言版本成为迫切需要的功能,早在2018年年中就考虑要增加这一功能——在gMIS吉密斯中实现多语言版本的支持。以期实现gMIS吉密斯的跨行业、跨地区和国际化

笨办法学 Linux 学习处理文件,`pwd`,`ls`,`cp`,`mv`,`rm`,`touch`

Bash:处理文件,pwd,ls,cp,mv,rm,touch 在Linux中,一切都是文件。但是什么是文件?现在完全可以说,它是一个包含一些信息的对象。它通常定义如下: 计算机文件是用于存储信息的

react是mvvm框架吗?

react是mvvm框架吗?不是。React可以作为MVVM中第二个V,也就是View,但是并不是MVVM框架。MVVM一个最显著的特征:双向绑定。React没有这个,它是单向数据绑定的。React是

SpringBoot 集成 JWT 实现 token 验证,token 注销

什么是JWT Jsonwebtoken(JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC7519).定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形

springboot 多数据源,最简单的整合方式

简介 相信大家有配置过多数据源,或者即将配置多数据的朋友们,会发现网上大概有以下几种方案: 1.使用AOP切片进行动态数据源切换 2.使用MapperScan的basePackages配置不同的map

springDataJpa 最佳实践

springDataJpa最佳实践 前言 SpringDataJpa框架的目标是显著减少实现各种持久性存储的数据访问层所需的样板代码量。SpringDataJpa存储库抽象中的中央接口是Reposit

你知道如何自动保存 Spring Boot 应用进程号吗

1.前言 欢迎阅读SpringBoot2实战系列文章PID对于系统运维来说并不陌生,但是对于一些开发者特别是新手还是要简单介绍一下的。它是ProcessID的简称,是系统分配给一个进程的唯一标识符,

你了解Spring Data JPA的批量插入吗?

实现场景 有关拼团旅游的业务,里面有这样一个逻辑实现:一个拼团活动表Activity对应多个Travel旅游表,travel旅游表要分段存储前端传来的List,Travel表需将每一条数据保存入库,

云端的生存之道,第 1 单元:将 Spring Boot 部署到 Kubernetes

初始化Kubernetes集群 第一步是初始化IBMCloud上的Kubernetes集群。IBMCloud可能需要几分钟时间来启动新的Kubernetes集群;因此,通过先执行初始化操作,可以在后台

云端的生存之道,第 2 单元:将 Spring Boot 应用程序连接到云托管的数据库

前提条件 本系列教程的第1部分,因为本教程直接以第1部分中的课程内容和完成的操作为基础。 一个IBMCloud帐户 云原生数据持久性 IBMCloud提供了许多可持久存储数据的选项。在本教程中,我

Spring Boot自动装配整理

首先写一个我们自己的HelloWorld配置类 1、基于"注解驱动"实现@Enable模块 @ConfigurationpublicclassHelloWorldConfiguration{@Bean

Spring Cloud Stream整合Kafka

引入依赖 org.springframework.cloud spring-cloud-stream-binder-kafka 或 org.springframework.cloud spr

结合Spring Security进行web应用会话安全管理

在本文中,将为大家说明如何结合SpringSecurity和SpringSession管理web应用的会话。 一、SpringSecurity创建使用session的方法 SpringSecurit