文章目录
  1. 1. servlet3.0使用Java SPI机制
    1. 1.1. SCI定义
    2. 1.2. SCI概述
    3. 1.3. SCI原理
    4. 1.4. 与类加载的区别
    5. 1.5. SpringMVC中的应用
    6. 1.6. Tomcat调用SCI的时机
  2. 2. springBoot打成war在外部容器的启动
  3. 3. Springboot提供的SCI接口
    1. 3.1. ServletContextInitializer接口
    2. 3.2. 接口使用
    3. 3.3. 关于该接口的一些想法及猜测

servlet3.0使用Java SPI机制

​ Servlet3.0环境下ServletContainerInitializer(简称SCI)接口的使用.

SCI定义

1
2
3
4
5
// 完整命名: javax.servlet.ServletContainerInitializer
public interface ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx)
throws ServletException;
}

SCI概述

ServletContainerInitializer 是 Servlet 3.0 新增的一个接口,主要用于在容器启动阶段通过编程风格注册Filter, Servlet以及Listener,以取代通过web.xml配置注册。这样就利于开发内聚的web应用框架.
我们以SpringMVC举例, servlet3.0之前我们需要在web.xml中依据Spring的规范新建一堆配置。这样就相当于将框架和容器紧耦合了。而在3.x后注册的功能内聚到Spring里,Spring-web就变成一个纯粹的即插即用的组件,不用依据应用环境定义一套新的配置。

SCI原理

1)ServletContainerInitializer接口的实现类通过java SPI声明自己是ServletContainerInitializer 的provider.

2)容器启动阶段依据java spi获取到所有ServletContainerInitializer的实现类,然后执行其onStartup方法.

3)另外在实现ServletContainerInitializer时还可以通过@HandlesTypes注解定义本实现类希望处理的类型,容器会 将当前应用中所有这一类型(继承或者实现)的类放在ServletContainerInitializer接口的集合参数c中传递进来。 如果不定义处理类型,或者应用中不存在相应的实现类,则集合参数c为空.

4)这一类实现了 SCI 的接口,如果做为独立的包发布,在打包时,会在JAR 文件的 META-INF/services/javax.servlet.ServletContainerInitializer 文件中进行注册。 容器在启动时,就会扫描所有带有这些注册信息的类(@HandlesTypes(WebApplicationInitializer.class)这里就是加载WebApplicationInitializer.class类)进行解析,启动时会调用其 onStartup方法——也就是说servlet容器负责加载这些指定类, 而ServletContainerInitializer的实现者(例如Spring-web中的SpringServletContainerInitializer对接口ServletContainerInitializer的实现中,是可以直接获取到这些类的)

与类加载的区别

​ 类加载是根据限定的名称去加载,并没有相关的标准去加载未知的内容.
​ 而SCI(全称 ServletContainerInitializer)则是根据约定的标准,扫描META-INF中包含注册信息的 class 并在启动阶段调用其onStartup.

SpringMVC中的应用

通过查看ServletContainerInitializer继承层级, 可以发现spring-web中的SpringServletContainerInitializer正是实现了ServletContainerInitializer接口;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {

List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

// webAppInitializerClasses 就是servlet3.0规范中为我们收集的 WebApplicationInitializer 接口的实现类的class
// 从webAppInitializerClasses中筛选并实例化出合格的相应的类
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) 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;
}

// 这行代码说明我们在实现WebApplicationInitializer可以通过继承Ordered, PriorityOrdered来自定义执行顺序
AnnotationAwareOrderComparator.sort(initializers);
servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);

// 迭代每个initializer实现的方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}

}

1)SpringServletContainerInitializer 由支持Servlet3.0+的Servlet容器实例化并调用.

2)Servlet容器还会查询classpath下SpringServletContainerInitializer类上修饰的@HandlesTypes注解所标注的 WebApplicationInitializer接口的实现类. 这一步也是容器帮我们完成的.

3)SpringServletContainerInitializer通过实现ServletContainerInitializer将自身并入到Servlet容器的生命周期中, 并通过自身定义的WebApplicationInitializer将依赖于Spring框架的系统初始化需求与Servlet容器解耦. 即依赖于spring的系统可以通过实现WebApplicationInitializer来实现自定义的初始化逻辑. 而不需要去实现ServletContainerInitializer

Tomcat调用SCI的时机

​ 现在还有一个疑问, 就是 ServletContainerInitializer 的调用时机?, 因为servlet容器除了会回调SCI之外, 还有回调诸如servlet, listener等. 搞清楚这些先后顺序可以帮助我们快速定位和理解某些奇怪的问题.

​ 这里我们就以Tomcat举例, 以下逻辑总结于Tomcat7.x, 有兴趣的读者可以去StandardContext类中对startInternal的实现中(第5608行 —— 第5618行, 这也是Tomcat中唯一的调用ServletContainerInitializers接口的onStartup方法的位置)求证下:

