欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringBoot——外置Servlet容器启动SpringBoot应用的原理

程序员文章站 2022-05-23 17:53:55
...

参考

1 前言

1.1 内嵌servlet 容器的情况下

根据SpringBoot的启动原理,可以知道,是通过以下代码可以直接,启动SpringBoot应用(就是SpringApplication.run(...)方法):

@SpringBootApplication
public class SpringBootApplicationStarter {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootApplicationStarter.class, args);
    }

}

1.2 在外置servlet容器的情况下

同上,同样是调用了SpringApplication.run(...)方法,不过需要先启动 服务器,再间接创建->初始化->启动SpringBoot应用,以下就是这种情况下的SpringBoot启动流程:

这种启动流程原理,是依赖Servlet3.1规范的(大致如下):

  • 根据Servlet3.1规范,服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer的实例。

  • ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名

  • 在任何 Servlet Listener 的事件被触发之前,当应用正在启动时,ServletContainerInitializeronStartup 方法将被调用。

  • ServletContainerInitializer 实现上的@handlesTypes注解用于寻找感兴趣的类,要么是@HandlesTypes注解指定的类,要么是指定类的子类。

1.2.1 启动流程(以Tomcat为例)

  • Tomcat启动

  • 根据Servlet3.1规范,找到 ServletContainerInitializer 的实现,进行实例化

  • 创建 ServletContainerInitializer 实例

  • 调用 ServletContainerInitializer 实例 (SpringServletContainerInitializer)的 onStartup 方法

  • SpringBoot 应用,就是在 onStartup 方法中启动的

1.2.2 ServletContainerInitializer 接口

ServletContainerInitializer 就是一个接口,只有一个待实现的方法onStartup

// javax/servlet/ServletContainerInitializer.class
public interface ServletContainerInitializer {
    void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}

2 外置Tomcat启动SpringBoot的流程原理

2.1 Tomcat 启动

Tomcat启动原理

重点看下 StandardContext 类的 startInternal 方法

protected synchronized void startInternal() throws LifecycleException {

// 略

    // 调用 所有的 ServletContainerInitializer 的 onStartup 方法
    for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
        initializers.entrySet()) {
        try {
            entry.getKey().onStartup(entry.getValue(),
                    getServletContext());
        } catch (ServletException e) {
            log.error(sm.getString("standardContext.sciFail"), e);
            ok = false;
            break;
        }
    }

// 略

}

2.2 根据Servlet3.1规范,找到ServletContainerInitializer的实现,进行实例化

SpringBoot 中的 ServletContainerInitializer 实现类位置在spring-web模块下
SpringBoot——外置Servlet容器启动SpringBoot应用的原理
javax.servlet.ServletContainerInitializer文件内容:

org.springframework.web.SpringServletContainerInitializer

2.3 SpringServletContainerInitializer 类

这个类实现了,ServletContainerInitializer 接口,重写了onStartup方法

SpringServletContainerInitializer@HandlesTypes(WebApplicationInitializer.class)

标注的所有这个类型( WebApplicationInitializer )的类都传入到onStartup方法的 Set集合,

为这些 WebApplicationInitializer 类型的类

创建实例 并遍历调用其 onStartup 方法。

// /org/springframework/web/SpringServletContainerInitializer.class

//感兴趣的类为WebApplicationInitializer及其子类
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    //先调用onStartup方法,会传入一系列webAppInitializerClasses
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        // 用于存放 感兴趣的类 的list 集合
        List<WebApplicationInitializer> initializers = new LinkedList();
        
        Iterator var4;
        if (webAppInitializerClasses != null) {
            var4 = webAppInitializerClasses.iterator();
            // 遍历感兴趣的类
            while(var4.hasNext()) {
                Class<?> waiClass = (Class)var4.next();
                // 判断是不是接口,是不是抽象类,是不是该类型
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        // 实例化每个WebApplicationInitializer并添加到initializers中
                        initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort(initializers);
            var4 = initializers.iterator();

            // 依次调用 WebApplicationInitializer 的onStartup方法
            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }

        }
    }
}

SpringBoot——外置Servlet容器启动SpringBoot应用的原理
SpringServletContainerInitializer方法中又调用每一个WebApplicationInitializer
onStartup方法。

即先调用SpringServletContainerInitializer实例的onStartup方法,在onStartup()方法内部又遍历每一个WebApplicationInitializer类型的实例,调用其onStartup()方法。

2.4 WebApplicationInitializer(Web应用初始化器)

在 Servlet 3.0+环境中提供的一个接口,以便编程式配置ServletContext而非传统的xml配置。

该接口的实例被SpringServletContainerInitializer自动检测(@HandlesTypes(WebApplicationInitializer.class)这种方式)。

