1. 程式人生 > >十一、SpringBoot之使用外接的Servlet容器及啟動原理

十一、SpringBoot之使用外接的Servlet容器及啟動原理

一、嵌入式和外接Servlet容器對比

嵌入式Servlet容器:應用打成可執行的jar

​優點:簡單、便攜;

​缺點:預設不支援JSP、優化定製比較複雜;

外接的Servlet容器:外面安裝Tomcat---應用war包的方式打包;

二、使用外接的Servlet容器步驟

1、必須建立一個war專案;(利用idea建立好目錄結構)

2、生成web目錄和web.xml

3.配置和啟動tomcat

4.將嵌入式的Tomcat指定為provided;

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

5.必須編寫一個SpringBootServletInitializer的子類,並呼叫configure方法

public class ServletInitializer extends SpringBootServletInitializer {

   @Override
   protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
       //傳入SpringBoot應用的主程式
      return application.sources(SpringBoot04WebJspApplication.class);
   }

}

6.啟動伺服器就可以使用;

controller

@Controller
public class HelloController {
    @GetMapping("/abc")
    public  String hello(Model model){
        model.addAttribute("msg","你好");
        return "success";
    }
}

application.properties

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

JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>Hello JSP</h1>
    <a href="abc">abc</a>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>SUCCESS</h1>
    <h3>${msg}</h3>
</body>
</html>

三、外接Servlet啟動原理

jar包啟動步驟:執行SpringBoot主類的main方法,啟動ioc容器,建立嵌入式的Servlet容器;

war包啟動步驟:啟動伺服器,伺服器啟動SpringBoot應用【SpringBootServletInitializer】,啟動ioc容器;

servlet3.0的8.2.4 Shared libraries / runtimes pluggability章節定義了一個規則

1、規則:

  • ​1.伺服器啟動(web應用啟動)會建立當前web應用裡面每一個jar包裡面ServletContainerInitializer例項;
  • 2.ServletContainerInitializer的實現放在jar包的META-INF/services資料夾下,有一個名為javax.servlet.ServletContainerInitializer的檔案,內容就是ServletContainerInitializer的實現類的全類名
  • 3.還可以使用@HandlesTypes,在應用啟動的時候載入我們感興趣的類;

2、流程:啟動Servlet容器,再啟動SpringBoot應用

  • 1.啟動Tomcat
  • 2.應用啟動ServletContainerInitializer

Spring的web模組裡面有這個檔案:org.springframework.web.SpringServletContainerInitializer

  • 3.為這些WebApplicationInitializer型別的類建立例項;
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }
    
    //SpringServletContainerInitializer將@HandlesTypes(WebApplicationInitializer.class)
   //標註的所有這個型別的類都傳入到onStartup方法的Set<Class<?>>集合裡面,
    public void onStartup(@Nullable 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)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).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();
                //每一個例項呼叫自己的onStartup()方法
                initializer.onStartup(servletContext);
            }

        }
    }
}
  • 4.每一個WebApplicationInitializer都呼叫自己的onStartup;

  • 5.相當於我們的SpringBootServletInitializer的類會被建立物件,並執行onStartup方法
  • 6.SpringBootServletInitializer例項執行onStartup的時候會createRootApplicationContext;建立容器
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {

    public void onStartup(ServletContext servletContext) throws ServletException {
        this.logger = LogFactory.getLog(this.getClass());
        //createRootApplicationContext;建立容器
        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();
        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(AnnotationConfigServletWebServerApplicationContext.class);
        //呼叫configure方法,子類重寫了這個方法,將SpringBoot的主程式類傳入了進來
        builder = this.configure(builder);
        builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
        //使用builder建立一個spring應用
        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
            application.addPrimarySources(Collections.singleton(this.getClass()));
        }

        Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
        if (this.registerErrorPageFilter) {
            application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
        }
        //啟動Spring應用
        return this.run(application);
    }
  • 7.Spring的應用就啟動並且建立IOKOC容器
 public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //重新整理IOC容器
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }