spring源码------spring什么时候以及何时初始化w

2019-12-12  本文已影响0人  szhlcy

@[toc]
 spring的web应用的上下文并不是在容器启动的时候就进行初始化的,而是在第一次请求的时候进行初始化的。

1.从Servlet规范到Spring

1.1 Servlet规范部分说明

 spring的web应用(不包含web-flux)是基于Servlet规范进行的搭建的,这里需要先讲一下Servlet规范的部分内容,因为spring的web上下文的初始化也是要从这里开始的。
Servlet是按照一个严格定义的生命周期被管理,该生命周期规定了Servlet如何被加载、实例化、初始化、处理客户端请求,以及何时结束服务。该声明周期可以通过 javax.servlet.Servlet 接口中的initservicedestroy这些 API 来表示,所有 Servlet必须直接或间接的实现 GenericServletHttpServlet 抽象类。
servlet 对象实例化后,容器必须初始化 servlet之后才能处理客户端的请求。初始化的目的是以便 Servlet能读取持久化配置数据,初始化一些代价高的资源(比如JDBC™ API 连接),或者执行一些一次性的动作。容器通过调用 Servlet实例的 init 方法完成初始化,init方法定义在Servlet接口中,并且提供一个唯一的 ServletConfig接口实现的对象作为参数,该对象每个 Servlet 实例一个。

1.2 分析初始化的入口

 从上面的规范说明中知道了,spring会实现GenericServlet 抽象类的init方法来初始化相关的信息,
通过查看init方法的实现类,发现在spring的所有的这个方法的实现中只有HttpServletBean是符合需求的。因此我们进入到这个方法。

2. 初始化上下文的入口HttpServletBean

 这里直接进入到HttpServletBean实现的init方法进行分析。

    public final void init() throws ServletException {

        // Set bean properties from init parameters.
        //获取ServletConfig,从servlet中获取对应的初始化参数,ServletConfig是servlet规范中的接口类
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        //如果初始化参数不为空,则需要将
        if (!pvs.isEmpty()) {
            try {
                //获取HttpServletBean的BeanWrapper
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                //讲ServletContext转化为一个ResourceLoader
                ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                //创建ResourceEditor并保存到HttpServletBean中
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                //这里是空方法,可以子类实现
                initBeanWrapper(bw);
                //将ServletConfig对应的属性保存到HttpServletBean
                bw.setPropertyValues(pvs, true);
            }
            catch (BeansException ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
                }
                throw ex;
            }
        }
        //进行初始化,这个是一个交给子类实现的方法,可以自定义初始化的逻辑
        initServletBean();
    }

 可以看到实现的逻辑也是比较简单的,就是从ServletConfig获取servlet容器中的相关的初始化的参数,如果属性不为空的时候,可以定义自己的属性处理逻辑,这里会调用。处理完容器中相关的初始化参数之后就是进行初始化了。这个初始化的方法initServletBean是交给子类去自由实现初始化逻辑的。
 这里补充说明ServletConfigServletContext这两个类都是servlet规范中的接口类。这里进行简单的说明一下

说明
ServletConfig ServletConfig是每个 Servlet 实例一个,配置对象允许 Servlet 访问由 Web 应用配置信息提供的键-值对的初始化参数
ServletContext ServletContext 接口定义了 servlet 运行在的 Web 应用的视图,servlet 可以使用 ServletContext 对象记录事件,获取 URL 引用的资源,存取当前上下文的其他 servlet 可以访问的属性。默认的 ServletContext 是非分布式的且仅存在于一个 JVM 中。

3. 初始化web应用上下文的FrameworkServlet

 在上面提到的initServletBean方法,其实现在spring中只有一个类实现了那就是FrameworkServlet。这个类是spring对spring应用上下文的集成类。作用很多:

  1. 为每个servlet应用管理一个WebApplicationContext对象。
  2. 在请求处理时发布事件,不管是否成功处理了请求

 这个类也提供了很多扩展,其子类必须实现这个类的doService方法。同时也可以重载initFrameworkServlet来自定义初始化。
 现在看看FrameworkServlet如何进行应用的初始化的,直接进入到initServletBean进行查看。

    protected final void initServletBean() throws ServletException {
        //获取ServletContext,打印servlet应用的名称
        getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
        if (logger.isInfoEnabled()) {
            logger.info("Initializing Servlet '" + getServletName() + "'");
        }
        //获取当前系统时间,用于计算初始化的时长
        long startTime = System.currentTimeMillis();

        try {
            //初始化web上下文WebApplicationContext
            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");
        }
    }

 可以看到这里只是简单的几个方法的调用,就分别完成了spring的web上下文初始化,以及其他的初始化,其中后面的初始化时可以自己定义逻辑的,默认是空的。这里主要就看上下文的初始化。

    protected WebApplicationContext initWebApplicationContext() {
        //如果用户指定了WebApplicationContext情况下,从ServletContext获取WebApplicationContext,这里获取的属性名是WebApplicationContext.class.getName() + ".ROOT"
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;
        //如果webApplicationContext不是null,说明已经初始化过了,在spring容器初始化的时候,会设置这个值得,因此这里判断是true
        if (this.webApplicationContext != null) {
        
            //设置已经存在的上下文
            wac = this.webApplicationContext;
            //如果是ConfigurableWebApplicationContext类型的,需要先设置WebApplicationContext为根上下文
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                //检查当前上下文是否是活跃的,既可以用的,如果不是的则需要刷新
                if (!cwac.isActive()) {
                //如果还没有设置根上下文则进行设置
                    if (cwac.getParent() == null) {
                    
                        cwac.setParent(rootContext);
                    }
                    //配置并刷新web上下文
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        //如果wac是null,则书名web上下文是null,则在servlet中寻找是否存在web上下文,这里寻找的是属性名为FrameworkServlet.class.getName() + ".CONTEXT."+当前servlet的名称的上下文
        if (wac == null) {
            wac = findWebApplicationContext();
        }
        //如果servlet中也不存在上下文,则创建一个
        if (wac == null) {
            
            wac = createWebApplicationContext(rootContext);
        }
        //检查onRefresh是否已经调用了
        if (!this.refreshEventReceived) {
        
            synchronized (this.onRefreshMonitor) {
                //模板方法,可以覆盖该方法以添加特定于servlet的刷新工作。成功刷新上下文后调用
                onRefresh(wac);
            }
        }
        //我们应该将上下文作为ServletContext属性发布吗,默认为true
        if (this.publishContext) {
            
            //获取要设置的属性名 FrameworkServlet.class.getName() + ".CONTEXT."+当前servlet的名称
            String attrName = getServletContextAttributeName();
            //讲当前上下文设置到servlet上下文中
            getServletContext().setAttribute(attrName, wac);
        }

        return wac;
    }

 这里在逻辑主要分文两点,寻找可以用的web应用上下文,然后进行时间的发布跟其他刷新工作。这里需要说明几点的是:

  1. 实例化跟初始化是有区别的,在这里方法这里是初始化而不是实例化
  2. 在正常情况下进入到这个方法的时候,是在spring的容器已经初始化完毕以后了,因为这个时候整个服务已经起来了,容器里面会包含对应的web应用上下文,因此上面方法中的webApplicationContext不会为null
  3. 对应的web的服务也是活跃状态,因此上面方法中的isActive的判断是true。

 对于其中的第二点,这里说明一下webApplicationContext是什么时候被设置进来的,因为FrameworkServlet实现了ApplicationContextAware接口,因此在FrameworkServlet实例化之后初始化之前会调用实现的setApplicationContext方法


    public void setApplicationContext(ApplicationContext applicationContext) {
        if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
            this.webApplicationContext = (WebApplicationContext) applicationContext;
            this.webApplicationContextInjected = true;
        }
    }

 这里关于ApplicationContextAware可以看看对应的前面的spring的生命周期的文章Spring源码----Spring的Bean生命周期流程图及代码解释

4. 初始化spring相关web相关类的DispatcherServlet

4.1 初始化servlet的常用策略

 我们注意到在FrameworkServlet中的initWebApplicationContext方法中有这么的一个步骤,在找到了web应用上下文之后,会有一个检查onRefresh方法是否已经调用了的步骤,如果没有调用就会调用对应的onRefresh方法,如果调用了就不用处理,那么还有哪里会调用呢。现在要分析就跟这个有关。
onRefresh方法在FrameworkServlet中是空逻辑的,其主要逻辑在DispatcherServlet这个类中进行的。接下来就进入到里面

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

    /**
     * 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) {
        //初始化解析文件相关的MultipartResolver,寻找对应的bean,然后保存
        initMultipartResolver(context);
        //初始化区域解析用的LocaleResolver,寻找对应的bean,然后保存
        initLocaleResolver(context);
        //初始化解析主题用的ThemeResolver,寻找对应的bean,然后保存
        initThemeResolver(context);
        //初始化handlerMapping,逻辑就是找到容器中所有的HandlerMapping类型的bean然后放到一个集合中
        initHandlerMappings(context);
        //初始化HandlerAdapter,逻辑就是找到容器中所有的HandlerAdapter类型的bean然后放到一个集合中
        initHandlerAdapters(context);
        //初始化HandlerExceptionResolver,逻辑跟上面的一样寻找HandlerExceptionResolver类型的bean,然后保存
        initHandlerExceptionResolvers(context);
        //初始化请求视图(url)转化为本地试图(url)的RequestToViewNameTranslator,寻找对应的bean,然后保存
        initRequestToViewNameTranslator(context);
        //初始化ViewResolver试图解析器,逻辑跟上面的一样寻找ViewResolver类型的bean,然后保存
        initViewResolvers(context);
        //初始化FlashMapManager,用来管理FlashMap(保存两个视图url之间的关系,在用direct的时候会用到)
        initFlashMapManager(context);
    }

 是不是感觉很多熟悉的东西出现了,没错这就是整个springmvc会用到的东西会在这里进行装载保存,在后面进行处理的时候用到。
 到这里spring的web应用会用的东西在这里就已经初始化完成了。

上一篇下一篇

猜你喜欢

热点阅读