1. 程式人生 > >Spring boot傳統部署

Spring boot傳統部署

types tar image 修改 技術 發現 tools tomcat 添加

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

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

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

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

Java代碼 技術分享圖片
  1. @SpringBootApplication
  2. public class TestApplication extends SpringBootServletInitializer{
  3. @Override
  4. protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
  5. return builder.sources(TestApplication .class);
  6. }
  7. public static void main(String[] args) {
  8. SpringApplication.run(TestApplication.class, args);
  9. }
  10. }

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

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

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

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

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

Java代碼 技術分享圖片
  1. if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
  2. WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
  3. try {
  4. initializers.add((WebApplicationInitializer) waiClass.newInstance());
  5. }
  6. catch (Throwable ex) {
  7. throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
  8. }
  9. }

也就是只要是非interface,且非抽象類,並都是WebApplicationInitializer的字類的話,就會被實例化,並最終調用。

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

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

順著這條線往下走。

Java代碼 技術分享圖片
  1. SpringApplication.run(String...)
  2. SpringApplication.createAndRefreshContext(SpringApplicationRunListeners, ApplicationArguments)
  3. SpringApplication.refresh(ApplicationContext)
  4. AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).refresh()
  5. AnnotationConfigEmbeddedWebApplicationContext(AbstractApplicationContext).refresh()
  6. AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).onRefresh()
  7. AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).createEmbeddedServletContainer()

有下面一個分支代碼

Java代碼 技術分享圖片
  1. if (localContainer == null && localServletContext == null) {
  2. EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
  3. this.embeddedServletContainer = containerFactory
  4. .getEmbeddedServletContainer(getSelfInitializer());
  5. }

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

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

Java代碼 技術分享圖片
  1. public GenericWebApplicationContext(ServletContext servletContext) {
  2. this.servletContext = servletContext;
  3. }
  4. public GenericWebApplicationContext(DefaultListableBeanFactory beanFactory, ServletContext servletContext) {
  5. super(beanFactory);
  6. this.servletContext = servletContext;
  7. }
  8. public void setServletContext(ServletContext servletContext) {
  9. this.servletContext = servletContext;
  10. }

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

Java代碼 技術分享圖片
  1. SpringApplication.run(String...)
  2. SpringApplication.createAndRefreshContext(SpringApplicationRunListeners, ApplicationArguments)
  3. SpringApplication.applyInitializers(ConfigurableApplicationContext)
  4. ServletContextApplicationContextInitializer.initialize(ConfigurableApplicationContext)
  5. ServletContextApplicationContextInitializer.initialize(ConfigurableWebApplicationContext)
  6. AnnotationConfigEmbeddedWebApplicationContext(GenericWebApplicationContext).setServletContext(ServletContext)

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

先看看這個方法的內容

Java代碼 技術分享圖片
  1. protected void applyInitializers(ConfigurableApplicationContext context) {
  2. for (ApplicationContextInitializer initializer : getInitializers()) {
  3. Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
  4. initializer.getClass(), ApplicationContextInitializer.class);
  5. Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
  6. initializer.initialize(context);
  7. }
  8. }

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

那麽返回到文章的開頭,看看抽象類SpringBootServletInitializer,就會發現在方法createRootApplicationContext裏,類ServletContextApplicationContextInitializer的註入過程。

Java代碼 技術分享圖片
  1. builder.initializers(new ServletContextApplicationContextInitializer(servletContext));

內部實現就是

Java代碼 技術分享圖片
  1. public SpringApplicationBuilder initializers(
  2. ApplicationContextInitializer<?>... initializers) {
  3. this.application.addInitializers(initializers);
  4. return this;
  5. }

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

Java代碼 技術分享圖片
  1. private void selfInitialize(ServletContext servletContext) throws ServletException {
  2. --------省略------------
  3. for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
  4. beans.onStartup(servletContext);
  5. }
  6. }

類ServletRegistrationBean

Java代碼 技術分享圖片
  1. @Override
  2. public void onStartup(ServletContext servletContext) throws ServletException {
  3. Assert.notNull(this.servlet, "Servlet must not be null");
  4. String name = getServletName();
  5. if (!isEnabled()) {
  6. logger.info("Servlet " + name + " was not registered (disabled)");
  7. return;
  8. }
  9. logger.info("Mapping servlet: ‘" + name + "‘ to " + this.urlMappings);
  10. Dynamic added = servletContext.addServlet(name, this.servlet);
  11. if (added == null) {
  12. logger.info("Servlet " + name + " was not registered "
  13. + "(possibly already registered?)");
  14. return;
  15. }
  16. configure(added);
  17. }

Spring boot傳統部署