Spring MVC启动原理详解(下)

导读:本篇文章讲解 Spring MVC启动原理详解(下),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一、父容器启动

《SpringMVC启动原理解析(上)》中,父子容器已经创建了,但是都还没有启动,那容器又是在哪里启动,我们先讲一下父容器是什么时候启动的,在上面创建父容器的时候,添加了一个ContextLoaderListener监听器,这个监听器实现了ServletContextListener接口,可以监听Web容器(Tomcat)的状态变化,属性变化等情况,在ContextLoaderListener中有一个contextInitialized()方法,这个方法表示Web容器初始化完成,也就是在Web容器初始化完成之后,就回去调用这个方法,而父容器的启动也就是在这个方法中完成的

在Web容器初始化完成的方法中,会去调用initWebApplicationContext来初始化父容器

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    // Initialize the root web application context.
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }

}

在父容器创建过程中创建ContextLoaderListener时,把父容器作为ContextLoaderListener构造参数的入参传给了这个监听器

所以父容器初始化的时候,context就是父容器,在初始胡之前,首先判断父容器是否为空,因为只有零配置SpringMVC实现时,才会在初始化容器之前去创建父子容器,使用xml方式时,这个时候是还没有父容器的,所以,这里需要判断,如果没有父容器,则通过createWebApplicationContext()方法来创建一个父容器

这个时候父容器还没有启动,只是创建完成了而已,所以容器状态isActive肯定为false,这个时候根容器是没有父容器的,但是loadParentContext()提供了扩展,可以为根容器再添加容器,默认返回是null

接下来最重要的就是配置启动容器,然后进行解析,容器启动完成之后,把根容器添加到Servlet上下文中

一个Web容器可以同时运行多个SpringMVC应用,所以,会按照webapp的加载器类区分各个应用的父容器

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {

    long startTime = System.currentTimeMillis();

    // xml会在这里创建
    if (this.context == null) {
        this.context = createWebApplicationContext(servletContext);
    }
    if (this.context instanceof ConfigurableWebApplicationContext) {
        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
        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 ->
                // determine parent for root web application context, if any.
                ApplicationContext parent = loadParentContext(servletContext);
                cwac.setParent(parent);
            }
            configureAndRefreshWebApplicationContext(cwac, servletContext);
        }
    }
    // 在servlet域中设置根容器(在子容器就可以直接拿到了)
    servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

    // 获取线程上下文类加载器,默认为WebAppClassLoader
    ClassLoader ccl = Thread.currentThread().getContextClassLoader();
    // 如果spring的jar包放在每个webapp自己的目录中
    // 此时线程上下文类加载器会与本类的类加载器(加载spring的)相同,都是
    if (ccl == ContextLoader.class.getClassLoader()) {
        currentContext = this.context;
    }
    // 如果不同,也就是上面说的那个问题的情况,那么用一个map把刚才创建的
    else if (ccl != null) {
        // 一个webapp对应一个记录,后续调用时直接根据WebAppClassLoader来取出
        currentContextPerThread.put(ccl, this.context);
    }

    return this.context;
}

接下来重点看下configureAndRefreshWebApplicationContext()方法中,是如何启动父容器的

首先会把Servlet上下文中的全局参数设置到父容器中,如果父容器中有与Servlet相关的环境变量,会对这些变量进行初始化,最后调用容器的refresh()方法来启动容器,加载配置,解析Bean,容器启动过程的源码可以参考《Spring容器启动(上)》《Spring容器启动(中)》、《Spring容器启动(下)》三篇文章

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    // 设置ServletContext到spring上下文
    wac.setServletContext(sc);
    // 获得servlet容器中的全局参数contextConfigLocation  (xml)
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }

    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }
    // 刷新容器
    wac.refresh();
}

二、子容器启动

在子容器创建的时候,创建了DispatcherServlet实例,并把子容器添加到了Servlet实例中,而子容器的启动就是在DispatcherServlet初始化的时候才启动的

DispatcherServlet继承自FrameworkServlet,而FrameworkServlet又继承自HttpServletBean类,所以在DispatcherServlet和FrameworkServlet都没有重写init()方法,所以,在DispatcherServlet初始化的时候,会调用HttpServletBean的init()方法

在init()方法中,首先会把DispatcherServlet的初始化参数解析封装成PropertyValues对象,然后把DispatcherServlet封装成为一个BeanWrapper,然后通过属性注入的方式来为DispatcherServlet的参数赋值

最后会调用initServletBean()方法来初始化子容器

@Override
public final void init() throws ServletException {

    // 解析 init-param 并封装只 pvs 中(xml)
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    if (!pvs.isEmpty()) {
        // 将当前的这个 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);
    }

    // Let subclasses do whatever initialization they like.
    initServletBean();
}

2.1 子容器初始化

initServletBean()方法的实现是在FrameworkServlet类中,调用initWebApplicationContext()来初始化子容器

protected final void initServletBean() throws ServletException {

    this.webApplicationContext = initWebApplicationContext();
}

我们前面一直说SpringMVC的父子容器,但是在这一步之前,这两个容器是完全独立的,没有任何关系,而在初始化子容器的时候,才会确定它们的父子关系,然后调用configureAndRefreshWebApplicationContext()方法来初始化子容器

对于使用xml的方式来说,这个时候还没有创建子容器,所以同样先去Servlet上下文取,没有再调用createWebApplicationContext()方法创建子容器,与父容器的创建是一样的