而SpringServletContainerInitializer是Servlet 3.0+容器自动引导的。

通过WebApplicationInitializer,以往在xml中配置的DispatcherServletFilter等都可以通过代码注入。

你可以不用直接实现WebApplicationInitializer,而选择继承AbstractDispatcherServletInitializer
SpringBoot——外置Servlet容器启动SpringBoot应用的原理
可以看到,将会创建我们的com.springboot.template.ServletInitializer(继承自SpringBootServletInitializer)实例,并调用onStartup方法。

2.5 创建 SpringBootServletInitializer的实例,调用 onStartup方法

继承这个类的,就是作为SpringBoot的启动类

我定义的SpringBoot 启动类,com.springboot.template.ServletInitializer 继承 自 SpringBootServletInitializer;

SpringBootServletInitializer 又实现了 WebApplicationInitializer接口,并重写了它的 onStartup 方法。

重点是 这个 createRootApplicationContext(servletContext)

通过这个方法 开始 创建->启动 springboot应用

// org/springframework/boot/web/servlet/support/SpringBootServletInitializer.class
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {

// 略

    // 创建WebApplicationContext  和 为容器添加监听
    // 重点是 createRootApplicationContext(servletContext);
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        this.logger = LogFactory.getLog(this.getClass());
        // 创建 WebApplicationContext 
        WebApplicationContext rootApplicationContext = this.createRootApplicationContext(servletContext);
        // 如果根容器不为null
        if (rootApplicationContext != null) {
            // 则添加监听
            servletContext.addListener(new SpringBootServletInitializer.SpringBootContextLoaderListener(rootApplicationContext, servletContext));
        } else {
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
        }

    }

    // 开始 创建->启动 springboot应用
    protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
        // 创建 SpringApplicationBuilder --这一步很关键
        SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
        // 设置SpringBoot应用主启动类
        // 这里为 com.springboot.template.ServletInitializer
        builder.main(this.getClass());
        // 从servletContext中获取
        // servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)作为parent。
        // 第一次获取肯定为null
        ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
        if (parent != null) {
            this.logger.info("Root context already created (using as parent).");
            // 将ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE重置为null
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
            // 注册一个新的ParentContextApplicationContextInitializer--包含parent
            builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
        }

        // 初始化 
        // 注册ServletContextApplicationContextInitializer--包含servletContext
        builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
        // 设置applicationContextClass为AnnotationConfigServletWebServerApplicationContext
        builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
        // 调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
        // 此方法被启动引导类 ServletInitializer有方法重写, 传入的是应用构建器SpringApplicationBuilder, 也就是SpringBoot的主程序类
        builder = this.configure(builder);
        // 添加监听器
        builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
        // 返回一个准备好的SpringApplication ,准备run-很关键
        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
            application.addPrimarySources(Collections.singleton(this.getClass()));
        }

        Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
        if (this.registerErrorPageFilter) {
            application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
        }

        application.setRegisterShutdownHook(false);
        // 启动SpringBoot应用
        return this.run(application);
    }

// 略
}

2.6 createRootApplicationContext 详细流程源码分析

/org/springframework/boot/web/servlet/support/SpringBootServletInitializer.class
createRootApplicationContext 的方法

    protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
        // 创建 SpringApplicationBuilder --这一步很关键
        SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
        // 略
    }

跟踪代码到:

/org/springframework/boot/builder/SpringApplicationBuilder.class

public SpringApplicationBuilder(Class<?>... sources) {
		this.application = createSpringApplication(sources);
}

此时的Sources为空,继续跟踪代码:

/org/springframework/boot/SpringApplication.class

public class SpringApplication {

// 略

    public SpringApplication(Class<?>... primarySources) {
        this((ResourceLoader)null, primarySources);
    }

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        // web应用类型--Servlet
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 获取ApplicationContextInitializer,也是在这里开始首次加载spring.factories文件
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // 这里是第二次加载spring.factories文件
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

// 略

}

ApplicationContextInitializer(应用上下文初始化器)
是spring组件spring-context组件中的一个接口,主要是spring ioc容器刷新之前的一个回调接口,用于处于自定义逻辑。

ConfigurableApplicationContext
Spring IOC容器称为“已经被刷新”状态前的一个回调接口,去初始化ConfigurableApplicationContext。

通常用于需要对应用程序上下文进行某些编程初始化的Web应用程序中。

例如,与ConfigurableApplicationContext#getEnvironment() 对比,注册property sources或**配置文件。

另外ApplicationContextInitializer(和子类)相关处理器实例被鼓励使用去检测org.springframework.core.Ordered接口是否被实现或是否存在org.springframework.core.annotation.Order注解,如果存在,则在调用之前对实例进行相应排序。

相关标签: springboot