Springboot
默认使用的是嵌入式的Servlet
容器(Tomcat
);
问题?
1),如何定制和修改servlet容器的相关配置?
以前使用外置的Tomcat
,可以到conf
目录下修改配置,进行优化。嵌入式的如何做到呢?
1.修改和server
有关的配置(ServerProperties
):
server.port=8082
server.servlet.context-path=/crud
server.tomcat.uri-encoding=utf-8
//通用的Servlet容器设置
server.xxx
//Tomcat的设置
server.tomcat.xxx
2.编写一个EmbeddedServletContainerCustomizer
:嵌入式的servlet
容器的定制器,来修改servlet
容器的配置(Spring Boot2.0
以上版本EmbeddedServletContainerCustomizer
被WebServerFactoryCustomizer
替代)
//配置嵌入式的servlet容器
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>(){
//customize方法就是根据相关配置初始化Servlet容器
@Override
public void customize(ConfigurableWebServerFactory configurableWebServerFactory) {
//优先级高于配置文件
configurableWebServerFactory.setPort(8083);
}
};
}
另一个方法
//必须要加到容器中才能生效
// @Bean
// public ConfigurableServletWebServerFactory configurableServletWebServerFactory(){
// //修改jetty相关配置,优先级低于配置文件
// JettyServletWebServerFactory jettyServletWebServerFactory = new JettyServletWebServerFactory();
// jettyServletWebServerFactory.setPort(8085);
// return jettyServletWebServerFactory;
// }
2),SpringBoot
能不能支持其他servlet
容器springboot
默认使用Tomcat
,还支持使用Jetty(更适合长连接)
和Undertow(不支持JSP)
<!--引入web模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--排除toncat-->
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--引入其他servlet容器—jetty-->
<dependency>
<artifactId>spring-boot-starter-jetty</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
启动:上面的配置是所有servlet
容器通用的
注册三大组件
springboot
是以jar
包的方式启动嵌入式的tomcat
,,而不是创建一个标准的web
应用的目录结构。如果是一个web
应用的目录结构,webapp/WEB-INF/web.xml
三大组件将注册在web.xml
中。然而springboot
没有提供web.xml
文件,怎么注册呢?
注册三大组件用一下方式:
ServletRegistrationBean:
//注册三大组件
@Bean
public ServletRegistrationBean myServlet(){
return new ServletRegistrationBean(new MyServlet(),"/myServlet");
}
public class MyServlet extends HttpServlet {
//处理get请求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("hello myservlet");
}
}
FilterRegistrationBean:
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new MyFilter());
filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
return filterRegistrationBean;
}
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyFilter process...");
filterChain.doFilter(servletRequest,servletResponse );
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
ServletListenerRegistrationBean:
@Bean
public ServletListenerRegistrationBean myListener(){
ServletListenerRegistrationBean<MyListener> myListenerServletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyListener());
return myListenerServletListenerRegistrationBean;
}
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("contextInitialized..web应用启动啦!!");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("contextDestroyed..服务器关闭啦!!");
}
}
点其他停止的相当于断电,不会打印"contextDestroyed…服务器关闭啦!!"的。
SpringBoot
帮我们自动配置SpringMVC
的时候,自动注册Springmvc
的前端控制器(DispatcherServlet
)DispatcherServletAutoConfiguration
:
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
//默认拦截: / 所有请求;包静态资源,但是不拦截jsp请求; /*会拦截jsp
//可以通过spring.mvc.servlet.path=来修改SpringMVC前端控制器默认拦截的请求路径
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
嵌入式Servlet容器自动配置原理
springboot2.x。
回顾修改servlet
容器的相关配置:从下面的代码片,可以看到我们要从容器中获取一个组件ConfigurableWebServerFactory
,然后利用configurableWebServerFactory
进行属性的设置,设置完属性,把WebServerFactoryCustomizer
注入到容器中。
//配置嵌入式的servlet容器
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>(){
//customize方法就是根据相关配置初始化Servlet容器
@Override
public void customize(ConfigurableWebServerFactory configurableWebServerFactory) {
//优先级高于配置文件
configurableWebServerFactory.setPort(8083);
}
};
}
1.猜想自动配置时,要先在容器中注册WebServerFactory
,看一下WebServerFactory
继承结构:
看ServletWebServerFactoryConfiguration
:这是一个配置类,Spring容器启动时,添加到容器中,并且如果导入了tomcat
,jetty
或者Undertow
依赖,就会把对应的XXXServletWebServerFactory
导入到容器中。
@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {
@Configuration(proxyBeanMethods = false)
//如果有这三个类就生效,即如果依赖了tomcat,这个就生效,下面的也是如此
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
//..省略
return factory;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedJetty {
@Bean
JettyServletWebServerFactory JettyServletWebServerFactory(
ObjectProvider<JettyServerCustomizer> serverCustomizers) {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedUndertow {
@Bean
UndertowServletWebServerFactory undertowServletWebServerFactory(
ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers,
ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
//....
return factory;
}
}
}
2.继续猜想容器中有了WebServerFactory
,那么WebServerFactoryCustomizer
是在哪里被注入的呢
WebServerFactoryCustomizer
的继承结构:
看EmbeddedWebServerFactoryCustomizerAutoConfiguration
:我们导入了那个servlet
容器依赖,哪个xxxWebServerFactoryCustomizer
就别注入到容器中。
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
@Configuration(proxyBeanMethods = false)
//当容器中存在Tomcat相关类就生效,下面几个也是如此,也就是说我们导入哪个依赖,哪个就生效
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
public static class TomcatWebServerFactoryCustomizerConfiguration {
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class })
public static class JettyWebServerFactoryCustomizerConfiguration {
@Bean
public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new JettyWebServerFactoryCustomizer(environment, serverProperties);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })
public static class UndertowWebServerFactoryCustomizerConfiguration {
@Bean
public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
}
}
}
3.容器中WebServerFactory
有了,WebServerFactoryCustomizer
也有了,那么是什么时候初始化Servlet
容器的呢(就是调用WebServerFactoryCustomizer
的customize
方法)
先看类ServletWebServerFactoryAutoConfiguration
:
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
//...
public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
//...
//这个类的作用是往容器中添加一些组件
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
//往容器中添加了WebServerFactoryCustomizerBeanPostProcessor组件(web服务工厂定制器的后置处理器)。
//这个类实现了BeanPostProcessor,属于bean的后置处理器。作用是在bean初始化前后加一些自己的逻辑处理
registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
WebServerFactoryCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
再看bean
的后置处理器WebServerFactoryCustomizerBeanPostProcessor
:简单点说就是在bean(xxxServletWebServerFactory )
的初始化之前,获取所有的定制器来先定制servlet
相关配置。
public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private List<WebServerFactoryCustomizer<?>> customizers;
...
//bean初始化前调用
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
//判断这个bean的类型是WebServerFactory
//TomcatServletWebServerFactory继承了WebServerFactory,所以它初始化时,会往下执行
if (bean instanceof WebServerFactory) {
postProcessBeforeInitialization((WebServerFactory) bean);
}
return bean;
}
//bean初始化后调用
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@SuppressWarnings("unchecked")
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
LambdaSafe
//获取Web服务工厂定制器(WebServerFactoryCustomizer)
.callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
//调用customizer的customize方法,定制嵌入式容器的servlet容器相关的规则,优先级高于配置文件
.invoke((customizer) -> customizer.customize(webServerFactory));
}
private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
//返回WebServerFactoryCustomizer类型的Customizer(定制器)
//上面自动配置类注册的Web服务工厂定制器(xxxWebServerFactoryCustomizer)就是继承了WebServerFactoryCustomizer,
//所以这里将那些Customizer(定制器)返回
return (Collection) this.beanFactory
.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
}
}
参考:
嵌入式Servlet容器启动原理
前面自动配置将配置在WebServerFactoryCustomizerBeanPostProcessor
中定制在xxxWebServerFactoryCustomizer
定制器中了。
那么
什么时候创建嵌入式的Servlet
容器工厂?(之前自动配置注入到容器的只是工厂的bean
定义)
什么时候获取嵌入式的Servlet
容器并启动Tomcat
的呢?
步骤:
1、Spring Boot
启动运行run
方法。执行到SpringApplication
的run(String… args)
方法。
只截取相关代码:
public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
...
ConfigurableApplicationContext context = null;
...
//调用createApplicationContext方法,返回的是
//org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext类
//注意这个类,比较重要,如果是web应用的话,springioc容器用的就是它
context = createApplicationContext();
...
refreshContext(context);
...
return context;
}
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
//DEFAULT_SERVLET_WEB_CONTEXT_CLASS=
//org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
}
如果是web
应用context
的类型就是AnnotationConfigServletWebServerApplicationContext
。
2、执行refreshContext
(context
);SpringBoot
刷新IOC
容器(创建IOC
容器对象,并初始化容器,创建容器中的每一
个组件)。一路执行,到refresh(ApplicationContext applicationContext)
方法。
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//将applicationContext强转成AbstractApplicationContext类型,然后调用refresh()方法。
((AbstractApplicationContext) applicationContext).refresh();
}
AbstractApplicationContext
的refresh
()方法。执行到refresh,表明了如果是web应用,在创建springIOC容器的同时,也会创建内嵌的servlet容器。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
。。。。
之前自动配置的WebServerFactoryCustomizerBeanPostProcessor
后置处理器,已经在registerBeanPostProcessors
方法中注册在容器中了。
3,主要是onRefresh()
方法,web
的springIOC
容器AnnotationConfigServletWebServerApplicationContext
重写了onRefresh
方法。因为AbstractApplicationContext
是由applicationContext
强转的,而applicationContext
是AnnotationConfigServletWebServerApplicationContext
类型的,所以调用的其实是AnnotationConfigServletWebServerApplicationContext
的onRefresh
()方法,而AnnotationConfigServletWebServerApplicationContext
的onRefresh
()方法是从它的父类继承过来的,也就是ServletWebServerApplicationContext
的onRefresh
()方法。
spring原理的refresh方法调用的就是AbstractApplicationContext的refresh方法。
ServletWebServerApplicationContext也继承自AbstractApplicationContext
@Override
protected void onRefresh() {
super.onRefresh();
createWebServer();
。。。
}
4.webioc
容器会创建嵌入式的Servlet
容器;createWebServer
();
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
5. 获取嵌入式的Servlet容器工厂ServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
6.使用容器工厂获取嵌入式的Servlet容器,入参为一个匿名的ServletContextInitializer
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
protected ServletWebServerFactory getWebServerFactory() {
//获取ServletWebServerFactory类型的bean名称
String[] beanNames = getBeanFactory()
.getBeanNamesForType(ServletWebServerFactory.class);
。。。
//根据获取到的bean定义创建xxxServletWebServerFactory对象,在对象的初始化之前会调用自动配置里的
//后置处理器获取所有的定制器来先定制servlet相关配置
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
//注意this::selfInitialize,相当于如下写法
return new ServletContextInitializer(){
@Override
public void onStartUp(ServletContext servletContext){
return selfInitialize(servletContext);
}
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
//getServletContextInitializerBeans方法从Spring IOC容器中获取所有的ServletContextInitializer
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
问题:
1、什么是ServletContextInitializer
?
用于往ServletContext
容器中注册Servlet
,Filter
或者EventListener
。它的方法和WebApplicationInitializer
一模一样,但是它是SpringBoot
提供的。
如果只是用于注册Servlet
,Filter
或者EventListener
,我直接是用WebApplicationInitializer
接口就行了啊,为什么还需要这个接口。
因为接口WebApplicationInitializer
有局限性,只有当Springboot
以War
包方式启动时(即使用外置Tomcat
),容器在启动时会去每个jar
包下找META-INF/services/javax.servlet.ServletContainerInitializer
文件,如果有则根据这个文件内容创建ServletContainerInitializer
的实现类实例。Spring
对ServletContainerInitializer
进行了实现,为SpringServletContainerInitializer
,SpringServletContainerInitializer
感兴趣的类就是WebApplicationInitializer
。Tomcat
启动时会去调用ServletContainerInitializer
的onstartup
方法,SpringServletContainerInitializer
的onstartup
方法,就会调用WebApplicationInitializer
的onstartup
方法。
所以当Springboot
以jar
包方式启动时,WebApplicationInitializer
就不会被使用到,但我又想往ServletContext
容器中注册Servlet
,Filter
或者EventListener
,这时就可以使用ServletContextInitializer
来注入三大组件了。
7,嵌入式的Servlet
容器创建对象并启动Servlet
容器;
在上文的自动配置原理中我们知道了自动配置往容器中注入了TomcatServletWebServerFactory
(以tomcat
为例,其他Servlet
容器相同),而这个类实际上继承了ServletWebServerFactory
。所以在这里会被拿到,执行getWebServer
方法。TomcatServletWebServerFactory
的getWebServer
方法:
//入参为一个匿名的ServletContextInitializer,上面已经传过来了
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
//创建了tomcat容器对象
Tomcat tomcat = new Tomcat();
//配置tomcat相关配置
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("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);
}
//重点1:
prepareContext(tomcat.getHost(), initializers);
//重点2:
return getTomcatWebServer(tomcat);
}
//先看重点2---------------------------------------------------------
TomcatWebServer 类
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
public class TomcatWebServer implements WebServer {
...
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
// Start the server to trigger initialization listeners
//启动tomcat
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(),
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) {
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
}
重点1:----------------------------------------------------------------------------------------------------------------
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = this.getValidDocumentRoot();
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
.....
// (1)把ServletContextInitializer的实现都合并起来
ServletContextInitializer[] initializersToUse = this.mergeInitializers(initializers);
// 这个方法最重要的是把TomcatStarter添加到了tomcat容器ContextServletContainerInitializer集合中
// 容器创建完成后会遍历调用ContextServletContainerInitializer的onStartUp方法。
this.configureContext(context, initializersToUse);
this.postProcessContext(context);
}
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
// (2)直接new 一个TomcatStarter
TomcatStarter starter = new TomcatStarter(initializers);
//把TomcatStarter添加到了tomcat容器ContextServletContainerInitializer集合中
context.addServletContainerInitializer(starter, NO_CLASSES);
.........
}
TomcatStarter是ServletContainerInitializer的实现类:
class TomcatStarter implements ServletContainerInitializer {
// ServletContextInitializer是Spring中维护的接口
private final ServletContextInitializer[] initializers;
TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}
// tomcat容器启动最后会调用这个方法,这是servlet3.0时添加的
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
ServletContextInitializer[] var3 = this.initializers;
int var4 = var3.length;
// 遍历执行ServletContextInitializer#onStartup
for(int var5 = 0; var5 < var4; ++var5) {
ServletContextInitializer initializer = var3[var5];
initializer.onStartup(servletContext);
}
}
问题:
1、ServletContainerInitializer
是什么时候被执行的?
Tomcat
启动会调用LifecycleBase
类的start
方法,start
方法又会调用StandardContext
的startInternal
方法:
会遍历所有的ServletContainerInitializer
并执行它的onStartup
方法
外置Servlet容器
使用外置Servlet容器
嵌入式的Servlet的容器用起来简单、便携。
但是也有缺点:默认不支持jsp,优化定制比较复杂。所以在适当场景下,我们还是需要外部的servlet容器。
1、创建war类型的maven项目。创建好web项目的目录结构(必须有web.xml文件)。
2、将嵌入式的servlet容器依赖的scope指定为provided。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<packaging>war</packaging>
3、编写一个SpringBootServletInitializer类型的子类,并重写configure方法。
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入springboot应用的主程序
return application.sources(SpringBootWebDemo1Application.class);
}
}
4、启动容器。
外置Servlet容器启动原理
原理对比;jar
包: 执行SpringBoot
主类的main
方法,调用SpringApplication
的run
方法,启动Spring
的ioc
容器,创建嵌入式的Servlet
容器。war
包:启动服务器(创建嵌入式的Servlet
容器),服务器启动SpringBoot
的SpringBootServletInitializer
,启动Spring
的ioc
容器。
Servlet3.0+
定义了几个web
应用在启动时的规则:
(1)容器在启动时会去每个jar包下找META-INF/services/javax.servlet.ServletContainerInitializer
文件,如果有则根据这个文件内容创建ServletContainerInitializer
的实现类实例。
(2)可以使用@HandlesTypes
注解加载需要的类。
启动原理:
SpringServletContainerInitializer ->spring SCI机制
其实就是servlet3.0的ServletContainerInitializer SCI机制的扩展延伸
1、启动servlet
容器。
2、容器根据Servlet
的规则创建SpringServletContainerInitializer
。该类在在spring-web-xxx.jar
下,这个jar
包的META-INF/services/javax.servlet.ServletContainerInitializer
文件内容就是org.springframework.web.SpringServletContainerInitializer
的全类名。
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
//@HandlesTypes标注的所有这个类型的类都传入到onStartup方法的Set中
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
//如果这个类不是接口和抽象类,就会创建实例
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((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);
}
}
}
3、SpringBootServletInitializer
实现了WebApplicationInitializer
接口。所以SpringBootServletInitializer
的实现类(上文的ServletInitializer
)会被创建对象,并执行onStartup
方法。
4、SpringBootServletInitializer
实例执行onStartup
方法时调用了createRootApplicationContext
方法。
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
builder.main(this.getClass());
ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
}
builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
//调用configure方法,子类(上文的ServletInitializer )重写了这个方法
//并且将SpringBoot的主程序类传入了进来。
builder = this.configure(builder);
builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
//使用builder创建一个Spring应用
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
application.addPrimarySources(Collections.singleton(this.getClass()));
}
Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
if (this.registerErrorPageFilter) {
application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
}
//启动Spring应用,进入就会去到嵌入式Servlet容器启动原理的步骤1
return this.run(application);
}
5、Spring应用启动后就创建ioc容器。执行到这一步就和内置Servlet容器启动原理相同了。
遗留问题
如果使用的是外置servlet容器,同时在配置文件做了相关配置,然后又在springboot应用里定制了servlet容器配置,那么谁生效?
自测是外置的生效,那么,为啥自动配置会没生效呢?
是因为内置的Servlet
容器根本没启动。
在之前将内置Servlet
容器启动原理时,ServletWebServerApplicationContext
:使用外置tomcat
启动时,一启动servletContext !=null
,if (webServer == null && servletContext == null)
条件不满足,因此,内置的servlet
容器并没有启动。
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
5. 获取嵌入式的Servlet容器工厂ServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
6.使用容器工厂获取嵌入式的Servlet容器
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}