spring-boot源码解析三之集成tomcat加载流程


抛出几个问题!
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
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值