1)解析web.xml
2)往ServletContext实例中注入 context-param 参数
3)回调Servlet3.0的ServletContainerInitializers接口实现类
4)触发 Listener 事件(beforeContextInitialized, afterContextInitialized); 这里只会触发 ServletContextListener 类型的

5)初始化 Filter, 调用其init方法

6)加载 启动时即加载的servlet

原文链接:https://blog.csdn.net/lqzkcx3/article/details/78507169

springBoot打成war在外部容器的启动

Springboot在使用外部容器运行时

需要写一个初始化类继承SpringBootServletInitializer

1
2
3
4
5
6
public class OasysServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(OasysApplication.class);
}
}

SpringBootServletInitializer实现了WebApplicationInitializer接口,而实现了WebApplicationInitializer的类会被servlet容器扫描,并且调用onStartup()方法。

1
2
3
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
...
}

Springboot提供的SCI接口

ServletContextInitializer接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.springframework.boot.web.servlet;

import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.springframework.web.SpringServletContainerInitializer;
import org.springframework.web.WebApplicationInitializer;

/**
* Interface used to configure a Servlet 3.0+ {@link ServletContext context}
* programmatically. Unlike {@link WebApplicationInitializer}, classes that implement this
* interface (and do not implement {@link WebApplicationInitializer}) will <b>not</b> be
* detected by {@link SpringServletContainerInitializer} and hence will not be
* automatically bootstrapped by the Servlet container.
* <p>
* This interface is designed to act in a similar way to
* {@link ServletContainerInitializer}, but have a lifecycle that's managed by Spring and
* not the Servlet container.
* <p>
* For configuration examples see {@link WebApplicationInitializer}.
*
* @author Phillip Webb
* @since 1.4.0
* @see WebApplicationInitializer
*/
@FunctionalInterface
public interface ServletContextInitializer {

/**
* Configure the given {@link ServletContext} with any servlets, filters, listeners
* context-params and attributes necessary for initialization.
* @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;

}

注释:

1)接口用于以编程方式配置Servlet 3.0+ {@link ServletContext context}。

2)实现这个接口(并且没有实现{@link WebApplicationInitializer})的类不会被{@link SpringServletContainerInitializer}检测到,因此不会被Servlet容器自动引导。

3)这个接口的作用类似于{@link ServletContainerInitializer},但是它的生命周期是由Spring管理的,而不是由Servlet容器管理的。

接口使用

在springboot中注册一个servlet可以使用这种方式

1
2
3
4
@Bean
public ServletRegistrationBean registrationBean() {
...
}

ServletRegistrationBean extends DynamicRegistrationBean

DynamicRegistrationBean extends RegistrationBean

RegistrationBean implements ServletContextInitializer, Ordered

通过继承结构可以看出,是通过实现Springboot提供的SCI接口(调用该接口的onStartup())来完成的。

关于该接口的一些想法及猜测

1)实现了该接口的类必须通过@Bean等方式注册进spring容器中,才能被springboot发现从而调用其onStartup()

2)servlet 3动态加载servlet的机制只能在上下文ServletContext加载时对Servlet,Filter,,Listener进行注册。

当springboot打包成war包放入外部容器中时,外部容器启动springboot项目入口是实现WebApplicationInitializer接口的SpringBootServletInitializer类,SpringBootServletInitializer实例执行onStartup方法的时候会通过createRootApplicationContext方法来执行run方法,接下来的过程就同以jar包形式启动的应用的run过程一样了,在内部会创建IOC容器并返回,只是以war包形式的应用在创建IOC容器过程中,不再创建Servlet容器了。

因此整个启动过程都是在ServletContext加载时进行的,使用Springboot提供的SCI接口注册Servlet的Bean也会被springboot发现从而被调用onStartup()方法完成servlet注册(且该Bean是由spring容器管理的,并不会被servlet容器发现)。

3) springboot的内置容器启动和外置容器启动

​ 内置容器:

​ jar包:

​ 执行SpringBootApplication的run方法,启动IOC容器,然后创建嵌入式Servlet容器

​ 外置容器:

​  war包:

​ 先是启动Servlet服务器,服务器启动Springboot应用(springBootServletInitizer),然后启动IOC容器

文章目录
  1. 1. servlet3.0使用Java SPI机制
    1. 1.1. SCI定义
    2. 1.2. SCI概述
    3. 1.3. SCI原理
    4. 1.4. 与类加载的区别
    5. 1.5. SpringMVC中的应用
    6. 1.6. Tomcat调用SCI的时机
  2. 2. springBoot打成war在外部容器的启动
  3. 3. Springboot提供的SCI接口
    1. 3.1. ServletContextInitializer接口
    2. 3.2. 接口使用
    3. 3.3. 关于该接口的一些想法及猜测