protected WebApplicationContext initWebApplicationContext() {
    // 获得ContextLoaderListener存的父容器
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        // 获得子容器
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // 如果没有设置父容器   spring  doGetBean
                if (cwac.getParent() == null) {
                    cwac.setParent(rootContext);
                }
                // 配置并且加载子容器
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // 从servlet上下文根据<contextAttribute>名字从域里面获取
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // xml会在这里创建
        wac = createWebApplicationContext(rootContext);
    }

    //refreshEventReceived 它会在容器加载完设置为true (通过事件onApplicationEvent)
    // springboot在这初始化组件
    if (!this.refreshEventReceived) {
        synchronized (this.onRefreshMonitor) {
            onRefresh(wac);
        }
    }

    if (this.publishContext) {
        // 将当前容器放到servlet域中, 可以再创建子容器
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

下面看下子容器初始化过程中都干了什么

也是设置一些Servlet信息和环境变量的参数,但不同的时,子容器添加了一个ContextRefreshListener的监听器,然后调用子容器的refresh()方法加载解析配置,生成Bean实例

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
  
   // 设置servlet上下文
   wac.setServletContext(getServletContext());
   wac.setServletConfig(getServletConfig());
   wac.setNamespace(getNamespace());
   // 监听器  委托设计模式
   wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

   // 将init-param设置到Environment中
   ConfigurableEnvironment env = wac.getEnvironment();
   if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
   }
  
   // 容器启动前初始化
   applyInitializers(wac);
   wac.refresh();
}

2.2 初始化SpringMVC其他组件

在子容器初始化的时候,添加了一个ContextRefreshListener监听器,这个监听器用于监听Spring容器启动完成的事件

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

   @Override
   public void onApplicationEvent(ContextRefreshedEvent event) {
      FrameworkServlet.this.onApplicationEvent(event);
   }
}

在refresh()方法中,当容器启动完成之后,会发布一个ContextRefreshedEvent事件,这个时候ContextRefreshListener监听器就回去调用它的onApplicationEvent()方法

@Override
public void refresh() throws BeansException, IllegalStateException {
    ……
    // Last step: publish corresponding event.
    finishRefresh();
}

protected void finishRefresh() {
    ……
    // Publish the final event.
    publishEvent(new ContextRefreshedEvent(this));
}

在ContextRefreshListener监听器的onApplicationEvent()方法,它会去调用FrameworkServlet的onApplicationEvent()方法

public void onApplicationEvent(ContextRefreshedEvent event) {
   this.refreshEventReceived = true;
   synchronized (this.onRefreshMonitor) {
      onRefresh(event.getApplicationContext());
   }
}

onRefresh()方法的实现在DispatcherServlet,在initStrategies()方法中,就会去加载SpringMVC中配置的一些组件,比如处理器映射器、处理器适配器、视图解析器等,下面以处理器映射器为例,查看它的实现逻辑

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

/**初始化策略,加了s都是多个
	 * 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);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

就是通过Spring容器取获取所有HandlerMapping实现类的Bean实例,然后按照beanName为KEY,Bean实例为VALUE的方式缓存

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    // 根据类型(多个)   默认true
    if (this.detectAllHandlerMappings) {
        // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    // 根据名字(唯一)
    else {
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }

    // 如果没有配 , 就去DispatcherServlet.properties拿默认的
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
    }

    for (HandlerMapping mapping : this.handlerMappings) {
        if (mapping.usesPathPatterns()) {
            this.parseRequestPath = true;
            break;
        }
    }
}

三、@EnableWebMvc的作用

@EnableWebMvc注解用在子容器的配置类上,也就是《SpringMVC启动原理解析(上)》定义的WebAppConfig类上,该注解源码如下:

@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

@EnableWebMvc源码主要的作用就是添加DelegatingWebMvcConfiguration这个配置类,而这个配置类继承了WebMvcConfigurationSupport,在WebMvcConfigurationSupport中定义SpringMVC各种组件的创建过程

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}

在上面的初始化SpringMVC其他组件中,其实只是从Spring容器中去拿这些组件而已,而这些组件真正创建是在WebMvcConfigurationSupport配置类中,以RequestMappingHandlerMapping为例,配置类的解析是在子容器启动的过程中,而这些组件的初始化,是在容器启动后DispatcherServlet的初始化过程中,所以在初始化的时候,可以从Spring容器里面获取到这些组件

public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    @Bean
    @SuppressWarnings("deprecation")
    public RequestMappingHandlerMapping requestMappingHandlerMapping(
        @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
        @Qualifier("mvcConversionService") FormattingConversionService conversionService,
        @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

        // 创建RequestMappingHandlerMapping实例,下面全部是填充它的属性
        RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
        mapping.setOrder(0);
        mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
        mapping.setContentNegotiationManager(contentNegotiationManager);
        mapping.setCorsConfigurations(getCorsConfigurations());

        PathMatchConfigurer pathConfig = getPathMatchConfigurer();
        if (pathConfig.getPatternParser() != null) {
            mapping.setPatternParser(pathConfig.getPatternParser());
        }
        else {
            mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
            mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());

            Boolean useSuffixPatternMatch = pathConfig.isUseSuffixPatternMatch();
            if (useSuffixPatternMatch != null) {
                mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
            }
            Boolean useRegisteredSuffixPatternMatch = pathConfig.isUseRegisteredSuffixPatternMatch();
            if (useRegisteredSuffixPatternMatch != null) {
                mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
            }
        }
        Boolean useTrailingSlashMatch = pathConfig.isUseTrailingSlashMatch();
        if (useTrailingSlashMatch != null) {
            mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
        }
        if (pathConfig.getPathPrefixes() != null) {
            mapping.setPathPrefixes(pathConfig.getPathPrefixes());
        }

        return mapping;
    }

    protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
        return new RequestMappingHandlerMapping();
    }

}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/112141.html

(0)
Java光头强的头像Java光头强

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!