当前位置: 移动技术网 > IT编程>开发语言>Java > SpringBoot加载web项目流程源码解析

SpringBoot加载web项目流程源码解析

2020年07月22日  | 移动技术网IT编程  | 我要评论

​上一篇文章中我们写了一个极简版的SpringBoot,基本完成了Controller的映射和访问,真实的SpringBoot是如何来做的?我们以2.3.0.RELEASE版本的SpringBoot为例来看下它是如何一步一步创建内嵌的tomcat并且注册DispatcherServlet的,它是否跟上篇文章中讲的一样,也是利用了Servlet3.0的ServletContainerInitializer和WebApplicationInitializer呢? 友情提示:公众号的排版对源码的文章非常不友好,建议直接上IDEA。
首先从SpringApplication#run方法开始:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

继续往下走,一直到:

public ConfigurableApplicationContext run(String... args) {try {//这里会创建AnnotationConfigServletWebServerApplicationContext
		context = createApplicationContext();
		//重点看下这里,其实里面是调用的context.refresh();
		refreshContext(context);}return context;
}

SpringApplication#refresh:

protected void refresh(ConfigurableApplicationContext applicationContext) {
	//这个就是前面创建出来的AnnotationConfigServletWebServerApplicationContext
	applicationContext.refresh();
}

这个refresh就是spring核心的那个refresh方法,因为AnnotationConfigServletWebServerApplicationContext继承了ServletWebServerApplicationContext,因此,会调用到ServletWebServerApplicationContext#refresh:

@Override
public final void refresh() throws BeansException, IllegalStateException {
	try {
		super.refresh();
	}
	catch (RuntimeException ex) {
		WebServer webServer = this.webServer;
		if (webServer != null) {
			webServer.stop();
		}
		throw ex;
	}
}

通过上面的代码也能很容易的看出来,里面肯定创建了webServer,也就是tomcat,它的refresh实际上啥也没干,直接是调用的父类的refresh,也就是AbstractApplicationContext#refresh,熟悉spring的小伙伴对这个方法应该非常熟悉,这个方法就是用来初始化整个spring的环境的,做的事情很多,我们就不再做太多介绍,里面会执行到一行onRefresh(),因为ServletWebServerApplicationContext重写了onRefresh():

@Override
protected void onRefresh() {
	super.onRefresh();
	try {
		//就是在这里创建的webserver
		createWebServer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}

ServletWebServerApplicationContext#createWebServer():

private void createWebServer() {
	WebServer webServer = this.webServer;
	ServletContext servletContext = getServletContext();
	if (webServer == null && servletContext == null) {
		ServletWebServerFactory factory = getWebServerFactory();
		//这里真正去创建tomcat
		this.webServer = factory.getWebServer(getSelfInitializer());
		。。。
	}
	。。。
}

这里首先是调用了getSelfInitializer(),然后把拿到的ServletContextInitializer传递给getWebServer()方法,注意下ServletContextInitializer,SpringBoot就是用这个接口来完成类似Servlet3.0的ServletContainerInitializer的功能的,进去到getWebServer()里面,TomcatServletWebServerFactory#getWebServer():

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());
	Connector connector = new Connector(this.protocol);
	connector.setThrowOnFailure(true);
	//添加了connector
	tomcat.getService().addConnector(connector);
	//在这里设置了connector的端口号
	customizeConnector(connector);
	tomcat.setConnector(connector);
	tomcat.getHost().setAutoDeploy(false);
	configureEngine(tomcat.getEngine());
	for (Connector additionalConnector : this.additionalTomcatConnectors) {
		tomcat.getService().addConnector(additionalConnector);
	}
	//这里会把ServletContextInitializer传进去
	prepareContext(tomcat.getHost(), initializers);
	return getTomcatWebServer(tomcat);
}

看下TomcatServletWebServerFactory#prepareContext():

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {//这里又添加了2个ServletContextInitializer
	ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
	host.addChild(context);
	//重点看下这里
	configureContext(context, initializersToUse);
	postProcessContext(context);
}

TomcatServletWebServerFactory#configureContext():

protected void configureContext(Context context, ServletContextInitializer[] initializers) {
	//这里创建了一个TomcatStarter,传递进去ServletContextInitializer
	TomcatStarter starter = new TomcatStarter(initializers);
	//TomcatEmbeddedContext继承了tomcat里面的StandardContext
	if (context instanceof TomcatEmbeddedContext) {
		TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
		//把TomcatStarter添加到StandardContext的一个成员变量starter中
		embeddedContext.setStarter(starter);
		embeddedContext.setFailCtxIfServletStartFails(true);
	}
	//把TomcatStarter添加到StandardContext的initializers中,
	//initializers是一个Map
	context.addServletContainerInitializer(starter, NO_CLASSES);
	。。。
}

TomcatStarter是一个非常关键的类,它实现了ServletContainerInitializer接口,这个接口是servlet提供的,因此TomcatStarter是连接SpringBoot和tomcat的一个关键纽带。
继续往下执行就到了TomcatServletWebServerFactory#getTomcatWebServer():

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
	return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {.
	//这里会去启动tomcat
	initialize();
}

TomcatWebServer#initialize():

private void initialize() throws WebServerException {
	logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
	。。。
	// 启动tomcat
	this.tomcat.start();
	//启动一个阻塞线程,防止tomcat退出
	startDaemonAwaitThread();
。。。
}

Tomcat启动以后,会执行到tomcat里面的StandardContext#startInternal():

protected synchronized void startInternal() throws LifecycleException {
	...
	// initializers是前面存进去的,里面只有一个TomcatStarter
	for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
		initializers.entrySet()) {
		try {
			//这里会调用TomcatStarter的onStartup()
			//TomcatStarter里面有所有的ServletContextInitializer,因此这里就会回调到SpringBoot里面的ServletContextInitializer里面去
		    entry.getKey().onStartup(entry.getValue(),
		            getServletContext());
		} catch (ServletException e) {
		    log.error(sm.getString("standardContext.sciFail"), e);
		    ok = false;
		    break;
		}
	}
	...
}

TomcatStarter#onStartup():

@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
	try {
		// initializers也是前面存进去的,里面一共3个,其中一个是ServletWebServerApplicationContext里面的selfInitialize
		for (ServletContextInitializer initializer : this.initializers) {
			initializer.onStartup(servletContext);
		}
	}
	catch (Exception ex) {
		...
	}
}

ServletWebServerApplicationContext#selfInitialize():

private void selfInitialize(ServletContext servletContext) throws ServletException {
	prepareWebApplicationContext(servletContext);
	registerApplicationScope(servletContext);
	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
	//这里面就拿到了DispatcherServletRegistrationBean
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		beans.onStartup(servletContext);
	}
}

首先看下ServletWebServerApplicationContext#getServletContextInitializerBeans():

protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
	return new ServletContextInitializerBeans(getBeanFactory());
}
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
		Class<? extends ServletContextInitializer>... initializerTypes) {
	this.initializers = new LinkedMultiValueMap<>();
	this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
			: Collections.singletonList(ServletContextInitializer.class);
	//这里去加载DispatcherServletRegistrationBean
	addServletContextInitializerBeans(beanFactory);
	addAdaptableBeans(beanFactory);
	List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
			.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
			.collect(Collectors.toList());
	this.sortedList = Collections.unmodifiableList(sortedInitializers);
	logMappings(this.initializers);
}
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
	for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
		for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,initializerType)) {
			//这里就有DispatcherServletRegistrationBean
			addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
		}
	}
}

拿到了DispatcherServletRegistrationBean,上面继续执行它的onStartup(),RegistrationBean#onStartup():

@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
	//注册DispatcherServlet
	register(description, servletContext);
}

DynamicRegistrationBean# register():

@Override
protected final void register(String description, ServletContext servletContext) {
	//注册DispatcherServlet
	D registration = addRegistration(description, servletContext);
	if (registration == null) {
		logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
		return;
	}
	//设置映射路径和启动级别
	configure(registration);
}

ServletRegistrationBean#configure():

@Override
protected void configure(ServletRegistration.Dynamic registration) {
	super.configure(registration);
	//设置映射路径
	String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
	if (urlMapping.length == 0 && this.alwaysMapUrl) {
		urlMapping = DEFAULT_MAPPINGS;
	}
	if (!ObjectUtils.isEmpty(urlMapping)) {
		registration.addMapping(urlMapping);
	}
	//设置启动级别
	registration.setLoadOnStartup(this.loadOnStartup);
	if (this.multipartConfig != null) {
		registration.setMultipartConfig(this.multipartConfig);
	}
}

至此,整个Spring环境和DispatcherServlet就都加载起来了。
现在还剩下一个问题,DispatcherServletRegistrationBean是如何加载到Spring的容器的呢?
在spring-boot-autoconfigure-**.jar的META-INF下面的spring.factories里面配置了一个DispatcherServletAutoConfiguration,在这里面去创建了DispatcherServletRegistrationBean。
(1)SpringBoot首先扫描classpath,根据里面是否有web相关的类去创建了AnnotationConfigServletWebServerApplicationContext。
(2)AnnotationConfigServletWebServerApplicationContext在它的refresh()里面回调了ServletWebServerApplicationContext的onRefresh(),在这里面去创建了tomcat。
(3)SpringBoot把实现了ServletContextInitializer接口的实现类传递到了TomcatStarter里面,TomcatStarter是由SpringBoot提供的,它同时实现了ServletContainerInitializer接口,而ServletContainerInitializer是Servlet提供的,因此,TomcatStarter是一个非常关键的纽带。
(4)当tomcat启动以后,内部会调用所有的ServletContainerInitializer的onStartup(),因此就回调到了TomcatStarter的onStartup(),进而回调到了ServletContextInitializer里面。
(5)在ServletWebServerApplicationContext里面的一个ServletContextInitializer里面去加载了DispatcherServletRegistrationBean,在它的onStartup()回调里面去创建了DispatcherServlet,并且设置了映射路径和启动级别。
(6)整个启动流程可以看出来,SpringBoot并不是向SpringMVC那样通过回调ServletContainerInitializer来完成加载。SpringBoot是直接生成了它的一个叫TomcatStarter的子类,然后在其中把对ServletContainerInitializer的回调转移到对ServletContextInitializer的回调,然后在ServletContextInitializer中去加载的DispatcherServlet。

欢迎扫码查看更多文章:
qrcode.jpg

本文地址:https://blog.csdn.net/goldenfish1919/article/details/107456809

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网