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

导读:本篇文章讲解 SpringBoot——外置Servlet容器启动SpringBoot应用的原理,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

参考

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模块下
aD4F1O.png
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);
            }

        }
    }
}

aD5dZd.png
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
aDIM6S.png
可以看到,将会创建我们的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注解,如果存在,则在调用之前对实例进行相应排序。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/69763.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!