DispatcherServlet初始化过程源码分析 | SpringMVC源码分析

news2024/11/24 4:25:12

一、继承或实现关系

public class DispatcherServlet extends FrameworkServlet

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware

public abstract class HttpServlet extends GenericServlet

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable

public interface Servlet

图解

image-20221225193052646

二、SpringMVC的初始化过程源码分析

调用Servlet接口提供的init()方法进行初始化。由于Servlet接口中的方法都是抽象方法,真正调用的是它的实现类中的实现了init()的方法,从上图中关系得知,GenericServlet类实现了Servlet接口,并且实现了Servlet接口提供的init()方法。

public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
}

在实现了Servlet接口中的init()方法后,该方法还有它的重载方法,并且调用了它的重载方法。

重载的init()方法如下:

public void init() throws ServletException {
}

可以看到init()方法中没有具体的方法体,所以需要关注它的子类。然后它的直接子类HttpServlet并没有重写init()方法,所以我们需要关注HttpServlet的子类,看看它的子类有没有重写init()方法。

image-20221225194253608

从上图中可以看出,HttpServlet的子类HttpServletBean实现了init()方法。

HttpServletBean中init()的源码如下:

@Override
public final void init() throws ServletException {

    // 从web.xml中获取servlet的初始化参数,把它添加到List<PropertyValue>
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        try {
            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();
}

我们在web.xml中配置的init-param就是在这个阶段读取到内存中。

image-20221225200619762

image-20221225200340800

1、ProtertyValues

PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);的研究。

ServletConfigPropertyValues源码

public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
    throws ServletException {

    Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
                                new HashSet<>(requiredProperties) : null);
	// 从servletConfig对象中获取web.xml中配置的Servlet的初始化参数的名字,即param-name标签里的内容
    // 这里 paramNames = contextConfigLocation
    Enumeration<String> paramNames = config.getInitParameterNames();
    while (paramNames.hasMoreElements()) {
        String property = paramNames.nextElement();
        Object value = config.getInitParameter(property);
        // 添加到 list 集合中
        addPropertyValue(new PropertyValue(property, value));
        if (missingProps != null) {
            missingProps.remove(property);
        }
    }

    // Fail if we are still missing properties.
    if (!CollectionUtils.isEmpty(missingProps)) {
        throw new ServletException(
            "Initialization from ServletConfig for servlet '" + config.getServletName() +
            "' failed; the following required properties were missing: " +
            StringUtils.collectionToDelimitedString(missingProps, ", "));
    }
}

addPropertyValue(new PropertyValue(property, value));的源码分析。

private final List<PropertyValue> propertyValueList;
public MutablePropertyValues addPropertyValue(PropertyValue pv) {
    for(int i = 0; i < this.propertyValueList.size(); ++i) {
        PropertyValue currentPv = (PropertyValue)this.propertyValueList.get(i);
        if (currentPv.getName().equals(pv.getName())) {
            pv = this.mergeIfRequired(pv, currentPv);
            this.setPropertyValueAt(pv, i);
            return this;
        }
    }

    this.propertyValueList.add(pv);
    return this;
}

由于刚开始propertyValueList为空,所以不会进入for循环。直接执行this.propertyValueList.add(pv);把形参中的protertyValue对象放到propertyValueList集合中。

实际上是把servlet初始化中的参数封装到list集合中了。

2、BeanWrapper

BeanWrapper是接口,实际上创建的是它的实现类BeanWrapperImpl

作用:对DispatcherServlet进行包装。

3、ResourceLoader

资源加载器

作用:加载各种资源。

private final ServletContext servletContext;
public ServletContextResourceLoader(ServletContext servletContext) {
    this.servletContext = servletContext;
}

4、注册到自定义编辑器中

bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));

将ResourceLoader和Environment封装进ResourceEditor。

再将ResourceEditor封装进BeanWrapper。

ResourceEditor属性编辑器接口,它的作用更像一个转换器,将字符串转换为类对象的属性。

5、initBeanWrapper(bw)

它是一个没有具体方法体的方法。

作用:如果我们想要自定义包装类,我们就可以重写该方法。

protected void initBeanWrapper(BeanWrapper bw) throws BeansException {}

6、bw.setPropertyValues(pvs, true)

ProtertyValues封装到BeanWrapper中。

通过BeanWrapper可以访问到Servlet的所有参数、资源加载器加载的资源以及DispatcherServlet中的所有属性,并且可以像访问Javabean一样简单。

7、initServletBean()

最后还调用initServletBean();方法。

protected void initServletBean() throws ServletException {}

由于该方法没有方法体,所以需要看该类的子类是否重写了该方法。

HttpServletBean的子类FrameworkServlet

8、分析FrameworkServlet

initServletBean()源码

