文章目录
抛出几个问题!
spring-boot如何集成tomcat?
tomcat如何加载dispathServlet?
刷新spring容器后,会创建tomcat容器并启动.
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();//创建tomcat容器
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
创建webServer
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
//这里第一次进入都为null
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
//从容器中获取ServletWebServerFactory,这里可以获取不同的web容器,tomcat,jetty等,
ServletWebServerFactory factory = getWebServerFactory();//解耦
createWebServer.tag("factory", factory.getClass().toString());
//创建web容器
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
从容器中获取ServletWebServerFactory,这里可以获取不同的web容器,tomcat,jetty等,
protected ServletWebServerFactory getWebServerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
ServletWebServerFactory注入流程
首先META-INF/spring.factories加载ServletWebServerFactoryAutoConfiguration
然后此类会导入ServletWebServerFactoryConfiguration.EmbeddedTomcat,spring容器默认使用tomcat,这里可以设置具体加载那种容器.
加载容器.
创建web容器并启动.
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
//创建tomcat容器
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
//创建http连接方式,默认1.1,nio方式连接
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
//添加连接器
tomcat.getService().addConnector(connector);
customizeConnector(connector);//配置连接器
//添加容器到service
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
//添加自定义连接器,可通过addAdditionalTomcatConnectors添加
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
//创建tomcatwebServer,并启动
return getTomcatWebServer(tomcat);
}
启动tomcat
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
//初始化tomcat
initialize();
}
初始化tomcat
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();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
设置异步线程阻塞,启动socket
private void startDaemonAwaitThread() {
Thread awaitThread = new Thread("container-" + (containerCounter.get())) {
@Override
public void run() {
TomcatWebServer.this.tomcat.getServer().await();
}
};
awaitThread.setContextClassLoader(getClass().getClassLoader());
awaitThread.setDaemon(false);
awaitThread.start();
}
DispatcherServlet加入tomcat容器
通过META-INF自动配置
从spring-mvc加载DispatcherServlet到tomcat
这里tomcat直接通过读取web.xml直接加载dispatcherServer
然后创建spring-mvc容器,该容器主要配置Controller.
那他是如何与spring容器整合一块的了?
这里主要通过ServletContext,当监听器初始化时,会创建spring容器,然后设置到ServletContext中
org.springframework.web.context.ContextLoader#initWebApplicationContext
然后在创建dispatchServlet后,创建spring-mvc容器,紧接着从ServletContext获取spring容器,并设置成为父容器.
org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
spring-mvc加载DispatcherServlet到内嵌tomcat(或不配置web.xml,使用响应式注入).
使用方式
org.springframework.web.WebApplicationInitializer
public void onStartup(ServletContext container) {
* 创建spring容器
* // Create the 'root' Spring application context
* AnnotationConfigWebApplicationContext rootContext =
* new AnnotationConfigWebApplicationContext();
* //注册spring容器扫描Bean路径
* rootContext.register(AppConfig.class);
*
* // Manage the lifecycle of the root application context
* //注册监听器到tomcat容器,执行完后会将spring容器存储Tomcat上下文中
* container.addListener(new ContextLoaderListener(rootContext));
*
* // Create the dispatcher servlet's Spring application context
* //创建spring-mvc容器
* AnnotationConfigWebApplicationContext dispatcherContext =
* new AnnotationConfigWebApplicationContext();
* //注册spring-mvc容器配置
* dispatcherContext.register(DispatcherConfig.class);
*
* // Register and map the dispatcher servlet
* //创建dispatchServlet对象,并传入spring-mvc容器
* ServletRegistration.Dynamic dispatcher =
* container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
* dispatcher.setLoadOnStartup(1);
* dispatcher.addMapping("/");
* }
*
当通过内嵌tomcat启动时,会利用spi机制,servlet3.0规范,注入一个
org.springframework.web.SpringServletContainerInitializer
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
//通过反射创建WebApplicationInitializer对象
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);
//遍历执行WebApplicationInitializer对象
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
可以发现这里调用所有继承了WebApplicationInitializer的类,我们可以在这里实现注入servlet.将springMvc容器dispatcher添加到tomcat,并添加映射路径.
spring提供了抽象类,可以继承实现.
org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
//注册servlet,并添加映射路径
registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
//将springMvc容器dispatcher添加到tomcat
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name.");
}
registration.setLoadOnStartup(1);
//映射路径
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
//自定义ServletRegistration,子类扩展
customizeRegistration(registration);
}
spring-boot加载DispatchServlet流程.
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#configureContext
1:创建TomcatStarter,这个类本身继承ServletContainerInitializer.
紧接着将其添加到mcat容器,这样当启动tomcat时,就会调用.
2:调用TomcatStarter
org.apache.catalina.core.StandardContext#startInternal
3:创建并遍历initializers
org.springframework.boot.web.embedded.tomcat.TomcatStarter#onStartup
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
注意addServletContextInitializerBeans,这个类添加了所有在容器中实现了ServletContextInitializer的接口.其中包括DispatcherServletRegistrationBean,该类就只注册dispatchServlet核心类.
该DispatcherServletRegistrationBean是在org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration 类加载的.以及完成注入dispatchServlet过程.
4:也就是在这里实现的注册mapping.
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
5:注册拦截路径,以及dispatchServlet
public final void onStartup(ServletContext servletContext) throws ServletException {
String description = getDescription();//获取注册Tomcat容器servlet名
if (!isEnabled()) {
logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
return;
}
register(description, servletContext);//注册拦截路径,以及dispatchServlet
}
@Override
protected final void register(String description, ServletContext servletContext) {
D registration = addRegistration(description, servletContext);//注册DispatchServlet
if (registration == null) {
logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
return;
}
configure(registration);
}
6:可以看到这里注册了
7:下一步设置拦截路径
tomcat调用WebApplicationInitializer流程.
1:加载web配置
org.apache.catalina.startup.ContextConfig#webConfig
protected void processServletContainerInitializers() {
List<ServletContainerInitializer> detectedScis;
try {
WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
//加载META-INF/services/下所有类
detectedScis = loader.load(ServletContainerInitializer.class);
} catch (IOException e) {
log.error(sm.getString(
"contextConfig.servletContainerInitializerFail",
context.getName()),
e);
ok = false;
return;
}
...}
2:加载META-INF/services/下所有类,并添加到initializerClassMap中.
3:遍历添加到initializers
org.apache.catalina.startup.ContextConfig#webConfig
@Override
public void addServletContainerInitializer(
ServletContainerInitializer sci, Set<Class<?>> classes) {
initializers.put(sci, classes);
}
4:调用所有ServletContainerInitializers
org.apache.catalina.core.StandardContext#startInternal