spring的启动过程(二) :springMvc的启动过程

文章详细阐述了Spring和SpringMVC的父子容器关系,以及在项目中的作用。Spring作为父容器管理bean,SpringMVC作为子容器处理web组件。在SpringBoot之前,项目常使用Spring+SpringMVC架构,通过web.xml启动,DispatcherServlet负责请求调度。文章还深入到代码层面,展示了SpringMVC容器初始化的过程,包括DispatcherServlet的init方法、WebApplicationContext的创建和刷新,以及HandlerMapping的初始化等关键步骤。

在上一篇文章中,我们详解了spring的启动过程,这一篇介绍spring mvc的启动过程,那么spring和spring mvc有什么联系呢。

   1.Spring和SpringMVC是父子容器关系。
   2.Spring整体框架的核心思想是容器,用来管理bean的生命周期,而一个项目中
   会包含很多容器,并且它们分上下层关系,目前最常用的一个场景是在一个项目
   中导入Spring和SpringMVC框架,而Spring和SpringMVC其实就是两个容器,
   Spring是父容器,SpringMVC是子容器,Spring父容器中注册的Bean对
   SpringMVC子容器是可见的,反之则不行。
   3.按照官方文档推荐,根据不同的业务模块来划分不同的容器中注册不同的Bean,
   SpringMVC主要就是为我们构建web应用程序,那么SpringMVC子容器用来注册
   web组件的Bean,如控制器(controller层,这也是为什么spring配置文件中包扫描
   排除controller的原因)、处理器映射、视图解析器等。而Spring用来注册其他Bean,
   这些Bean通常是驱动应用后端的中间层和数据层组件。

而在spring boot以前,我们常用的架构是spring+spring mvc,上文也提起过,web项目的启动过程是先读web.xml文件,web.xml 加载顺序为: context-param < listener < filter < servlet。

在上篇文章中spring root容器的初始化是在contextLoaderListenner这个上下文监听器中实现的,再往下走spring mvc的启动是在DispatcherServlet中。

springmvc的核心是DispatcherServlet,它是请求调度控制器,负责拦截客户端请求,根据url通过HandlerMapping找到对应的handler,通过HandlerAdapter根据handler匹配对应的controller,完成接口调用后返回model,然后通过ViewResolver渲染视图,最后DispatcherServlet响应用户请求。
继承关系: DispatcherServlet -> FrameworkServlet -> HttpServletBean

初始化
1.HttpServletBean 的 init() 方法

@Override
public final void init() throws ServletException {
    try {
        // ServletConfigPropertyValues是静态内部类,使用ServletConfig获取 web.xml中配置的参数
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        // 使用 BeanWrapper 来构造 DispatcherServlet
        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) {}
    // 让子类实现的方法,这种在父类定义在子类实现的方式叫做模版方法模式
    initServletBean();
}  

2.FrameworkServlet 的 initServletBean() 方法

 protected final void initServletBean() throws ServletException {
        this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
        }

        long startTime = System.currentTimeMillis();

        try {
            //初始化 WebApplicationContext (即SpringMVC的IOC容器)
            this.webApplicationContext = this.initWebApplicationContext();
            this.initFrameworkServlet();
        } catch (RuntimeException | ServletException var4) {
            this.logger.error("Context initialization failed", var4);
            throw var4;
        }

      ......错误日志略......

    }

点进 this.initWebApplicationContext();

protected WebApplicationContext initWebApplicationContext() {
    // 获取 ContextLoaderListener 初始化并注册在 ServletContext 中的根容器,即 Spring 的容器
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    if (this.webApplicationContext != null) {
        // 因为 WebApplicationContext 不为空,说明该类在构造时已经将其注入
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    // 将 Spring 的容器设为 SpringMVC 容器的父容器
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
      // 如果 WebApplicationContext 为空,则进行查找,能找到说明上下文已经在别处初始化。
      wac = findWebApplicationContext();
    }
    if (wac == null) {
        // 如果 WebApplicationContext 仍为空,则以 Spring 的容器为父上下文建立一个新的。
        wac = createWebApplicationContext(rootContext);
    }
    if (!this.refreshEventReceived) {
        // 重点方法,由 DispatcherServlet 实现,后续会讲解
        onRefresh(wac);
    }
    if (this.publishContext) {
        // 发布这个 WebApplicationContext 容器到 ServletContext 中
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

点进createWebApplicationContext(rootContext);

 protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
        Class<?> contextClass = this.getContextClass();
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
        } else {
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            wac.setEnvironment(this.getEnvironment());
            wac.setParent(parent);
            String configLocation = this.getContextConfigLocation();
            if (configLocation != null) {
                wac.setConfigLocation(configLocation);
            }
            //重要方法
            this.configureAndRefreshWebApplicationContext(wac);
            return wac;
        }
    }

点进this.configureAndRefreshWebApplicationContext(wac);
其实在这里已经和spring的启动源码一样了

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            if (this.contextId != null) {
                wac.setId(this.contextId);
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(this.getServletContext().getContextPath()) + '/' + this.getServletName());
            }
        }

        wac.setServletContext(this.getServletContext());
        wac.setServletConfig(this.getServletConfig());
        wac.setNamespace(this.getNamespace());
        wac.addApplicationListener(new SourceFilteringListener(wac, new FrameworkServlet.ContextRefreshListener()));
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment)env).initPropertySources(this.getServletContext(), this.getServletConfig());
        }

        this.postProcessWebApplicationContext(wac);
        this.applyInitializers(wac);
        //点进
        wac.refresh();
    }

wac.refresh();这个方法在上文已经介绍过了,所以spring和spring mvc容器的初始化过程是一样的。

public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

spring mvc容器初始化完成,里面的bean也初始化完成。
具体是在 this.finishBeanFactoryInitialization(beanFactory);中完成的,
在上一篇文章中我并没有详细介绍这个方法,下面贴张图
在这里插入图片描述
在这里插入图片描述
这两张图是普通的bean实例化,以及requestmapping注解的方法实例化成RequestMappingHandlerMapping 实例。

bean的实例化无非就是从BeanDefinition中获取bean的信息,执行一系列的BeanPostProcessor,最后通过构造器反射创建bean,注入bean的属性等等

接下来看DispatcherServlet 实现的onRefresh(wac);

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

    protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
        //重要方法,可自己了解
        this.initHandlerMappings(context);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }

initHandlerMappings() 方法从 SpringMVC 的容器及 Spring 的容器中查找所有的 HandlerMapping 实例,并把它们放入到 handlerMappings 这个 list 中。这个方法并不是对 HandlerMapping 实例的创建,HandlerMapping 实例是在上面 WebApplicationContext 容器初始化,即 SpringMVC 容器初始化的时候创建的。

我们通常在页面发起一个request请求,springMVC会先解析url,然后通过url去urlLookup中找对应的mapping,再根据mapping去mappingLookup中找Controller的Mapping处理方法HandlerMethod,如果有cros配置,那么最后再根据HandlerMethod找到对应的配置,最后通过反射最终调用到我们Controller的@RequestMapping方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值