• Chapter5: SpringBoot与Web开发2


    接上一篇 Chapter4: SpringBoot与Web开发1

    10. 配置嵌入式Servlet容器

    SpringBoot默认采用Tomcat作为嵌入的Servlet容器;查看pom.xml的Diagram依赖图:
    在这里插入图片描述
    那么如何定制和修改Servlet容器的相关配置? 下面给出实操方案。

    10.1 application.properties配置

    SpringBoot全局配置文件中修改与Server相关的配置属性都封装在ServerProperties类中。

    @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
    public class ServerProperties
    		implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
    
    	/**
    	 * Server HTTP port.
    	 */
    	private Integer port;
    
    	/**
    	 * Network address to which the server should bind to.
    	 */
    	private InetAddress address;
    
    	/**
    	 * Context path of the application.
    	 */
    	private String contextPath;
    
    	/**
    	 * Display name of the application.
    	 */
    	private String displayName = "application";
    
    	@NestedConfigurationProperty
    	private ErrorProperties error = new ErrorProperties();
    
    	/**
    	 * Path of the main dispatcher servlet.
    	 */
    	private String servletPath = "/";
        
        // ...
        
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    修改配置属性

    server.port=8082
    ##springboot 2.0之后,配置为 server.servlet.context-path
    #server.servlet.context-path=/boot1
    #springboot 2.0之前,配置为 server.context-path
    server.context-path=/boot1
    # 通用的Servlet容器配置
    #server.xxx
    #tomcat的配置
    # server.tomcat.xxx
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    10.2 Java代码方式配置

    编写一个嵌入式Servlet容器定制器EmbeddedServletContainerCustomizer的实现类,来修改Servlet容器的配置。ConfigurableEmbeddedServletContainer提供了setXXX进行配置属性注入.

    // 配置嵌入式的Servlet容器定制器
    @Bean
    public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
    
        return new EmbeddedServletContainerCustomizer() {
            // 定制嵌入式的Servlet容器相关的规则
            @Override
            public void customize(ConfigurableEmbeddedServletContainer container) {
                container.setPort(8083);
            }
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    重启应用,从启动日志中查看,应用是从8083端口启动的。
    在这里插入图片描述

    10.3 注册Servlet三大Web组件

    Web的三大组件: Servlet、Filter、Listener

    由于SpringBoot默认是以jar包方式的嵌入式Servlet容器来启动SpringBoot的Web应用,没有web.xml文件配置。SpringBoot提供了ServletRegistrationBean、FilterRegistrationBean和ServletListenerRegistrationBean来实现三大组件的注册。
    在这里插入图片描述

    (1)注册Servlet

    首先自定义Servlet

    public class MyServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.getWriter().println("hello, my Servlet");
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.getWriter().println("hello, my Servlet");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    再使用ServletRegistrationBean注册自定义的servlet,并设置匹配的uri.

    @Configuration
    public class MyServerConfig {
        // 注册三大组件
        // 注册servlet
        @Bean
        public ServletRegistrationBean myServlet() {
            ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new MyServlet(), "/myServlet");
            System.out.println("regist servlet");
            return servletRegistrationBean;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    启动应用后,浏览器访问 /myServlet
    在这里插入图片描述
    (2)注册Filter

    创建自定义Filter

    public class MyFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("init my filter");
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("my filter process...");
            // 放行
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        @Override
        public void destroy() {
            System.out.println("destory my filter");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    FilterRegistrationBean注册自定义Filter

    @Configuration
    public class MyServerConfig {
       // ....
        // 注册filter
        @Bean
        public FilterRegistrationBean myFilter() {
            // 启动类上加 @ServletComponentScan注解,会扫描到@WebFilter注解标识的filter类 MyWebFilter; filter实例名称如果重复只会有一个生效
            FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
            filterRegistrationBean.setFilter(new MyFilter());
            // 拦截指定请求
            filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello", "/myServlet")); 
            return filterRegistrationBean;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    测试过滤器,查看启动日志和请求/hello日志
    在这里插入图片描述
    在这里插入图片描述
    查看销毁日志
    在这里插入图片描述

    (3)注册Listener

    创建自定义Listener

    public class MyListener implements ServletContextListener {
    
        @Override
        public void contextInitialized(ServletContextEvent servletContextEvent) {
            System.out.println("MyListener.contextInitialized...web启动");
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent servletContextEvent) {
            System.out.println("MyListener.contextDestroyed....web销毁");
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ServletListenerRegistrationBean注册自定义Listener

    @Configuration
    public class MyServerConfig {
       //  ...
        // 注册listener
        @Bean
        public ServletListenerRegistrationBean myListener() {
            ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();
            servletListenerRegistrationBean.setListener(new MyListener());
            return servletListenerRegistrationBean;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    测试Listener,查看启动日志
    在这里插入图片描述
    查看销毁日志
    在这里插入图片描述
    SpringBoot自动配置SpringMVC组件时,其中自动注册的SpringMVC前端控制器(dispatcherServlet)使用了ServletRegistrationBean去注册。

    DispatcherServletAutoConfiguration查看源码:

    / 默认拦截所有请求, 包括静态资源,但是不拦截jsp请求; /*会拦截jsp请求。

    可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的uri.

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public ServletRegistrationBean dispatcherServletRegistration(
    		DispatcherServlet dispatcherServlet) {
        // 注册dispatcherServlet
    	ServletRegistrationBean registration = new ServletRegistrationBean(
            									// 设置拦截的uri使用通配符匹配 / /*
    			dispatcherServlet, this.serverProperties.getServletMapping());
    	registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
    	registration.setLoadOnStartup(
    			this.webMvcProperties.getServlet().getLoadOnStartup());
    	if (this.multipartConfig != null) {
    		registration.setMultipartConfig(this.multipartConfig);
    	}
    	return registration;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    也可以使用注解注册三大Web组:@WebServlet, @WebFilter, @WebListener.

    10.4 支持其他Servlet容器

    上面提到SpringBoot默认支持的嵌入式Servlet容器是tomcat, 也就是只要你引入了Web模块, 就会使用tomcat容器。SpringBoot也支持其他Servlet容器, 比如Jetty, Undertow。
    在这里插入图片描述
    接口ConfigurableEmbeddedServletContainer提供了支持tomcat, jetty,undertow的实现。
    在这里插入图片描述

    (1)改造成Jetty容器

    首先要排除tomcat的依赖, 再引入Jetty的启动器依赖。

    <dependency>
    	<groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-webartifactId>
    	<exclusions>
    		<exclusion>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-tomcatartifactId>
    		exclusion>
    	exclusions>
    dependency>
    
    
    <dependency>
    	<groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-jettyartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    查看启动日志, 启动的容器是Jetty.

    [2023-05-18 22:00:30.030] [org.eclipse.jetty.server.handler.ContextHandler$Context] [main] [2221] [INFO ] Initializing Spring FrameworkServlet 'dispatcherServlet'
    [2023-05-18 22:00:30.030] [org.springframework.web.servlet.FrameworkServlet] [main] [489] [INFO ] FrameworkServlet 'dispatcherServlet': initialization started
    [2023-05-18 22:00:30.030] [org.springframework.web.servlet.FrameworkServlet] [main] [508] [INFO ] FrameworkServlet 'dispatcherServlet': initialization completed in 7 ms
    [2023-05-18 22:00:30.030] [org.eclipse.jetty.server.AbstractConnector] [main] [288] [INFO ] Started ServerConnector@53125718{HTTP/1.1,[http/1.1]}{0.0.0.0:8082}
    [2023-05-18 22:00:30.030] [org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainer] [main] [155] [INFO ] Jetty started on port(s) 8082 (http/1.1) // 启动Jetty容器
    [2023-05-18 22:00:30.030] [org.springframework.boot.StartupInfoLogger] [main] [57] [INFO ] Started SpringBoot02ConfigApplication in 4.428 seconds (JVM running for 5.0)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2)改造成undertow容器

    排除tomcat的依赖, 再引入undertow的启动器依赖。

    <dependency>
    	<groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-webartifactId>
    	<exclusions>
    		<exclusion>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-tomcatartifactId>
    		exclusion>
    	exclusions>
    dependency>
    
    
    <dependency>
    	<groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-undertowartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    查看启动日志

    [2023-05-18 22:05:21.021] [org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainer] [main] [160] [INFO ] Undertow started on port(s) 8082 (http) // 启动undertow容器
    [2023-05-18 22:05:21.021] [org.springframework.boot.StartupInfoLogger] [main] [57] [INFO ] Started SpringBoot02ConfigApplication in 4.293 seconds (JVM running for 4.863)
    
    • 1
    • 2

    10.5 嵌入式Servlet容器自动配置原理

    步骤:

    • SpringBoot根据导入的依赖情况,自动配置类给容器中添加相应的Servlet容器工厂EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】, 获取对应的servlet容器EmbeddedServletContainer【TomcatEmbeddedServletContainer】

    • 容器中某个组件要创建对象就会惊动后置处理器进行初始化工作EmbeddedServletContainerCustomizerBeanPostProcessor 只要是嵌入式的Servlet容器工厂,后置处理器就会工作;

    • 在后置处理器中,从容器中获取所有的EmbeddedServletContainerCustomizer定制器,调用定制器的customize定制方法设置配置属性, ServerProperties#customize设置servlet相关配置.

    10.5.1 定制器配置

    嵌入式Servlet容器自动配置类 EmbeddedServletContainerAutoConfiguration
    导入BeanPostProcessorsRegistrar 给容器中导入一些组件;在其registerBeanDefinitions方法中注册了EmbeddedServletContainerCustomizerBeanPostProcessor后置处理器,用于bean初始化前后执行指定工作;

    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @Configuration
    @ConditionalOnWebApplication
    @Import(BeanPostProcessorsRegistrar.class) // Bean后置处理器注册器
    public class EmbeddedServletContainerAutoConfiguration {
    
    	// ....
    	public static class BeanPostProcessorsRegistrar
    			implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
    
    		private ConfigurableListableBeanFactory beanFactory;
    
    		@Override
    		public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    			if (beanFactory instanceof ConfigurableListableBeanFactory) {
    				this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    			}
    		}
    
    		@Override
    		public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
    				BeanDefinitionRegistry registry) {
    			if (this.beanFactory == null) {
    				return;
    			}
                // 注册嵌入式Servlet容器定制器Bean的后置处理器, 在定制器实例化过程中进行初始化工作.
    			registerSyntheticBeanIfMissing(registry,
    					"embeddedServletContainerCustomizerBeanPostProcessor",
    					EmbeddedServletContainerCustomizerBeanPostProcessor.class);
    			registerSyntheticBeanIfMissing(registry,
    					"errorPageRegistrarBeanPostProcessor",
    					ErrorPageRegistrarBeanPostProcessor.class);
    		}
    
    		private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
    				String name, Class<?> beanClass) {
    			if (ObjectUtils.isEmpty(
    					this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
    				RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
    				beanDefinition.setSynthetic(true);
    				registry.registerBeanDefinition(name, beanDefinition);
    			}
    		}
    
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    嵌入式Servlet容器定制器后置处理器的初始化方法如下:

    public class EmbeddedServletContainerCustomizerBeanPostProcessor
    		implements BeanPostProcessor, BeanFactoryAware {
    
    	private ListableBeanFactory beanFactory;
    
    	private List<EmbeddedServletContainerCustomizer> customizers;
    
    	@Override
    	public void setBeanFactory(BeanFactory beanFactory) {
    		Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
    				"EmbeddedServletContainerCustomizerBeanPostProcessor can only be used "
    						+ "with a ListableBeanFactory");
    		this.beanFactory = (ListableBeanFactory) beanFactory;
    	}
    
        // 之前处理
    	@Override
    	public Object postProcessBeforeInitialization(Object bean, String beanName)
    			throws BeansException {
            // 如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件
    		if (bean instanceof ConfigurableEmbeddedServletContainer) {
                // 获取所有的定制器,调用每一个定制器的customize方法来给嵌入式Servlet容器进行属性赋值。
    			postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
    		}
    		return bean;
    	}
    
    	@Override
    	public Object postProcessAfterInitialization(Object bean, String beanName)
    			throws BeansException {
    		return bean;
    	}
    
    	private void postProcessBeforeInitialization(
    			ConfigurableEmbeddedServletContainer bean) {
            // 获取所有的定制器,调用每一个定制器的customize方法来给嵌入式Servlet容器进行属性赋值。
    		for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
    			customizer.customize(bean);
    		}
    	}
    
    	private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
    		if (this.customizers == null) {
    			// Look up does not include the parent context
    			this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
    					this.beanFactory
                    // 从容器中获取所有这种类型的组件:EmbeddedServletContainerCustomizer
                    // 定制Servlet容器,可以给容器中添加一个EmbeddedServletContainerCustomizer类型的组件。
    							.getBeansOfType(EmbeddedServletContainerCustomizer.class,
    									false, false)
    							.values());
    			Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
    			this.customizers = Collections.unmodifiableList(this.customizers);
    		}
    		return this.customizers;
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    嵌入式servlet容器定制器接口提供了customize方法给我们进行定制化处理, 帮助我们修改Servlet容器的配置。

    public interface EmbeddedServletContainerCustomizer {
    
    	/**
    	 * Customize the specified {@link ConfigurableEmbeddedServletContainer}.
    	 * @param container the container to customize
    	 */
    	void customize(ConfigurableEmbeddedServletContainer container);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    那么我们对嵌入Servlet式容器的配置修改是怎么生效呢?

    嵌入式的Servlet容器配置修改通过ServerProperties实现,ServerProperties也是定制器(它实现了嵌入式Servlet容器定制器接口EmbeddedServletContainerCustomizer)。
    在这里插入图片描述
    在application.properties文件中修改server.xxx配置时,就是通过定制器的customize方法进行定制化处理,从而达到修改Servlet容器的配置效果。

    @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
    public class ServerProperties
    		implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
    
    	/**
    	 * Server HTTP port.
    	 */
    	private Integer port;
    
    	/**
    	 * Network address to which the server should bind to.
    	 */
    	private InetAddress address;
    
    	/**
    	 * Context path of the application.
    	 */
    	private String contextPath;
        // ServerProperties#customize 修改servlet容器配置
        @Override
    	public void customize(ConfigurableEmbeddedServletContainer container) {
    		if (getPort() != null) {
    			container.setPort(getPort());
    		}
    		if (getAddress() != null) {
    			container.setAddress(getAddress());
    		}
    		if (getContextPath() != null) {
    			container.setContextPath(getContextPath());
    		}
    		if (getDisplayName() != null) {
    			container.setDisplayName(getDisplayName());
    		}
    		if (getSession().getTimeout() != null) {
    			container.setSessionTimeout(getSession().getTimeout());
    		}
    		container.setPersistSession(getSession().isPersistent());
    		container.setSessionStoreDir(getSession().getStoreDir());
    		if (getSsl() != null) {
    			container.setSsl(getSsl());
    		}
    		if (getJspServlet() != null) {
    			container.setJspServlet(getJspServlet());
    		}
    		if (getCompression() != null) {
    			container.setCompression(getCompression());
    		}
    		container.setServerHeader(getServerHeader());
    		if (container instanceof TomcatEmbeddedServletContainerFactory) {
    			getTomcat().customizeTomcat(this,
    					(TomcatEmbeddedServletContainerFactory) container);
    		}
    		if (container instanceof JettyEmbeddedServletContainerFactory) {
    			getJetty().customizeJetty(this,
    					(JettyEmbeddedServletContainerFactory) container);
    		}
    
    		if (container instanceof UndertowEmbeddedServletContainerFactory) {
    			getUndertow().customizeUndertow(this,
    					(UndertowEmbeddedServletContainerFactory) container);
    		}
    		container.addInitializers(new SessionConfiguringInitializer(this.session));
    		container.addInitializers(new InitParameterConfiguringServletContextInitializer(
    				getContextParameters()));
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    自定义嵌入式Servlet容器定制器配置

    @Configuration
    public class MyServerConfig {
    	// 配置嵌入式的Servlet容器
        @Bean
        public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
    
            return new EmbeddedServletContainerCustomizer() {
                // 定制嵌入式的Servlet容器相关的规则
                @Override
                public void customize(ConfigurableEmbeddedServletContainer container) {
                    // 修改Servlet容器http端口
                    container.setPort(8082);
                }
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    10.5.2 嵌入式Servlet容器初始化(自动启动原理)

    EmbeddedServletContainerFactory 嵌入式Servlet容器工厂提供了创建嵌入式Servlet容器的方法。

    public interface EmbeddedServletContainerFactory {
    
    	/**
    	 * Gets a new fully configured but paused {@link EmbeddedServletContainer} instance.
    	 * Clients should not be able to connect to the returned server until
    	 * {@link EmbeddedServletContainer#start()} is called (which happens when the
    	 * {@link ApplicationContext} has been fully refreshed).
    	 * @param initializers {@link ServletContextInitializer}s that should be applied as
    	 * the container starts
    	 * @return a fully configured and started {@link EmbeddedServletContainer}
    	 * @see EmbeddedServletContainer#stop()
    	 *  获取嵌入式Servlet容器
    	 */
    	EmbeddedServletContainer getEmbeddedServletContainer(
    			ServletContextInitializer... initializers);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    嵌入式Servlet容器工厂接口提供了3个实现类,支持tomcat、Jetty和Undertow容器。
    在这里插入图片描述
    EmbeddedServletContainer 嵌入式Servlet容器
    在这里插入图片描述
    EmbeddedServletContainer容器提供了启动和停止Servlet容器的方法。

    public interface EmbeddedServletContainer {
    
    	/**
    	 * Starts the embedded servlet container. Calling this method on an already started
    	 * container has no effect.
    	 * @throws EmbeddedServletContainerException if the container cannot be started
    	 */
    	void start() throws EmbeddedServletContainerException;
    
    	/**
    	 * Stops the embedded servlet container. Calling this method on an already stopped
    	 * container has no effect.
    	 * @throws EmbeddedServletContainerException if the container cannot be stopped
    	 */
    	void stop() throws EmbeddedServletContainerException;
    
    	/**
    	 * Return the port this server is listening on.
    	 * @return the port (or -1 if none)
    	 */
    	int getPort();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    思考: 什么时候创建嵌入式Servlet容器工厂?什么时候获取嵌入式Servlet容器并启动Tomcat?

    嵌入式Servlet容器初始化步骤(以tomcat容器为例):

    1)SpringBoot应用启动运行run方法, 创建IOC容器对象.

    // org.springframework.boot.SpringApplication#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);
            // 创建IOC容器
    		context = createApplicationContext();
    		analyzers = new FailureAnalyzers(context);
    		prepareContext(context, environment, listeners, applicationArguments,
    				printedBanner);
            // 刷新IOC容器【初始化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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    如果是Web应用创建AnnotationConfigEmbeddedWebApplicationContext容器;

    如果不是Web应用创建AnnotationConfigApplicationContext容器。

    public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
    			+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
    
    public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
    			+ "annotation.AnnotationConfigApplicationContext";
    // org.springframework.boot.SpringApplication#createApplicationContext方法
    protected ConfigurableApplicationContext createApplicationContext() {
    	Class<?> contextClass = this.applicationContextClass;
    	if (contextClass == null) {
    		try {
                
    			contextClass = Class.forName(this.webEnvironment
                          // AnnotationConfigEmbeddedWebApplicationContext
    					? DEFAULT_WEB_CONTEXT_CLASS : 
    					  // AnnotationConfigApplicationContext                              
                                             DEFAULT_CONTEXT_CLASS);
    		}
    		catch (ClassNotFoundException ex) {
    			throw new IllegalStateException(
    					"Unable create a default ApplicationContext, "
    							+ "please specify an ApplicationContextClass",
    					ex);
    		}
    	}
        //  返回IOC容器对象
    	return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2)refreshContext(context): SpringBoot刷新IOC容器【初始化IOC容器,实例化容器中的每一个组件】;

    // org.springframework.boot.SpringApplication#refreshContext方法
    private void refreshContext(ConfigurableApplicationContext context) {
        // refresh(context) 刷新刚才创建好的IOC容器;
    	refresh(context);
    	if (this.registerShutdownHook) {
    		try {
    			context.registerShutdownHook();
    		}
    		catch (AccessControlException ex) {
    			// Not allowed in some environments.
    		}
    	}
    }
    
    protected void refresh(ApplicationContext applicationContext) {
    	Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        // AbstractApplicationContext#refresh方法
    	((AbstractApplicationContext) applicationContext).refresh();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    (3)AbstractApplicationContext#refresh()中, onRefresh()实现获取嵌入式Servlet容器。

    // org.springframework.context.support.AbstractApplicationContext#refresh方法
    @Override
    public void refresh() throws BeansException, IllegalStateException {
    	synchronized (this.startupShutdownMonitor) {
    		// Prepare this context for refreshing.
    		prepareRefresh();
    
    		// Tell the subclass to refresh the internal bean factory.
    		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    		// Prepare the bean factory for use in this context.
    		prepareBeanFactory(beanFactory);
    
    		try {
    			// Allows post-processing of the bean factory in context subclasses.
    			postProcessBeanFactory(beanFactory);
    
    			// Invoke factory processors registered as beans in the context.
    			invokeBeanFactoryPostProcessors(beanFactory);
    
    			// Register bean processors that intercept bean creation.
    			registerBeanPostProcessors(beanFactory);
    
    			// Initialize message source for this context.
    			initMessageSource();
    
    			// Initialize event multicaster for this context.
    			initApplicationEventMulticaster();
    
    			// Initialize other special beans in specific context subclasses.
                // AbstractApplicationContext#onRefresh中实现获取嵌入式Servlet容器
    			onRefresh();
    
    			// Check for listener beans and register them.
    			registerListeners();
    
    			// Instantiate all remaining (non-lazy-init) singletons.
                // 启动嵌入式Servlet容器后,再将IOC容器中剩下没有创建的组件进行bean实例化。
    			finishBeanFactoryInitialization(beanFactory);
    
    			// Last step: publish corresponding event.
    			finishRefresh();
    		}
    
    		catch (BeansException ex) {
    			if (logger.isWarnEnabled()) {
    				logger.warn("Exception encountered during context initialization - " +
    						"cancelling refresh attempt: " + ex);
    			}
    
    			// Destroy already created singletons to avoid dangling resources.
    			destroyBeans();
    
    			// Reset 'active' flag.
    			cancelRefresh(ex);
    
    			// Propagate exception to caller.
    			throw ex;
    		}
    
    		finally {
    			// Reset common introspection caches in Spring's core, since we
    			// might not ever need metadata for singleton beans anymore...
    			resetCommonCaches();
    		}
    	}
    }
    
    // AbstractApplicationContext#onRefresh()方法,在子类IOC容器中重写onRefresh()
    protected void onRefresh() throws BeansException {
    	// For subclasses: do nothing by default.
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    (4)onRefresh() , IOC容器EmbeddedWebApplicationContext重写了onRefresh()方法,实现创建嵌入式Servlet容器。

    @Override
    protected void onRefresh() {
    	super.onRefresh();
    	try {
            // 创建嵌入式Servlet容器
    		createEmbeddedServletContainer();
    	}
    	catch (Throwable ex) {
    		throw new ApplicationContextException("Unable to start embedded container",
    				ex);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    5)web的IOC容器会创建嵌入式的Servlet容器工厂; 从IOC容器中获取EmbeddedServletContainerFactory容器工厂组件;

    // EmbeddedWebApplicationContext#createEmbeddedServletContainer()方法
    private void createEmbeddedServletContainer() {
    	EmbeddedServletContainer localContainer = this.embeddedServletContainer;
    	ServletContext localServletContext = getServletContext();
    	if (localContainer == null && localServletContext == null) {
            // 获取嵌入式Servlet容器工厂
    		EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
            // 获取嵌入式Servlet容器,并启动嵌入式Servlet容器(tomcat)
    		this.embeddedServletContainer = containerFactory
    				.getEmbeddedServletContainer(getSelfInitializer());
    	}
    	else if (localServletContext != null) {
    		try {
    			getSelfInitializer().onStartup(localServletContext);
    		}
    		catch (ServletException ex) {
    			throw new ApplicationContextException("Cannot initialize servlet context",
    					ex);
    		}
    	}
    	initPropertySources();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    getEmbeddedServletContainerFactory()方法获取IOC容器中的嵌入式Servlet容器工厂对象。

    // EmbeddedWebApplicationContext#getEmbeddedServletContainerFactory()方法
    protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
    	// Use bean names so that we don't consider the hierarchy
    	String[] beanNames = getBeanFactory()
            // 从Bean工厂中获取嵌入式servlet容器工厂bean名称
    			.getBeanNamesForType(EmbeddedServletContainerFactory.class);
    	if (beanNames.length == 0) {
    		throw new ApplicationContextException(
    				"Unable to start EmbeddedWebApplicationContext due to missing "
    						+ "EmbeddedServletContainerFactory bean.");
    	}
    	if (beanNames.length > 1) {
    		throw new ApplicationContextException(
    				"Unable to start EmbeddedWebApplicationContext due to multiple "
    						+ "EmbeddedServletContainerFactory beans : "
    						+ StringUtils.arrayToCommaDelimitedString(beanNames));
    	}
        //从IOC容器中获取并返回嵌入式Servlet容器工厂实例对象
    	return getBeanFactory().getBean(beanNames[0],
    			EmbeddedServletContainerFactory.class);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    那么IOC容器中的嵌入式Servlet容器工厂实例对象是什么时候实例化的呢?

    在Servlet容器自动配置类EmbeddedServletContainerAutoConfiguration中有实例化嵌入式Servlet容器工厂的方法,比如 TomcatEmbeddedServletContainerFactory

    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @Configuration
    @ConditionalOnWebApplication
    @Import(BeanPostProcessorsRegistrar.class)
    public class EmbeddedServletContainerAutoConfiguration {
    
    	/**
    	 * Nested configuration if Tomcat is being used.
    	 */
    	@Configuration
    	@ConditionalOnClass({ Servlet.class, Tomcat.class })
    	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    	public static class EmbeddedTomcat {
    		// 当容器中没有EmbeddedServletContainerFactory实例时就创建Servlet容器工厂。
    		@Bean
    		public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
    			return new TomcatEmbeddedServletContainerFactory();
    		}
    
    	}
    }	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    (6)BeanPostProcessorsRegistrar注册后置处理器 EmbeddedServletContainerCustomizerBeanPostProcessor

    TomcatEmbeddedServletContainerFactory容器工厂对象创建,后置处理器发现是嵌入式Servlet容器工厂,就获取所有的定制器先定制Servlet容器的相关配置。

    // EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar
    // BeanPostProcessorsRegistrar#registerBeanDefinitions
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
    		BeanDefinitionRegistry registry) {
    	if (this.beanFactory == null) {
    		return;
    	}
    	registerSyntheticBeanIfMissing(registry,
    			// 注册嵌入式servlet容器定制器后置处理器                                   
    			"embeddedServletContainerCustomizerBeanPostProcessor",
    			EmbeddedServletContainerCustomizerBeanPostProcessor.class);
    	registerSyntheticBeanIfMissing(registry,
    			"errorPageRegistrarBeanPostProcessor",
    			ErrorPageRegistrarBeanPostProcessor.class);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在后置处理器的前置方法中判断,如果是嵌入式Servlet容器对象就初始化Servlet容器。通过获取所有Servlet容器定制器,调用其定制方法初始化Servlet容器属性配置。

    public class EmbeddedServletContainerCustomizerBeanPostProcessor
    		implements BeanPostProcessor, BeanFactoryAware {
    
    	private ListableBeanFactory beanFactory;
    
    	private List<EmbeddedServletContainerCustomizer> customizers;
    
    	@Override
    	public void setBeanFactory(BeanFactory beanFactory) {
    		Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
    				"EmbeddedServletContainerCustomizerBeanPostProcessor can only be used "
    						+ "with a ListableBeanFactory");
    		this.beanFactory = (ListableBeanFactory) beanFactory;
    	}
    
    	@Override
    	public Object postProcessBeforeInitialization(Object bean, String beanName)
    			throws BeansException {
            // 如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件
    		if (bean instanceof ConfigurableEmbeddedServletContainer) {
                // 获取所有的定制器,调用每一个定制器的customize方法来给嵌入式Servlet容器进行属性赋值。
    			postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
    		}
    		return bean;
    	}
    
    	@Override
    	public Object postProcessAfterInitialization(Object bean, String beanName)
    			throws BeansException {
    		return bean;
    	}
    
    	private void postProcessBeforeInitialization(
    			ConfigurableEmbeddedServletContainer bean) {
            // 获取所有的定制器,调用每一个定制器的customize方法来给嵌入式Servlet容器进行属性赋值。
    		for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
    			customizer.customize(bean);
    		}
    	}
    
    	private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
    		if (this.customizers == null) {
    			// Look up does not include the parent context
    			this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
    					this.beanFactory
                    // 从容器中获取所有这种类型的组件:EmbeddedServletContainerCustomizer
                    // 定制Servlet容器,可以给容器中添加一个EmbeddedServletContainerCustomizer类型的组件。
    							.getBeansOfType(EmbeddedServletContainerCustomizer.class,
    									false, false)
    							.values());
    			Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
    			this.customizers = Collections.unmodifiableList(this.customizers);
    		}
    		return this.customizers;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    (7) 通过嵌入式Servlet容器工厂获取嵌入式Servlet容器对象(默认 tomcat)

    EmbeddedWebApplicationContext#createEmbeddedServletContainer()方法中containerFactory.getEmbeddedServletContainer(getSelfInitializer()); 实际调用的是子类的实现。
    在这里插入图片描述
    (8)获取tomcat容器并启动tomcat

    在嵌入式Servlet容器工厂TomcatEmbeddedServletContainerFactory中,继承了EmbeddedServletContainerFactory接口的getEmbeddedServletContainer方法,可以获取tomcat容器。

    public class TomcatEmbeddedServletContainerFactory
    		extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
    	// 获取tomcat容器工厂
    	@Override
    	public EmbeddedServletContainer getEmbeddedServletContainer(
    			ServletContextInitializer... initializers) {
            // 创建一个tomcat对象
    		Tomcat tomcat = new Tomcat();
    		File baseDir = (this.baseDirectory != null ? this.baseDirectory
    				: createTempDir("tomcat"));
            // 配置tomcat容器的基本信息
    		tomcat.setBaseDir(baseDir.getAbsolutePath());
    		Connector connector = new Connector(this.protocol);
    		tomcat.getService().addConnector(connector);
    		customizeConnector(connector);
    		tomcat.setConnector(connector);
    		tomcat.getHost().setAutoDeploy(false);
    		configureEngine(tomcat.getEngine());
    		for (Connector additionalConnector : this.additionalTomcatConnectors) {
    			tomcat.getService().addConnector(additionalConnector);
    		}
    		prepareContext(tomcat.getHost(), initializers);
            // 将配置好的tomcat传进去,返回一个嵌入式Servlet容器 (tomcat)
    		return getTomcatEmbeddedServletContainer(tomcat);
    	}
    	
    	protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
    			Tomcat tomcat) {
            // 返回一个嵌入式Servlet容器 (tomcat)
    		return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
    	}
    	
    	public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
    		Assert.notNull(tomcat, "Tomcat Server must not be null");
    		this.tomcat = tomcat;
    		this.autoStart = autoStart;
            // 初始化tomcat
    		initialize();
    	}
    	
    	private void initialize() throws EmbeddedServletContainerException {
    		TomcatEmbeddedServletContainer.logger
    				.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    		synchronized (this.monitor) {
    			try {
    				addInstanceIdToEngineName();
    				try {
    					// Remove service connectors to that protocol binding doesn't happen
    					// yet
    					removeServiceConnectors();
    
    					// Start the server to trigger initialization listeners
                        // 启动tomcat服务器
    					this.tomcat.start();
    
    					// We can re-throw failure exception directly in the main thread
    					rethrowDeferredStartupExceptions();
    
    					Context context = findContext();
    					try {
    						ContextBindings.bindClassLoader(context, getNamingToken(context),
    								getClass().getClassLoader());
    					}
    					catch (NamingException ex) {
    						// Naming is not enabled. Continue
    					}
    
    					// Unlike Jetty, all Tomcat threads are daemon threads. We create a
    					// blocking non-daemon to stop immediate shutdown
    					startDaemonAwaitThread();
    				}
    				catch (Exception ex) {
    					containerCounter.decrementAndGet();
    					throw ex;
    				}
    			}
    			catch (Exception ex) {
    				throw new EmbeddedServletContainerException(
    						"Unable to start embedded Tomcat", ex);
    			}
    		}
    	}	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    11. 使用外置的Servlet容器

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

      优点:简单、便携;

      缺点:默认不支持Jsp、优化定制比较复杂(使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】,自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】);

    • 外置的Servlet容器,外面安装Tomcat:应用打成war包

    11.1 步骤

    1)必须创建一个web项目,war包方式
    在这里插入图片描述
    在这里插入图片描述

    完善web应用目录,指定web.xml; Web Resource目录:

    D:\Develops\IdeaProjects\study-spring-boot\spring-boot-atguigu\spring-boot-02-web-jsp\src\main\webapp

    在这里插入图片描述

    2)将嵌入式的Tomcat指定为provided;provided意味着打包的时候可以不用打包进去,外部容器(Web Container)会提供。该依赖理论上可以参与编译,测试,运行等周期。相当于compile,但是在打包阶段做了exclude的动作。

    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-tomcat</artifactId>
    	<scope>provided</scope>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3)必须编写一个SpringBootServletInitializer的子类,重写configure方法指定SpringBoot应用的主启动类。

    public class ServletInitializer extends SpringBootServletInitializer {
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder applicationBuilder) {
            // 传入SpringBoot应用的主程序
            return applicationBuilder.sources(SpringBoot02WebJspApplication.class);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4)如果要支持Jsp,在application.properties文件添加mvc相关配置。

    # jsp视图: /webapp/WEB-INF/xxx.jsp
    # jsp视图文件前缀
    spring.mvc.view.prefix=/WEB-INF/
    # jsp视图文件后缀 
    spring.mvc.view.suffix=.jsp
    # 静态资源目录 resources/static/
    spring.mvc.static-path-pattern=/static/**
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    比如添加src/main/webapp/WEB-INF/success.jsp视图

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    
        
            来到success页面
        
        
            

    ${msg}

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    添加handler

    @Controller
    public class HelloController {
        @GetMapping("/hello")
        public String hello(Model model) {
            model.addAttribute("msg", "succeed");
            return "success";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5)配置外部web容器(tomcat),启动服务器就可以使用。
    在这里插入图片描述
    6)测试,访问 /hello
    在这里插入图片描述

    11.2 原理

    Jar包:执行SpringBoot主类的main方法,启动IOC容器,创建嵌入式的Servlet容器。

    War包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动IOC容器。

    外部Servlet容器启动原理 :

    • Servlet3.0标准,ServletContainerInitializer扫描所有jar包中META-INF/services/javax.servlet.ServletContainerInitializer文件指定的类并加载;
    • 加载spring-web-xxx包下的ServletContainerInitializer;
    • 扫描@HandlesTypes(WebApplicationInitializer.class)
    • 加载SpringBootServletInitializer并运行onStartup方法;
    • 加载@SpringBootApplication主类,启动IOC容器;

    具体规则:

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

    • ServletContainerInitializer的实现放在spring-web-xxx.jar包的/META-INF/services/javax.servlet.ServletContainerInitializer文件,内容就是ServletContainerInitializer的实现类的全限定名,比如:SpringServletContainerInitializer
      在这里插入图片描述

    • 应用启动的时候加载@HandlesTypes指定的类组件。

    详细流程:

    • 启动配置的外部Tomcat

    • 加载org/springframework/spring-web/5.3.7/spring-web-5.3.7.jar!/META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的ServletContainerInitializer实例 SpringServletContainerInitializer组件;

      public interface ServletContainerInitializer {
          void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
      }
      
      • 1
      • 2
      • 3

      SpringServletContainerInitializer将@HandlesTypes({WebApplicationInitializer.class})

      标注所有该类型的类都传入到onStartup方法的Set> webAppInitializerClasses中, 并

      为这些WebApplicationInitializer类型的类创建实例。最后依次调用WebApplicationInitializer#onStartup方法初始化;

      // 扫描所有WebApplicationInitializer组件
      @HandlesTypes(WebApplicationInitializer.class)
      public class SpringServletContainerInitializer implements ServletContainerInitializer {
      	@Override
      	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
      			throws ServletException {
      
      		List<WebApplicationInitializer> initializers = Collections.emptyList();
      
      		if (webAppInitializerClasses != null) {
      			initializers = new ArrayList<>(webAppInitializerClasses.size());
      			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 {
                              /* 将@HandlesTypes扫描到的WebApplicationInitializer组件实例化并添						  * 加到initializers中
                               */
      						initializers.add((WebApplicationInitializer)
                                               // 实例化WebApplicationInitializer组件
      ReflectionUtils.accessibleConstructor(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) {
                  // 依次执行所有WebApplicationInitializer#onStartup方法
      			initializer.onStartup(servletContext);
      		}
      	}
      
      }
      
      // WebApplicationInitializer接口
      public interface WebApplicationInitializer {
      	void onStartup(ServletContext servletContext) throws ServletException;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49

      执行到自定义WebApplicationInitializer组件,ServletInitializer#onStartup方法,其实是父类的SpringBootServletInitializer#onStartup;

      // 自定义WebApplicationInitializer组件
      public class ServletInitializer extends SpringBootServletInitializer {
      
          @Override
          protected SpringApplicationBuilder configure(SpringApplicationBuilder applicationBuilder) {
              // 传入SpringBoot应用的主程序到SpringApplicationBuilder
              return applicationBuilder.sources(SpringBoot02WebJspApplication.class);
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    在这里插入图片描述

    // WebApplicationInitializer组件
    public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
    	// SpringBootServletInitializer#onStartup
    	@Override
    	public void onStartup(ServletContext servletContext) throws ServletException {
    		servletContext.setAttribute(LoggingApplicationListener.REGISTER_SHUTDOWN_HOOK_PROPERTY, false);
    		// Logger initialization is deferred in case an ordered
    		// LogServletContextInitializer is being used
    		this.logger = LogFactory.getLog(getClass());
    		// 创建web的IOC容器
    		WebApplicationContext rootApplicationContext = createRootApplicationContext(servletContext);
    		if (rootApplicationContext != null) {
    			servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
    		}
    		else {
    			this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not "
    					+ "return an application context");
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • SpringServletContainerInitializer实例执行SpringBootServletInitializer#onStartup时会调用

      createRootApplicationContext方法创建IOC容器。

      并通过自定义WebApplicationInitializer组件重写的configure方法获取builder对象,再通过builder.build()获取SpringApplication对象。

      protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
      	SpringApplicationBuilder builder = createSpringApplicationBuilder();
      	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.contextFactory((webApplicationType) -> new AnnotationConfigServletWebServerApplicationContext());
          // 执行的是子类的configure,将应用主启动类添加到builder并返回SpringApplicationBuilder
      	builder = configure(builder);
      	builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
          // 通过SpringApplicationBuilder#build创建应用实例 SpringApplication
      	SpringApplication application = builder.build();
      	// ...省略
          // 运行SpringApplication,application通过builder添加了主启动类,会启动主启动类
      	return run(application);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20

    在这里插入图片描述
    最后执行run(application)启动Spring应用;后面就和SpringBoot应用主程序入口启动一样的步骤了。

    // org.springframework.boot.web.servlet.support.SpringBootServletInitializer#run
    protected WebApplicationContext run(SpringApplication application) {
    	return (WebApplicationContext) application.run();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 先启动Servlet容器,再启动SpringBoot应用。
      在这里插入图片描述
  • 相关阅读:
    Python-Requests
    关于Python和自动化
    SpringBoot定义优雅全局统一Restful API 响应框架四
    信息泄露漏洞的JS整改方案
    Notepad++安装插件和配置快捷键
    townscaper随机生成城镇算法分析
    【沐风老师】怎么在3DMAX中使用MAXScript脚本动画编程?
    python+yolov3视频车辆检测代码
    PHP 如何创建一个 composer 包 并在 项目中使用自己的 composer sdk 包
    解锁潜力:创建支持Actions接口调用的高级GPTs
  • 原文地址:https://blog.csdn.net/u013044713/article/details/130856141