天天看點

Spring boot傳統部署

使用spring boot很友善,一個jar包就可以啟動了,因為它裡面内嵌了tomcat等伺服器。

但是spring boot也提供了部署到獨立伺服器的方法。

如果你看文檔的話,從jar轉換為war包很簡單,pom.xml的配置修改略去不講。

隻看source的修改,很簡單,隻要一個配置類,繼承自SpringBootServletInitializer, 并覆寫configure方法。

Java代碼  

Spring boot傳統部署
Spring boot傳統部署

@SpringBootApplication  

public class TestApplication extends SpringBootServletInitializer{  

    @Override  

    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {  

        return builder.sources(TestApplication .class);  

    }  

    public static void main(String[] args) {  

        SpringApplication.run(TestApplication.class, args);  

}  

}

 對,你沒看錯,就這麼簡單。

但是,我覺得但凡有點兒好奇心的人都不甘于就這麼用它,總會想知道為啥這樣就行了?

那麼我們根據調用關系來弄個究竟。

SpringBootServletInitializer.configure

<-createRootApplicationContext

<-onStartup

<-SpringServletContainerInitializer.onStartup

SpringServletContainerInitializer這個類比較特殊,實作的是interface ServletContainerInitializer,這個類的onStartup方法,是由tomcat調用了。

那麼tomcat是怎麼找到它的呢?是搜尋的這個資源檔案META-INF/services/javax.servlet.ServletContainerInitializer

而在spring的包spring-web-xxxx.jar包裡正好有這個檔案,它注冊的恰恰就是這個類

寫道

org.springframework.web.SpringServletContainerInitializer

這個類有個注解@HandlesTypes(WebApplicationInitializer.class)。

調用SpringServletContainerInitializer.onStartup方法時,會把所有的WebApplicationInitializer類以及子類都傳過來。

然後再通過條件過濾一下。

Spring boot傳統部署
Spring boot傳統部署

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);  

                }  

 也就是隻要是非interface,且非抽象類,并都是WebApplicationInitializer的字類的話,就會被執行個體化,并最終調用。

然後,在SpringBootServletInitializer的createRootApplicationContext方法裡,最終會初始化SpringApplication,調用其run方法,跟直接運作入口的main方法是一樣的了。

 既然從,SpringApplication.run方法以後走的邏輯是一樣的,那麼是不是需要啟動内嵌web伺服器的分支是在哪兒呢?

順着這條線往下走。

Spring boot傳統部署
Spring boot傳統部署

SpringApplication.run(String...)  

    SpringApplication.createAndRefreshContext(SpringApplicationRunListeners, ApplicationArguments)  

        SpringApplication.refresh(ApplicationContext)  

            AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).refresh()  

                AnnotationConfigEmbeddedWebApplicationContext(AbstractApplicationContext).refresh()  

                    AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).onRefresh()  

                        AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).createEmbeddedServletContainer()  

 有下面一個分支代碼

Spring boot傳統部署
Spring boot傳統部署

if (localContainer == null && localServletContext == null) {  

    EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();  

    this.embeddedServletContainer = containerFactory  

            .getEmbeddedServletContainer(getSelfInitializer());  

 localContainer在初始化的時候沒有指派過程,一直會是null,主要是localServletContext,看看什麼時候為null,什麼時候有值。

它的指派有三個地方,兩個構造函數,一個set方法

Spring boot傳統部署
Spring boot傳統部署

public GenericWebApplicationContext(ServletContext servletContext) {  

    this.servletContext = servletContext;  

public GenericWebApplicationContext(DefaultListableBeanFactory beanFactory, ServletContext servletContext) {  

    super(beanFactory);  

public void setServletContext(ServletContext servletContext) {  

 查找一下,發現構造函數并沒有地方調用,調用的是這個set方法,過程如下

Spring boot傳統部署
Spring boot傳統部署

        SpringApplication.applyInitializers(ConfigurableApplicationContext)  

            ServletContextApplicationContextInitializer.initialize(ConfigurableApplicationContext)  

                ServletContextApplicationContextInitializer.initialize(ConfigurableWebApplicationContext)  

                    AnnotationConfigEmbeddedWebApplicationContext(GenericWebApplicationContext).setServletContext(ServletContext)  

 你會發現,至少到SpringApplication.applyInitializers(ConfigurableApplicationContext)這一步,部署不部署到tomcat,都會執行這個方法的,那麼差別在哪兒呢?

先看看這個方法的内容

Spring boot傳統部署
Spring boot傳統部署

protected void applyInitializers(ConfigurableApplicationContext context) {  

    for (ApplicationContextInitializer initializer : getInitializers()) {  

        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(  

                initializer.getClass(), ApplicationContextInitializer.class);  

        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");  

        initializer.initialize(context);  

 也就是說,如果注入的initializers裡是否包含了ServletContextApplicationContextInitializer,就能決定是否會調用以後的邏輯。

那麼傳回到文章的開頭,看看抽象類SpringBootServletInitializer,就會發現在方法createRootApplicationContext裡,類ServletContextApplicationContextInitializer的注入過程。

Spring boot傳統部署
Spring boot傳統部署

builder.initializers(new ServletContextApplicationContextInitializer(servletContext));  

 内部實作就是

Spring boot傳統部署
Spring boot傳統部署

public SpringApplicationBuilder initializers(  

        ApplicationContextInitializer<?>... initializers) {  

    this.application.addInitializers(initializers);  

    return this;  

至于spring的禦用servlet——DispatcherServlet,則是通過動态添加方式添加到ServletContext裡的。類EmbeddedWebApplicationContext

Spring boot傳統部署
Spring boot傳統部署

private void selfInitialize(ServletContext servletContext) throws ServletException {  

--------省略------------  

    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {  

        beans.onStartup(servletContext);  

 類ServletRegistrationBean

Spring boot傳統部署
Spring boot傳統部署

@Override  

public void onStartup(ServletContext servletContext) throws ServletException {  

    Assert.notNull(this.servlet, "Servlet must not be null");  

    String name = getServletName();  

    if (!isEnabled()) {  

        logger.info("Servlet " + name + " was not registered (disabled)");  

        return;  

    logger.info("Mapping servlet: '" + name + "' to " + this.urlMappings);  

    Dynamic added = servletContext.addServlet(name, this.servlet);  

    if (added == null) {  

        logger.info("Servlet " + name + " was not registered "  

                + "(possibly already registered?)");  

    configure(added);  

繼續閱讀