@Override
protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
    if (logger.isInfoEnabled()) {
        logger.info("Initializing Servlet '" + getServletName() + "'");
    }
    long startTime = System.currentTimeMillis();

    try {
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        throw ex;
    }

    if (logger.isDebugEnabled()) {
        String value = this.enableLoggingRequestDetails ?
            "shown which may lead to unsafe logging of potentially sensitive data" :
        "masked to prevent unsafe logging of potentially sensitive data";
        logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
                     "': request parameters and headers will be " + value);
    }

    if (logger.isInfoEnabled()) {
        logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
    }
}

这里调用了initWebApplicationContext()方法。

initWebApplicationContext()源码

protected WebApplicationContext initWebApplicationContext() {
    // 内部判断是否有异常,如果没有,则返回 WebApplicationContext
    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) {
                    // 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) {
        // No context instance is defined for this servlet -> create a local one
        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.
        synchronized (this.onRefreshMonitor) {
            onRefresh(wac);
        }
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

wac刚开始为null,调用createWebApplicationContext(rootContext)方法来创建SpringMVC容器。

createWebApplicationContext(rootContext)源码

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    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); // 设置 Application 为父容器
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        wac.setConfigLocation(configLocation);
    }
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

configureAndRefreshWebApplicationContext(wac)源码

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) {
            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);
    wac.refresh();
}
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                      ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());

设置容器的id

image-20221226001600002

image-20221226002004316

返回到initWebApplicationContext()方法。

最后得到了wac对象。

image-20221226002223359

最后返回到调用处initServletBean()

9、刷新容器

initWebApplicationContext()中会调用onRefresh(wac)刷新容器。

由于onRefresh()方法的方法体为空,所以需要看它的子类重写之后的方法。

image-20221226004422574

FrameworkServlet的子类DispatcherServlet中的重写了onRefresh()方法。

protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

DispatcherServlet中的onRefresh()方法调用了本类中的initStrategies()方法。

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

到此,DispatcherServlet就初始化完成了。

【SpringMVC源码分析】DispatcherServlet初始化源码分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/114956.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

聊聊Go语言并发之道

写在前面 2007年&#xff0c;Go语言诞生于Google公司&#xff0c;2009年开源&#xff0c;2012年推出1.0版本&#xff0c;曾两次获得TIOBE年度语言。2012年起&#xff0c;全球大量的开源项目开始使用Go语言开发&#xff0c;目前Go语言已成为云计算领域事实上的标准语言&#xff…

深度学习常见概念字典(感知机、全连接层、激活函数、损失函数、反向传播、过拟合等)

这一章的所有内容均是为了进入深度学习具体的某某网络而准备的&#xff0c;简单但是非常有必要。 1. 神经网络&#xff08;neural networks&#xff09;的基本组成 1.1 神经元&#xff08;neuron&#xff09; 神经元&#xff08;neuron&#xff09; 是神经网络&#xff08;n…

slf4j常用配置文件读取

slf4j常用配置文件读取 log4j2读取配置文件 日志现在一般都是使用slf4j作为接口、底层实现一般是用log4j2或者logback。 我们先看下log4j2是如何读取配置文件的。 implementation org.apache.logging.log4j:log4j-slf4j-impl:2.19.0如果使用gradle的话。上面的代码就会导入sl…

VS coda C++、python运行与Dbug配置

首先新建终端 一次性使用C方法 检查C编译器是否存在 which g可见位置存在于&#xff1a;/usr/bin/g 一次性命令格式&#xff1a; 使用json配置文件 运行C方法&#xff08;推荐&#xff09;&#xff1a; 根据你查找的g的位置来决定 使用配置好的tasks.json&#xff08;C的…

QT入门-UI-信号槽

目录 一、QWidget类&#xff08;重点&#xff09; 二、子组件&#xff08;掌握&#xff09; 三、样式表&#xff08;熟悉&#xff09; 一、什么是信号槽&#xff1f; 二、信号槽的连接方式 2.1 自带信号→自带槽 2.2 自带信号→自定义槽 2.3 自定义信号 三、传参方式 3.1 成员变…

C#语言实例源码系列-伪装文件

专栏分享点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断反思前进的过程。在这个过程中…

Redis分布式锁存在的问题

假设有这样一个场景&#xff0c;在一个购票软件上买一张票&#xff0c;但是此时剩余票数只有一张或几张&#xff0c;这个时候有几十个人都在同时使用这个软件购票。在不考虑任何影响下&#xff0c;正常的逻辑是首先判断当前是否还有剩余的票&#xff0c;如果有&#xff0c;那么…

Spring5.3.0源码下载

目录源码下载环境配置import into idea修改配置gradle-wapper.propertiesbuild.gradleSetting Gradlerefresh Gradle写一个小dome源码研究心得源码下载 Spring5.3.0 Download Address 我们只需要下载zip就行了&#xff0c; 如果忘记了这个地址&#xff0c;可以在Spring Offici…

