SpringBoot(一)内嵌tomcat原理和DispatcherServlet配置原理
Springboot使用起来很简单,在pom中引入如下依赖:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.3</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> </dependencies>
其实就可以起来一个web服务,可以写controller了。
启动入口也很简单。
@SpringBootApplication public class ApplicationStart { public static void main(String[] args) { SpringApplication.run(ApplicationStart.class); } }
对于启动流程来说,主要看SpringApplication.run方法了。
先挑着看,具体每步做什么,在启动流程中分析。
1:看创建的容器 public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); stopWatch.start(); DefaultBootstrapContext bootstrapContext = this.createBootstrapContext(); ConfigurableApplicationContext context = null; this.configureHeadlessProperty(); SpringApplicationRunListeners listeners = this.getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments); this.configureIgnoreBeanInfo(environment); Banner printedBanner = this.printBanner(environment);
// 创建的容器,分为三种 AnnotationConfigServletWebServerApplicationContext AnnotationConfigReactiveWebServerApplicationContext
// AnnotationConfigApplicationContext
context = this.createApplicationContext(); context.setApplicationStartup(this.applicationStartup); this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 在这里启动容器 this.refreshContext(context); this.afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } listeners.started(context); this.callRunners(context, applicationArguments); } catch (Throwable var10) { this.handleRunFailure(context, var10, listeners); throw new IllegalStateException(var10); } try { listeners.running(context); return context; } catch (Throwable var9) { this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); } }
1.1创建容器:
protected ConfigurableApplicationContext createApplicationContext() { return this.applicationContextFactory.create(this.webApplicationType); }
使用默认的容器工厂:是一个函数式接口。
会根据是servlet编程还是响应式编程来创建容器。
我们这里是servlet编程,使用 AnnotationConfigServletWebServerApplicationContext容器
2:启动容器 this.refreshContext(context);
private void refreshContext(ConfigurableApplicationContext context) { if (this.registerShutdownHook) { shutdownHook.registerApplicationContext(context); } this.refresh(context); } protected void refresh(ConfigurableApplicationContext applicationContext) { applicationContext.refresh(); }
是调用AnnotationConfigServletWebServerApplicationContext 中的refresh()方法,这是spring启动过程中的主要方法。
这个refresh()方法是在父类 AbstractApplicationContext 中的
public void refresh() throws BeansException, IllegalStateException { synchronized(this.startupShutdownMonitor) { this.prepareRefresh(); ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); this.prepareBeanFactory(beanFactory); try { this.postProcessBeanFactory(beanFactory); this.invokeBeanFactoryPostProcessors(beanFactory); this.registerBeanPostProcessors(beanFactory); this.initMessageSource(); this.initApplicationEventMulticaster();
// 这里是spring提供的一个扩展点,在父类中是个空方法,要由子类重写,这里就是springboot中的容器重写了 this.onRefresh(); this.registerListeners(); this.finishBeanFactoryInitialization(beanFactory); this.finishRefresh(); } catch (BeansException var9) { if (this.logger.isWarnEnabled()) { this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9); } this.destroyBeans(); this.cancelRefresh(var9); throw var9; } finally { this.resetCommonCaches(); } } }
AnnotationConfigServletWebServerApplicationContext 的父类中ServletWebServerApplicationContext重写了onResresh()方法
ServletWebServerApplicationContext#onResresh()
protected void onRefresh() { super.onRefresh(); try {
//内嵌的服务就是这里面创建的 this.createWebServer(); } catch (Throwable var2) { throw new ApplicationContextException("Unable to start web server", var2); } }
private void createWebServer() { WebServer webServer = this.webServer;
// 得到servlet上下文 ServletContext servletContext = this.getServletContext();
// 这里判断了下servlet上下文是否为空???见下面补充 if (webServer == null && servletContext == null) { StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
// 如果没有上下文就要创建服务了 ServletWebServerFactory factory = this.getWebServerFactory(); createWebServer.tag("factory", factory.getClass().toString());
// 执行TomcatServletServerWebServerFactory this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()}); createWebServer.end(); this.getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer)); this.getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); } else if (servletContext != null) { try { this.getSelfInitializer().onStartup(servletContext); } catch (ServletException var5) { throw new ApplicationContextException("Cannot initialize servlet context", var5); } } this.initPropertySources(); }
补充:上面为什么会有判断servlet上下文是否为空的逻辑呢?
因为对于springboot项目可以部署到tomcat,jetty,undertow等web服务器中,默认是使用tomcat的。如果像使用其他web服务器,就要使用配置文件了来修改默认的属性。这是约定大于配置。因为没有配置文件springboot也可以起一个web服务,因为由一些默认的配置。
对于jar部署的springboot项目,入口就是main方法,它要先读取配置才能启动servlet容器。这个时候走到上面的逻辑是没有servlet上下文的。
如果是war包部署的web服务,是tomcat先启动的,那走到上面逻辑的时候已经有了servlet上下文。
需要看下这里创建的到底是什么:ServletWebServerFactory factory = this.getWebServerFactory();
就是找一个ServletWebServerFactory的一个实现类类。
protected ServletWebServerFactory getWebServerFactory() { String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length == 0) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean."); } else if (beanNames.length > 1) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } else { return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); } }
这个接口呢有下面几个实现类:我们这里肯定要看tomcat的实现类,那这个TomcatServletWebServerFactory 是在哪里加载进来的呢
怎么加载的就要看springboot的自动配置原理了,在springboot中自动配置原理也是spi机制的一种原理,只不过加载的spring.factories文件这个后面再具体分析。
里面有一个ServletWebServerFactoryAutoConfiguration类,通过@Import导入了一个EmbeddedTomcat类。
这里就是 TomcatServletWebServerFactory 放入spring容器中的地方。
回到上面的逻辑,得到TomcatServletWebServerFactory调用其getWebServer 方法。
这里就是创建内嵌tomcat的地方,下面的写法是内嵌tomcat中创建有关tomcat组件的写法。
public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } // new 一个tomcat实例 Tomcat tomcat = new Tomcat(); File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath());
// tomcat中的Connector组件 Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); tomcat.getService().addConnector(connector); this.customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); this.configureEngine(tomcat.getEngine()); Iterator var5 = this.additionalTomcatConnectors.iterator(); while(var5.hasNext()) { Connector additionalConnector = (Connector)var5.next(); tomcat.getService().addConnector(additionalConnector); } // DispatcherServlet就是在这里初始化的 this.prepareContext(tomcat.getHost(), initializers);
// tomcat的start,和socket启动阻塞都在这个方法里 return this.getTomcatWebServer(tomcat); }
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, this.getPort() >= 0, this.getShutdown()); }
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) { this.monitor = new Object(); this.serviceConnectors = new HashMap(); Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; this.gracefulShutdown = shutdown == Shutdown.GRACEFUL ? new GracefulShutdown(tomcat) : null; this.initialize(); }
private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false)); synchronized(this.monitor) { try { this.addInstanceIdToEngineName(); Context context = this.findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && "start".equals(event.getType())) { this.removeServiceConnectors(); } });
// tomcat就是在这里启动的 this.tomcat.start(); this.rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader()); } catch (NamingException var5) { } // 这里就是设置socket监听 this.startDaemonAwaitThread(); } catch (Exception var6) { this.stopSilently(); this.destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", var6); } } }
private void startDaemonAwaitThread() { Thread awaitThread = new Thread("container-" + containerCounter.get()) { public void run() { TomcatWebServer.this.tomcat.getServer().await(); } }; awaitThread.setContextClassLoader(this.getClass().getClassLoader()); awaitThread.setDaemon(false); awaitThread.start(); }
上面就是内嵌tomcat的启动过程,下面分析下DispatcherServlet加载的源码分析。
在上面createServer方法中有一行调用: this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
参数是一个ServletContextInitializer 的数组,是从getSelfInitializer() 方法中得到,DispatcherServlet就是这里加载的。
// 但是这里返回是个lambda 表达式,ServletContextInitializer是个一个函数式接口,里面只有一个onStartup方法,这里相当于返回了一个接口的实例,只是这个实例不再容器中
// 只是这个实例的onStartup的逻辑就是下面的selfInitialize方法, 调用getSerlfInitializer方法的时候,下面那个方法不会马上执行。
private ServletContextInitializer getSelfInitializer() { return this::selfInitialize; } private void selfInitialize(ServletContext servletContext) throws ServletException { this.prepareWebApplicationContext(servletContext); this.registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(this.getBeanFactory(), servletContext);
// 这里就是找ServletContextInitializer的实现类 Iterator var2 = this.getServletContextInitializerBeans().iterator(); while(var2.hasNext()) {
// 这里遍历调用onStarup方法 ServletContextInitializer beans = (ServletContextInitializer)var2.next(); beans.onStartup(servletContext); } }
@FunctionalInterface public interface ServletContextInitializer { void onStartup(ServletContext servletContext) throws ServletException; }
回到上面的getWebServer方法,它的参数就是getSelfInitializer()得到的lamda表达式。在调用 this.prepareContext(tomcat.getHost(), initializers);
时初始化DispatcherServlet。这是在TomcatServletWebServerFactory 中的。
protected void prepareContext(Host host, ServletContextInitializer[] initializers) { File documentRoot = this.getValidDocumentRoot();
// 这个new出来的类,继承自tomcat中的StandardContext
// tomcat在初始化过程中会触发StandardContext中的一个全局变量 Map<ServletContainerInitializer,Set<Class<?>>> initializers
// 遍历它并执行ServletContainerInitializer.onStartup方法 这个过程在springmvc初始化servlet中有提到过 TomcatEmbeddedContext context = new TomcatEmbeddedContext(); if (documentRoot != null) { context.setResources(new TomcatServletWebServerFactory.LoaderHidingResourceRoot(context)); } context.setName(this.getContextPath()); context.setDisplayName(this.getDisplayName()); context.setPath(this.getContextPath()); File docBase = documentRoot != null ? documentRoot : this.createTempDir("tomcat-docbase"); context.setDocBase(docBase.getAbsolutePath()); context.addLifecycleListener(new FixContextListener()); context.setParentClassLoader(this.resourceLoader != null ? this.resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader()); this.resetDefaultLocaleMapping(context); this.addLocaleMappings(context); try { context.setCreateUploadTargets(true); } catch (NoSuchMethodError var8) { } this.configureTldPatterns(context); WebappLoader loader = new WebappLoader(); loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); loader.setDelegate(true); context.setLoader(loader); if (this.isRegisterDefaultServlet()) { this.addDefaultServlet(context); } if (this.shouldRegisterJspServlet()) { this.addJspServlet(context); this.addJasperInitializer(context); } context.addLifecycleListener(new TomcatServletWebServerFactory.StaticResourceConfigurer(context)); ServletContextInitializer[] initializersToUse = this.mergeInitializers(initializers); host.addChild(context);
// 到这里 initializersToUse还是上面的lambda表达式,只是做了一些合并操作 ,执行也是在这里面的 this.configureContext(context, initializersToUse); this.postProcessContext(context); }
this.configureContext(context, initializersToUse);
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
// 它就实现了ServletContainerInitializer的接口 并通过构造函数把上面的那个lambda表达式传递进去
// 它里面的onStartup方法,就会在执行的时候遍历这个数组中的实例执行其onStartup方法,也就时执行了上面的lambda表达式 TomcatStarter starter = new TomcatStarter(initializers); if (context instanceof TomcatEmbeddedContext) { TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext)context; embeddedContext.setStarter(starter); embeddedContext.setFailCtxIfServletStartFails(true); } // 这个context就是StandardContext的子类,把starter添加进去,上面TomcatStarter能执行也是这一步把它添加到了StandardContext的全局变量中了。 context.addServletContainerInitializer(starter, NO_CLASSES); Iterator var7 = this.contextLifecycleListeners.iterator(); while(var7.hasNext()) { LifecycleListener lifecycleListener = (LifecycleListener)var7.next(); context.addLifecycleListener(lifecycleListener); } var7 = this.contextValves.iterator(); while(var7.hasNext()) { Valve valve = (Valve)var7.next(); context.getPipeline().addValve(valve); } var7 = this.getErrorPages().iterator(); while(var7.hasNext()) { ErrorPage errorPage = (ErrorPage)var7.next(); org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage(); tomcatErrorPage.setLocation(errorPage.getPath()); tomcatErrorPage.setErrorCode(errorPage.getStatusCode()); tomcatErrorPage.setExceptionType(errorPage.getExceptionName()); context.addErrorPage(tomcatErrorPage); } var7 = this.getMimeMappings().iterator(); while(var7.hasNext()) { Mapping mapping = (Mapping)var7.next(); context.addMimeMapping(mapping.getExtension(), mapping.getMimeType()); } this.configureSession(context); (new DisableReferenceClearingContextCustomizer()).customize(context); var7 = this.getWebListenerClassNames().iterator(); while(var7.hasNext()) { String webListenerClassName = (String)var7.next(); context.addApplicationListener(webListenerClassName); } var7 = this.tomcatContextCustomizers.iterator(); while(var7.hasNext()) { TomcatContextCustomizer customizer = (TomcatContextCustomizer)var7.next(); customizer.customize(context); } }
知道了怎么走到ServletContextInitializer接口的onStartup方法,看下它有那些实现类。
这里会走到这个RegistrationBean,为什么会是它,而且看下也没有加上什么注解,它是怎么加载到spring容器中的呢,下面会说。在这个类里有onStartup方法。
它里面有一个注册方法this.register(description, servletContext); ,这个方法实在子类DynamicRegistrationBean中的
protected final void register(String description, ServletContext servletContext) { D registration = this.addRegistration(description, servletContext); if (registration == null) { logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)"); } else { this.configure(registration); } }
这个register方法中又有一个this.addRegistration(description, servletContext); 方法的调用,这个方法还是子类中调用的。子类是ServletRegistrationBean
到这里才真正看到Servlet的影子。
protected Dynamic addRegistration(String description, ServletContext servletContext) { String name = this.getServletName(); return servletContext.addServlet(name, this.servlet); }
上面三个的继承关系如下:
那上面那个servlet哪里来的呢? 这又要说到springboot的自动装配了,在spring.factories中有一个DispatcherServletAutoConfiguration 的配置类。
在这个配置类中有两个重要的地方和DispatcherServlet有关。
首先定义了一个DispatcherServlet
把它和上面的ServletRegistrationBean 关联起来的是下面的一个Bean
这个dispatcherServletRegistration 的bean中会自动注入上面创建的DispatcherServlet,然后把它传递给了DispatcherServletRegistrationBean构造函数。
从这个bean的继承和构造函数的调用就应该能明白,这里把DispatcherServlet传递给了ServletRegistrationBean
这就把上面整个串起来了。
这里贴上上面两个流程的图解,有助于理解。可能图的版本和贴出来的代码版本不一致,但是不影响理解主线的流程。
tomcat内嵌:
dispatcherServlet的装配