您现在的位置是:首页 >技术交流 >Chapter5: SpringBoot与Web开发2网站首页技术交流
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应用。