网络技术——网络运维工程师必会的网络知识(3)(详细讲解)

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.网络层协议与应用 1.网络层的功能 2.IP数据包格式 3.广播与…

100天精通Python(数据分析篇)——第70天:Pandas常用排序、排名方法(sort_index、sort_values、rank)

文章目录每篇前言一、按索引排序&#xff1a;sort_index()1. Series类型排序1&#xff09;升序2&#xff09;降序2. DataFrame类型排序1&#xff09;按行索引排序2&#xff09;按列索引排序二、按值排序&#xff1a;sort_values()1. Series类型排序1&#xff09;升序2&#xff…

冻结集合:不可能增删frozenset()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 冻结集合&#xff1a;不可能增删 frozenset() 选择题 对于以下python代码表述错误的一项是? a{1,2} print("【显示】a",a) a.add(3) print("【执行】a.add(3)【显示】a"…

【现代机器人学】学习笔记七:开链动力学(前向动力学Forward dynamics 与逆动力学Inverse dynamics)

这节的内容主要讲述机器人动力学的内容。相对于本书其他部分运动学内容相比&#xff0c;把动力学一下子合成了一章。看完以后有三个感受&#xff1a; 1.本章难度相对其他章节较大&#xff0c;因此需要反复去看&#xff0c;以求对重要内容的眼熟&#xff0c;不求全部记住&#…

Java window多环境配置

目录JDK版本下载配置内容描述创建JAVA_HOME在Path配置版本切换效果JDK版本下载 Java8 Download address 这个是Java8 的下载地址&#xff0c;下载是要登录的&#xff0c;自己花费一点时间去注册。如果想要下载其它版本的JDK&#xff0c;请看下面的图&#xff0c;然后你就可以看…

QT数据库-网络编程-打包

目录 一、讲解之前 二、数据库基本操作 三、模糊查询 二、编程之前 三、通信结构 一、设置应用图标&#xff08;熟悉&#xff09; 二、Debug和Release模式&#xff08;掌握&#xff09; 三、动态链接库&#xff08;掌握&#xff09; 四、打包&#xff08;熟悉&#xff09; 一、…

FastDDS(10)Transport Layer传输层

传输层在DDS实体之间提供通信服务,负责通过物理传输实际发送和接收消息。DDS层将此服务用于用户数据和发现流量通信。然而,DDS层本身是独立于传输的,它定义了一个传输API,可以运行在实现该API的任何传输插件上。这样,它就不局限于特定的传输,应用程序可以选择最适合其需求…

公众号开发(4) —— 使用Senparc.Weixin SDK进行模板消息推送

微信公众号支持推送模板消息给特定用户&#xff0c;只要获取到公众号用户的openid向微信提供的接口发送post请求即可向特定用户推送模板消息&#xff0c;以下记录简单记录使用Senparc.Weixin SDK进行模板消息推送的过程。 1 模板消息建立 在微信公众号测试账号中添加如下消息…

数据结构之排序【冒泡排序和快速排序之一的实现及分析】内含动态演示图

引言&#xff1a; 今天分享一下一点小事迹&#xff0c;自从从学校回到家里&#xff0c;我已经好久没睡上一个好觉了&#xff0c;因为真的冷&#xff0c;莫名被窝总是感觉很冷&#xff0c;然后穿着袜袜的脚也是冰凉&#xff0c;所以每次早晨要起床的时候总是感觉非常的冷&#…

shell基础使用

一、hello world 首先建立一个tmux vim test.sh //再创建一个test.sh文件 进入文件后&#xff0c;创建一个如下命令&#xff0c;指明bash为脚本解释器 #! /bin/bash //相当与c头文件echo "hello world"运行方式 1.作为可执行文件 acs9e0ebfcd82d7:~$ chmod x test.s…

我是如何将2千万StackOverflow问答翻译成中文的?

大家好&#xff01;大家觉得如果需要翻译SO上全部问答可以怎么做呢&#xff1f;我讲讲我是怎么的&#xff0c;大家看看做得怎么样。 自我介绍 我是一名有10年开发经验的老程序员。做过大数据&#xff0c;做过Java程序员&#xff0c;也做过算法工程师&#xff0c;目前是一名大厂…

Spring Security的项目中集成JWT Token令牌安全访问后台API

Spring Security的项目中集成JWT Token令牌安全访问后台API引言JWT 简介jwt token 的适用场景jwt 的结构完整jwtjwt 的使用方式客户端获取jwt令牌访问受保护资源的具体流程Spring Security 安全框架下使用jwt token新建一个spring boot项目加入spring security 和 jwt 相关依赖…