您现在的位置是:首页 >技术交流 >Chapter5: SpringBoot与Web开发2网站首页技术交流

Chapter5: SpringBoot与Web开发2

crysw 2024-06-24 06:01:02
简介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 = "/";
    
    // ...
    
}    

修改配置属性

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

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);
        }
    };
}

重启应用,从启动日志中查看,应用是从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");
    }
}

再使用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;
    }
}

启动应用后,浏览器访问 /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");
    }
}

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;
    }
}

测试过滤器,查看启动日志和请求/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销毁");

    }
}

ServletListenerRegistrationBean注册自定义Listener

@Configuration
public class MyServerConfig {
   //  ...
    // 注册listener
    @Bean
    public ServletListenerRegistrationBean myListener() {
        ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();
        servletListenerRegistrationBean.setListener(new MyListener());
        return servletListenerRegistrationBean;
    }
}

测试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;
}

也可以使用注解注册三大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.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<exclusions>
		<exclusion>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
		</exclusion>
	</exclusions>
</dependency>

<!--引入其他web容器 Jetty-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

查看启动日志, 启动的容器是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)

(2)改造成undertow容器

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

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<exclusions>
		<exclusion>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
		</exclusion>
	</exclusions>
</dependency>

<!--引入其他web容器 undertow-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

查看启动日志

[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)

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);
			}
		}

	}
}

嵌入式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;
	}

}

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

public interface EmbeddedServletContainerCustomizer {

	/**
	 * Customize the specified {@link ConfigurableEmbeddedServletContainer}.
	 * @param container the container to customize
	 */
	void customize(ConfigurableEmbeddedServletContainer container);

}

那么我们对嵌入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()));
	}
}

自定义嵌入式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);
            }
        };
    }
}

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);

}

嵌入式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();
}

思考: 什么时候创建嵌入式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);
	}
}

如果是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);
}

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();
}

(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.
}

(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);
	}
}

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();
}

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);
}

那么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();
		}

	}
}	

(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);
}

在后置处理器的前置方法中判断,如果是嵌入式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;
	}
}

(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);
			}
		}
	}	
}

11. 使用外置的Servlet容器

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

    优点:简单、便携;

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

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

11.1 步骤

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

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

D:DevelopsIdeaProjectsstudy-spring-bootspring-boot-atguiguspring-boot-02-web-jspsrcmainwebapp

在这里插入图片描述

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

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

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

public class ServletInitializer extends SpringBootServletInitializer {

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

}

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/**

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

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>来到success页面</title>
    </head>
    <body>
        <h1>${msg}</h1>
    </body>
</html>

添加handler

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

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;
    }
    

    SpringServletContainerInitializer将@HandlesTypes({WebApplicationInitializer.class})

    标注所有该类型的类都传入到onStartup方法的Set<Class<?>> 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;
    }
    

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

    // 自定义WebApplicationInitializer组件
    public class ServletInitializer extends SpringBootServletInitializer {
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder applicationBuilder) {
            // 传入SpringBoot应用的主程序到SpringApplicationBuilder
            return applicationBuilder.sources(SpringBoot02WebJspApplication.class);
        }
    
    }
    

在这里插入图片描述

// 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");
		}
	}
}
  • 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);
    }
    

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

// org.springframework.boot.web.servlet.support.SpringBootServletInitializer#run
protected WebApplicationContext run(SpringApplication application) {
	return (WebApplicationContext) application.run();
}
  • 先启动Servlet容器,再启动SpringBoot应用。
    在这里插入图片描述
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。