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

SpringBoot之使用外置Servlet容器

程序员文章站 2022-05-23 17:37:12
...

一、SpringBoot嵌入式Servlet容器与外置Servlet容器的比较

  • 嵌入式Servlet容器应用将程序打成Jar包,外置Servlet容器应用将程序打成War包。
  • 嵌入式Servlet容器的优点:简单、便携。缺点:默认不支持JSP,优化定制较复杂。

 嵌入式Servlet容器优化定制的方法:

   ① 使用定制器:ServerProperties、自定义 EmbeddedServletContainerCustomizer
  ② 自己编写嵌入式Servlet容器的创建工厂 【EmbeddedServletContainerFactory】

二、如何使用外置Servlet容器的步骤

1、创建一个War包项目

;File——》New——Project,选中Spring Initializr,然后单击Next,进入到如下页面,将默认的Jar包改成War,然后Next,直到Finish。
SpringBoot之使用外置Servlet容器
创建出来的目录结构:
SpringBoot之使用外置Servlet容器
  由于创建的是War包项目,所以以上的目录结构还不完整,我们需要手动创建web.xml和webapp。有两种创建方式:

① 手动在src/main下创建webapp目录及在/src/main/webapp/WEB-INF下创建web.xml文件。

② 在IDEA的Project Structure中完善目录结构。
SpringBoot之使用外置Servlet容器

点击Module,选中web,来到如下页面:
SpringBoot之使用外置Servlet容器
双击上图圈出来的url处,弹出以下对话框:
SpringBoot之使用外置Servlet容器
单击OK,选择Yes。
SpringBoot之使用外置Servlet容器
还是在Project Structure页面,点击如下图所示的“+”,弹出如下对话框,修改生成web.xml文件的目录/src/main/webapp,然后点击OK。
SpringBoot之使用外置Servlet容器
在上一步中点击OK后,先点击Apply,然后选择OK,即可创建出webapp目录和web.xml文件。
SpringBoot之使用外置Servlet容器
完善后的目录结构如下:
SpringBoot之使用外置Servlet容器

2、将嵌入式的tomcat指定为provided

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-tomcat</artifactId>
     <scope>provided</scope>
</dependency>

3、必须写一个SpringBootServletInitializer 的实现子类。

  目的是调用configure方法,传入springboot应用的主程序。

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(SpringBootWebJspApplication.class);
    }
}

4、启动服务器

三、外部Servlet启动SpringBoot应用原理(1.5.x)

  • 将项目打成 jar包启动方式:执行SpringBoot主类的main方法,启动IOC容器,创建嵌入式的Servlet容器。
  • 将项目打成 war包启动方式:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,然后启动IOC容器。

  在Spring3.0(Spring注解版)的8.2.4 Shared libraries / runtimes pluggability中,有如下规则:

【1】在服务器(web应用)启动时,会创建当前web应用里每一个jar包中的ServletContainerInitializer实例。
【2】ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,必须有一个名为 javax.servlet.ServletContainerInitializer 的文件,内容就是ServletContainerInitializer的实现类的全类名
【3】还可以使用@HandlesTypes注解,作用是在应用启动的时候加载我们感兴趣的类

步骤:

【1】启动tomcat服务器;
【2】 在../org/springframework/spring-web/4.3.14.RELEASE/spring-web-4.3.14.RELEASE.jar/META-INF/services目录下有一个名为javax.servlet.ServletContainerInitializer的文件,文件内容是:

org.springframework.web.SpringServletContainerInitializer

相当于在web应用启动的时候启动SpringServletContainerInitializer
【3】SpringServletContainerInitializer@HandlesTypes(WebApplicationInitializer.class)标注的所有类型的类(感兴趣的类)都传入到onStartup方法的Set<Class<?>> webAppInitializerClasses,为这些WebApplicationInitializer类型的类(不是接口、不是抽象类)创建实例。
【4】每一个WebApplicationInitializer都调用自己的onStartup,相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法。WebApplicationInitializer是一个接口,它的实现类如下:
SpringBoot之使用外置Servlet容器


//Set<Class<?>> webAppInitializerClasses表示感兴趣的类
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
    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.add((WebApplicationInitializer)waiClass.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();

          while(var4.hasNext()) {
              WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
              //每一个WebApplicationInitializer对象调用自己的onStartup方法创建SpringBootServletInitializer对象
              initializer.onStartup(servletContext);
          }
      }
}
public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(SpringBootWebJspApplication.class);
    }

}

【5】SpringBootServletInitializer实例执行onStartup方法的时候会调用createRootApplicationContext方法创建容器

public void onStartup(ServletContext servletContext) throws ServletException {
        this.logger = LogFactory.getLog(this.getClass());
        WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
        if (rootAppContext != null) {
            servletContext.addListener(new ContextLoaderListener(rootAppContext) {
                public void contextInitialized(ServletContextEvent event) {
                }
            });
        } else {
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
        }

    }

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
//1、创建SpringApplicationBuilder对象
    SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
    StandardServletEnvironment environment = new StandardServletEnvironment();
    environment.initPropertySources(servletContext, (ServletConfig)null);
    builder.environment(environment);
    builder.main(this.getClass());
    ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
    if (parent != null) {
      this.logger.info("Root context already created (using as parent).");
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
      builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
     }

      builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
      builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
      //调用configure方法,在子类重写了这个方法,传入SpringBoot的主程序类
      builder = this.configure(builder);
      //使用builder创建spring应用
      SpringApplication application = builder.build();
      if (application.getSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
          application.getSources().add(this.getClass());
      }

      Assert.state(!application.getSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
      if (this.registerErrorPageFilter) {
         application.getSources().add(ErrorPageFilterConfiguration.class);
      }
      //启动spring应用
     return this.run(application);
}

protected SpringApplicationBuilder createSpringApplicationBuilder() {
    return new SpringApplicationBuilder(new Object[0]);
}

【6】启动spring应用,并创建IOC容器

 public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            new FailureAnalyzers(context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //刷新IOC容器
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            listeners.finished(context, (Throwable)null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
            throw new IllegalStateException(var9);
        }
    }

一句话总结:先启动Servlet容器,启动spring应用,再创建IOC容器。

相关标签: javaEE