一、使用内置的 Servlet 容器

  嵌入式 Servlet 容器:可以把应用打成可执行的 jar 包

    优点:简单、便捷;

    缺点:默认不支持JSP、优化定制比较复杂

    定制方法:

1、使用定制器 ServerProperties 进行配置;
2、自定义 EmbeddedServletContainerCustomizer 组件;
3、自己编写嵌入式Servlet容器的创建工厂 【EmbeddedServletContainerFactory】

  

  外置的Servlet容器:外面安装Tomcat---应用war包的方式打包;

 

二、使用外部 Servlet 容器

  1、使用Spring Initializr 创建一个 war 项目,利用 idea 创建好目录结构

 

     使用 idea 修改文件目录

    (1)指定 webapp 路径

      

    (2)指定 web.xml 的路径

      

 

     目录结构:

      

 

  2、编写测试代码

    页面请求:

<a href="/hello">hello</a>

  

    控制器方法:

@Controller
public class HelloController {

    @GetMapping(value = "/hello")
    public String hello(Model model) {
        model.addAttribute("msg", "你好哇!");
        return "success";
    }
}

 

    配置视图解析器:

spring.mvc.view.prefix=/WEB-INF/pages/
spring.mvc.view.suffix=.jsp

 

    目录:

    

 

  3、将嵌入式的Tomcat指定为provided

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

 

  4、必须编写一个SpringBootServletInitializer的子类,并调用configure方法

public class ServletInitializer extends SpringBootServletInitializer {

   @Override
   protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
       //传入SpringBoot应用的主程序
      return application.sources(SpringBoot04WebJspApplication.class);
   }

}

 

  5、启动服务器就可以使用了

 

三、外部 Servlet 容器启动原理

  1、回顾

    jar包:执行SpringBoot主类的 main方法,启动ioc容器,创建嵌入式的Servlet容器;

    war包:启动服务器,服务器启动SpringBoot应用SpringBootServletInitializer】,启动ioc容器;

  2、Servlet3.0 新特性

    8.2.4 Shared libraries / runtimes pluggability

    规则

    (1)服务器启动(web应用启动)会创建当前web应用里面每一个 jar 包里面ServletContainerInitializer实例;

    (2)ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为
javax.servlet.ServletContainerInitializer的文件,内容就是 ServletContainerInitializer 的实现类的全类名;

    (3)还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;

     参考文章:Servlet3.0 新特性之 ServletContainerInitializer

  3、流程

  (1)启动 Tomcat 容器

  (2)找每一个 jar 包下的 ServletContainerInitializer 实例:

 

   会在下面的目录中找到

org\springframework\spring-web\4.3.13.RELEASE\spring-web-4.3.13.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer

      Springweb模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer

    并且创建该类的实例

  (3)SpringServletContainerInitializer@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class>;为这些WebApplicationInitializer类型的类创建实例;

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    /**
     * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
     * implementations present on the application classpath.
     * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
     * Servlet 3.0+ containers will automatically scan the classpath for implementations
     * of Spring's {@code WebApplicationInitializer} interface and provide the set of all
     * such types to the {@code webAppInitializerClasses} parameter of this method.
     * <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
     * this method is effectively a no-op. An INFO-level log message will be issued notifying
     * the user that the {@code ServletContainerInitializer} has indeed been invoked but that
     * no {@code WebApplicationInitializer} implementations were found.
     * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
     * they will be instantiated (and <em>sorted</em> if the @{@link
     * org.springframework.core.annotation.Order @Order} annotation is present or
     * the {@link org.springframework.core.Ordered Ordered} interface has been
     * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
     * method will be invoked on each instance, delegating the {@code ServletContext} such
     * that each instance may register and configure servlets such as Spring's
     * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
     * or any other Servlet API componentry such as filters.
     * @param webAppInitializerClasses all implementations of
     * {@link WebApplicationInitializer} found on the application classpath
     * @param servletContext the servlet context to be initialized
     * @see WebApplicationInitializer#onStartup(ServletContext)
     * @see AnnotationAwareOrderComparator
     */
    @Override
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                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);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
        AnnotationAwareOrderComparator.sort(initializers);
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }

}

 

  (4)每一个WebApplicationInitializer都调用自己的onStartup

servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
    initializer.onStartup(servletContext);
}

 

    WebApplicationInitializer 接口的实现:

    

 

  (5)相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法

  (6)SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    // Logger initialization is deferred in case a ordered
    // LogServletContextInitializer is being used
    this.logger = LogFactory.getLog(getClass());
    //创建容器
    WebApplicationContext rootAppContext = createRootApplicationContext(
        servletContext);
    if (rootAppContext != null) {
        servletContext.addListener(new ContextLoaderListener(rootAppContext) {
            @Override
            public void contextInitialized(ServletContextEvent event) {
                // no-op because the application context is already initialized
            }
        });
    }
    else {
        this.logger.debug("No ContextLoaderListener registered, as "
                          + "createRootApplicationContext() did not "
                          + "return an application context");
    }
}

protected WebApplicationContext createRootApplicationContext(
      ServletContext servletContext) {
   //1、创建SpringApplicationBuilder
   SpringApplicationBuilder builder = createSpringApplicationBuilder();
   StandardServletEnvironment environment = new StandardServletEnvironment();
   environment.initPropertySources(servletContext, null);
   builder.environment(environment);
   builder.main(getClass());
   ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
   if (parent != null) {
      this.logger.info("Root context already created (using as parent).");
      servletContext.setAttribute(
            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
      builder.initializers(new ParentContextApplicationContextInitializer(parent));
   }
   builder.initializers(
         new ServletContextApplicationContextInitializer(servletContext));
   builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
    
    //调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
   builder = configure(builder);
    
    //使用builder创建一个Spring应用
   SpringApplication application = builder.build();
   if (application.getSources().isEmpty() && AnnotationUtils
         .findAnnotation(getClass(), Configuration.class) != null) {
      application.getSources().add(getClass());
   }
   Assert.state(!application.getSources().isEmpty(),
         "No SpringApplication sources have been defined. Either override the "
               + "configure method or add an @Configuration annotation");
   // Ensure error pages are registered
   if (this.registerErrorPageFilter) {
      application.getSources().add(ErrorPageFilterConfiguration.class);
   }
    //启动Spring应用
   return run(application);
}

 

  (7)Spring的应用就启动并且创建IOC容器

protected WebApplicationContext run(SpringApplication application) {
    return (WebApplicationContext) application.run();
}

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   FailureAnalyzers analyzers = null;
   configureHeadlessProperty();
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      Banner printedBanner = printBanner(environment);
      context = createApplicationContext();
      analyzers = new FailureAnalyzers(context);
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
       
       //刷新IOC容器
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      listeners.finished(context, null);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
      return context;
   }
   catch (Throwable ex) {
      handleRunFailure(context, listeners, analyzers, ex);
      throw new IllegalStateException(ex);
   }
}

    启动 servlet 容器,再启动 SpringBoot 应用

  4、总结

    (1)实现 SpringBootServletInitializer 重写 configure 方法;

    (2)SpringApplicationBuilder:builder.source(@SpringBootApplication 类);

    (3)启动原理

      ① Servlet3.0标准ServletContainerInitializer扫描所有jar包中METAINF/services/javax.servlet.ServletContainerInitializer文件指定的类并加载
      ② 加载spring web包下的SpringServletContainerInitializer;
      ③ 扫描@HandleType(WebApplicationInitializer);
      ④ 加载SpringBootServletInitializer并运行onStartup方法;
      ⑤ 加载@SpringBootApplication主类,启动容器等;

 

 

posted on 2021-12-12 15:17  格物致知_Tony  阅读(78)  评论(0编辑  收藏  举报