zoukankan      html  css  js  c++  java
  • 第四章:(8)Web开发 之 使用外置 Servlet 容器

    一、使用内置的 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主类,启动容器等;

     

  • 相关阅读:
    Network (poj1144)
    C. Hongcow Builds A Nation
    ZYB loves Xor I(hud5269)
    D. Chloe and pleasant prizes
    Game(hdu5218)
    约瑟夫环的递推方法
    Misaki's Kiss again(hdu5175)
    Exploration(hdu5222)
    B. Arpa's weak amphitheater and Mehrdad's valuable Hoses
    C. Arpa's loud Owf and Mehrdad's evil plan
  • 原文地址:https://www.cnblogs.com/niujifei/p/15679383.html
Copyright © 2011-2022 走看看