Spring MVC容器初始化方式

初始化过程

  1. 通过SpringServletContainerInitializer来负责对容器启动时的相关组件进行初始化。
  2. 到底要初始化哪些组件则是通过Servlet规范中所提供的注解HandlesTypes来指定的。
  3. 在SpringServletContainerInitializer中,其HandlesTypes注解则明确指定为WebApplicationInitializer.class类型。
  4. 在SpringServletContainerInitializer的onStartup方法中,则主要是完成了一些验证和组件装配的工作。
  5. 在SpringServletContainerInitializer的onStartup方法中,由于某些容器并未遵循Servlet规范,导致虽然明确指定了HandlesTypes注解的类型为WebApplicationInitializer.class,但还是坑你会存在将一些非法类型传递过来的情况,所以,该方法还对传递过来的具体类型进行了细致的判断,只有符合条件的类型才会被纳入到List<WebApplicationInitializer>集合中。
  6. 当以上判断完成之后,List<WebApplicationInitializer>就是接下来需要进行初始化的组件了。
  7. 最后通过遍历List<WebApplicationInitializer>列表,取出其中的每一个WebApplicationInitializer对象,调用这些对象的onStartup方法,完成组件的启动初始化工作。

具体的内容

  1. ServletContainerInitializer

    /**
    * ServletContainerInitializers (SCIs) are registered via an entry in the
    * file META-INF/services/javax.servlet.ServletContainerInitializer that must be
    * included in the JAR file that contains the SCI implementation.
    * <p>
    * SCI processing is performed regardless of the setting of metadata-complete.
    * SCI processing can be controlled per JAR file via fragment ordering. If
    * absolute ordering is defined, then only the JARs included in the ordering
    * will be processed for SCIs. To disable SCI processing completely, an empty
    * absolute ordering may be defined.
    * <p>
    * SCIs register an interest in annotations (class, method or field) and/or
    * types via the {@link javax.servlet.annotation.HandlesTypes} annotation which
    * is added to the class.
    *
    * @since Servlet 3.0
    */
    public interface ServletContainerInitializer {
    
        /**
        * Receives notification during startup of a web application of the classes
        * within the web application that matched the criteria defined via the
        * {@link javax.servlet.annotation.HandlesTypes} annotation.
        *
        * @param c     The (possibly null) set of classes that met the specified
        *              criteria
        * @param ctx   The ServletContext of the web application in which the
        *              classes were discovered
        *
        * @throws ServletException If an error occurs
        */
        void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
    }
    
    • 从Servlet3.0开始,不强制使用web.xml文件作为应用容器启动的描述符文件。
    • ServletContainerInitializeronStartup方法会在容器启动的时候被调用,以此完成一些初始化的动作
    • 这是一个SPI的接口,在ServiceLoader(JVM类加载器相关知识)规范中定义配置文件位于META-INF/services文件夹下边,文件名为javax.servlet.ServletContainerInitializer里边的定义为该接口的实现类,以此完成应用容器初始化后的调用,在Spring-web中该文件内容为org.springframework.web.SpringServletContainerInitializer
  2. SpringServletContainerInitializer

    /**
    * Servlet 3.0 {@link ServletContainerInitializer} designed to support code-based
    * configuration of the servlet container using Spring's {@link WebApplicationInitializer}
    * SPI as opposed to (or possibly in combination with) the traditional
    * {@code web.xml}-based approach.
    *
    * <h2>Mechanism of Operation</h2>
    * This class will be loaded and instantiated and have its {@link #onStartup}
    * method invoked by any Servlet 3.0-compliant container during container startup assuming
    * that the {@code spring-web} module JAR is present on the classpath. This occurs through
    * the JAR Services API {@link ServiceLoader#load(Class)} method detecting the
    * {@code spring-web} module's {@code META-INF/services/javax.servlet.ServletContainerInitializer}
    * service provider configuration file. See the
    * <a href="https://download.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Service%20Provider">
    * JAR Services API documentation</a> as well as section <em>8.2.4</em> of the Servlet 3.0
    * Final Draft specification for complete details.
    *
    * <h3>In combination with {@code web.xml}</h3>
    * A web application can choose to limit the amount of classpath scanning the Servlet
    * container does at startup either through the {@code metadata-complete} attribute in
    * {@code web.xml}, which controls scanning for Servlet annotations or through an
    * {@code <absolute-ordering>} element also in {@code web.xml}, which controls which
    * web fragments (i.e. jars) are allowed to perform a {@code ServletContainerInitializer}
    * scan. When using this feature, the {@link SpringServletContainerInitializer}
    * can be enabled by adding "spring_web" to the list of named web fragments in
    * {@code web.xml} as follows:
    *
    * <pre class="code">
    * &lt;absolute-ordering&gt;
    *   &lt;name>some_web_fragment&lt;/name&gt;
    *   &lt;name>spring_web&lt;/name&gt;
    * &lt;/absolute-ordering&gt;
    * </pre>
    *
    * <h2>Relationship to Spring's {@code WebApplicationInitializer}</h2>
    * Spring's {@code WebApplicationInitializer} SPI consists of just one method:
    * {@link WebApplicationInitializer#onStartup(ServletContext)}. The signature is intentionally
    * quite similar to {@link ServletContainerInitializer#onStartup(Set, ServletContext)}:
    * simply put, {@code SpringServletContainerInitializer} is responsible for instantiating
    * and delegating the {@code ServletContext} to any user-defined
    * {@code WebApplicationInitializer} implementations. It is then the responsibility of
    * each {@code WebApplicationInitializer} to do the actual work of initializing the
    * {@code ServletContext}. The exact process of delegation is described in detail in the
    * {@link #onStartup onStartup} documentation below.
    *
    * <h2>General Notes</h2>
    * In general, this class should be viewed as <em>supporting infrastructure</em> for
    * the more important and user-facing {@code WebApplicationInitializer} SPI. Taking
    * advantage of this container initializer is also completely <em>optional</em>: while
    * it is true that this initializer will be loaded and invoked under all Servlet 3.0+
    * runtimes, it remains the user's choice whether to make any
    * {@code WebApplicationInitializer} implementations available on the classpath. If no
    * {@code WebApplicationInitializer} types are detected, this container initializer will
    * have no effect.
    *
    * <p>Note that use of this container initializer and of {@code WebApplicationInitializer}
    * is not in any way "tied" to Spring MVC other than the fact that the types are shipped
    * in the {@code spring-web} module JAR. Rather, they can be considered general-purpose
    * in their ability to facilitate convenient code-based configuration of the
    * {@code ServletContext}. In other words, any servlet, listener, or filter may be
    * registered within a {@code WebApplicationInitializer}, not just Spring MVC-specific
    * components.
    *
    * <p>This class is neither designed for extension nor intended to be extended.
    * It should be considered an internal type, with {@code WebApplicationInitializer}
    * being the public-facing SPI.
    *
    * <h2>See Also</h2>
    * See {@link WebApplicationInitializer} Javadoc for examples and detailed usage
    * recommendations.<p>
    *
    * @author Chris Beams
    * @author Juergen Hoeller
    * @author Rossen Stoyanchev
    * @since 3.1
    * @see #onStartup(Set, ServletContext)
    * @see WebApplicationInitializer
    */
    @HandlesTypes(WebApplicationInitializer.class)
    public class SpringServletContainerInitializer implements ServletContainerInitializer {
    
        /**
        * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
        * implementations present on the application classpath.
        * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
        * Servlet 3.0+ containers will automatically scan the classpath for implementations
        * of Spring's {@code WebApplicationInitializer} interface and provide the set of all
        * such types to the {@code webAppInitializerClasses} parameter of this method.
        * <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
        * this method is effectively a no-op. An INFO-level log message will be issued notifying
        * the user that the {@code ServletContainerInitializer} has indeed been invoked but that
        * no {@code WebApplicationInitializer} implementations were found.
        * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
        * they will be instantiated (and <em>sorted</em> if the @{@link
        * org.springframework.core.annotation.Order @Order} annotation is present or
        * the {@link org.springframework.core.Ordered Ordered} interface has been
        * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
        * method will be invoked on each instance, delegating the {@code ServletContext} such
        * that each instance may register and configure servlets such as Spring's
        * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
        * or any other Servlet API componentry such as filters.
        * @param webAppInitializerClasses all implementations of
        * {@link WebApplicationInitializer} found on the application classpath
        * @param servletContext the servlet context to be initialized
        * @see WebApplicationInitializer#onStartup(ServletContext)
        * @see AnnotationAwareOrderComparator
        */
        @Override
        public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
                throws ServletException {
    
            List<WebApplicationInitializer> initializers = new LinkedList<>();
    
            if (webAppInitializerClasses != null) {
                for (Class<?> waiClass : webAppInitializerClasses) {
                    // Be defensive: Some servlet containers provide us with invalid classes,
                    // no matter what @HandlesTypes says...
                    if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                            WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                        try {
                            initializers.add((WebApplicationInitializer)
                                    ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                        }
                        catch (Throwable ex) {
                            throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                        }
                    }
                }
            }
    
            if (initializers.isEmpty()) {
                servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
                return;
            }
    
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort(initializers);
            for (WebApplicationInitializer initializer : initializers) {
                initializer.onStartup(servletContext);
            }
        }
    
    }
    
    • SpringServletContainerInitializer负责将ServletContext的初始化委托给用户实现的WebApplicationInitializer,所以ServletContext的真正初始化是在WebApplicationInitializer中完成的。
    • SpringServletContainerInitializer作为WebApplicationInitializer的基础设施而存在,但不是必须的。在servlet3.0的环境中会加载和调用SpringServletContainerInitializeronStartup方法,用户可以选择任意的WebApplicationInitializer的实现,如果找不到WebApplicationInitializerSpringServletContainerInitializer将不做任何事。
    • 通常来说SpringServletContainerInitializer是一个内部的类,如果要扩展建议实现WebApplicationInitializer接口。
    • @HandlesTypes(WebApplicationInitializer.class)是Servlet3.0的注解,作用是声明一个class的数组传递给ServletContainerInitializer,扫描类路径下WebApplicationInitializer接口的实现类,然后传递给public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException方法作为第一个参数。
  3. WebApplicationInitializer

    public interface WebApplicationInitializer {
    
        /**
        * Configure the given {@link ServletContext} with any servlets, filters, listeners
        * context-params and attributes necessary for initializing this web application. See
        * examples {@linkplain WebApplicationInitializer above}.
        * @param servletContext the {@code ServletContext} to initialize
        * @throws ServletException if any call against the given {@code ServletContext}
        * throws a {@code ServletException}
        */
        void onStartup(ServletContext servletContext) throws ServletException;
    
    }
    

总结

SpringServletContainerInitializer整个初始化过程中,其扮演的角色实际上是委托或是代理的角色,真正完成初始化工作的依旧是一个个的WebApplicationInitializer实现类。

关于Spring Boot Web容器初始化方式可以参考Spring Boot Web容器初始化方式这篇文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270