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主类,启动容器等;

     

  • 相关阅读:
    Jmeter_Beanshell_使用Java处理JSON块
    BeanShell Processor_使用Java处理脚本
    MySQL_explain关键字分析查询语句
    LoadRunner11_录制脚本时的浏览器版本
    Jmeter_实现Excel文件导出到本地
    Jmeter_录制HTTPS
    性能测试常用sql语句
    LoadRunner11_MySQL数据库脚本
    LoadRunner11_录制Oracle数据库脚本
    实现liunx之间无密码访问——ssh密匙
  • 原文地址:https://www.cnblogs.com/niujifei/p/15679383.html
Copyright © 2011-2022 走看看