Spring boot傳統部署

使用spring boot很方便,一個jar包就可以啓動了,因爲它裏面內嵌了tomcat等服務器。

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

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

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

@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類以及子類都傳過來。

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

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服務器的分支是在哪兒呢?

順着這條線往下走。

SpringApplication.run(String...)
	SpringApplication.createAndRefreshContext(SpringApplicationRunListeners, ApplicationArguments)
		SpringApplication.refresh(ApplicationContext)
			AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).refresh()
				AnnotationConfigEmbeddedWebApplicationContext(AbstractApplicationContext).refresh()
					AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).onRefresh()
						AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).createEmbeddedServletContainer()

 有下面一個分支代碼

		if (localContainer == null && localServletContext == null) {
			EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
			this.embeddedServletContainer = containerFactory
					.getEmbeddedServletContainer(getSelfInitializer());
		}

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

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

	public GenericWebApplicationContext(ServletContext servletContext) {
		this.servletContext = servletContext;
	}
	public GenericWebApplicationContext(DefaultListableBeanFactory beanFactory, ServletContext servletContext) {
		super(beanFactory);
		this.servletContext = servletContext;
	}
	public void setServletContext(ServletContext servletContext) {
		this.servletContext = servletContext;
	}

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

SpringApplication.run(String...)
	SpringApplication.createAndRefreshContext(SpringApplicationRunListeners, ApplicationArguments)
		SpringApplication.applyInitializers(ConfigurableApplicationContext)
			ServletContextApplicationContextInitializer.initialize(ConfigurableApplicationContext)
				ServletContextApplicationContextInitializer.initialize(ConfigurableWebApplicationContext)
					AnnotationConfigEmbeddedWebApplicationContext(GenericWebApplicationContext).setServletContext(ServletContext)

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

先看看這個方法的內容

	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的注入過程。

builder.initializers(new ServletContextApplicationContextInitializer(servletContext));

 內部實現就是

	public SpringApplicationBuilder initializers(
			ApplicationContextInitializer<?>... initializers) {
		this.application.addInitializers(initializers);
		return this;
	}

 

至於spring的御用servlet——DispatcherServlet,則是通過動態添加方式添加到ServletContext裏的。類EmbeddedWebApplicationContext

	private void selfInitialize(ServletContext servletContext) throws ServletException {
	--------省略------------
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
	}

 類ServletRegistrationBean

	@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?)");
			return;
		}
		configure(added);
	}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章