一、继承或实现关系

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就初始化完成了。

Q.E.D.


热爱生活,热爱程序