博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringBoot源码解析-内嵌Tomcat容器的启动
阅读量:6921 次
发布时间:2019-06-27

本文共 11901 字,大约阅读时间需要 39 分钟。

tomcat使用简单示范

简单回顾下内嵌tomcat使用,新建一个maven项目,导入如下依赖

javax.annotation
javax.annotation-api
1.3.2
compile
org.apache.tomcat.embed
tomcat-embed-core
9.0.12
compile
tomcat-annotations-api
org.apache.tomcat
org.apache.tomcat.embed
tomcat-embed-el
9.0.12
compile
复制代码

新建一个servlet类,实现对应的方法。

public class HomeServlet extends HttpServlet {    private static final long serialVersionUID = 1L;    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        System.out.println("request scheme: " + req.getScheme());        resp.getWriter().print("hello tomcat");    }}复制代码

在main函数中添加如下代码

public static void main(String[] args) throws Exception {        Tomcat tomcat = new Tomcat();        //设置路径        tomcat.setBaseDir("d:tomcat/dir");        tomcat.getHost().setAutoDeploy(false);        Connector connector = new Connector();        //设置端口        connector.setPort(10086);        tomcat.getService().addConnector(connector);        Context context = new StandardContext();        //设置context路径        context.setPath("");        context.addLifecycleListener(new Tomcat.FixContextListener());        tomcat.getHost().addChild(context);        //添加servlet        tomcat.addServlet("", "homeServlet", new HomeServlet());        //设置servlet路径        context.addServletMappingDecoded("/", "homeServlet");        tomcat.start();        tomcat.getServer().await();    }复制代码

这样的话一个简单的tomcat服务器就启动了,打开浏览器输入localhost:10086,就可以看到servlet中的返回值。

springboot中tomcat容器的启动

还记得前两节讲到里面的配置文件么,配置文件中有一个类,org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration ,进入这个类。

@Configuration@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 {复制代码

发现上面有一个import注解,进入import注解导入的类

@Configuration	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)	public static class EmbeddedTomcat {		@Bean		public TomcatServletWebServerFactory tomcatServletWebServerFactory() {			return new TomcatServletWebServerFactory();		}	}复制代码

根据上一节学习的判断条件可以知道,import注解向spring容器中注入了一个TomcatServletWebServerFactory类,这个类我们先标记着。

回到main函数中,顺着SpringApplication.run(Application.class, args);方法进入AbstractApplicationContext的refresh方法

// Initialize other special beans in specific context subclasses.	onRefresh();复制代码

在onRefresh方法上发现一行注释,在子类方法中初始化特殊的bean。tomcat容器应该算是一个特殊的bean了,所以我们进入子类的onRefresh方法。在子类ServletWebServerApplicationContext发现了这样的代码。

@Override	protected void onRefresh() {		super.onRefresh();		try {			createWebServer();		}		catch (Throwable ex) {			throw new ApplicationContextException("Unable to start web server", ex);		}	}复制代码

猜也能猜到,createWebServer方法就是tomcat初始化的地方了。所以进入方法一探究竟。

private void createWebServer() {		WebServer webServer = this.webServer;		ServletContext servletContext = getServletContext();		//初始化进来,webServer和servletContext两个对象都是null,所以进入if		if (webServer == null && servletContext == null) {			ServletWebServerFactory factory = getWebServerFactory();			this.webServer = factory.getWebServer(getSelfInitializer());		}		else if (servletContext != null) {			...		}		initPropertySources();	}复制代码

首先看一下getWebServerFactory方法。

protected ServletWebServerFactory getWebServerFactory() {		// Use bean names so that we don't consider the hierarchy		String[] beanNames = getBeanFactory()				.getBeanNamesForType(ServletWebServerFactory.class);		if (beanNames.length == 0) {			...		}		if (beanNames.length > 1) {			...		}		return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);	}复制代码

方法逻辑比较简单,获取容器中ServletWebServerFactory类型的实例,并校验其数量,多了或者少了都不行,必须是正好1个。这个时候看一下上面通过自动化配置那边导入spring容器的TomcatServletWebServerFactory类,这个类就是ServletWebServerFactory的子类。所以在没有其他配置的情况下,getWebServerFactory方法,获取到的就是TomcatServletWebServerFactory类。

获取到factory实例后,就来看一下factory的getWebServer方法。

Tomcat tomcat = new 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);		}		配置context		prepareContext(tomcat.getHost(), initializers);		return getTomcatWebServer(tomcat);复制代码

虽然比我们一开始那个示范要复杂许多,但是大致的逻辑还是很清晰的,不难看懂。(这个地方如果不理解的话,你需要补充一下tomcat的知识)

进入getTomcatWebServer方法。

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {		return new TomcatWebServer(tomcat, getPort() >= 0);	}	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 {		synchronized (this.monitor) {			try {				addInstanceIdToEngineName();				Context context = findContext();				context.addLifecycleListener((event) -> {					if (context.equals(event.getSource())							&& Lifecycle.START_EVENT.equals(event.getType())) {						removeServiceConnectors();					}				});				this.tomcat.start();				...				startDaemonAwaitThread();			}			...		}	}复制代码

在getTomcatWebServer方法中,发现了tomcat启动相关的代码,所以这个地方就是tomcat容器启动的地方啦。不过如果你用debug的话,你会发现这个地方即使tomcat启动过后,依然无法访问。因为在启动前spring框架还做了一件事。

Context context = findContext();				context.addLifecycleListener((event) -> {					if (context.equals(event.getSource())							&& Lifecycle.START_EVENT.equals(event.getType())) {						//移除tomcat容器的连接器connector						removeServiceConnectors();					}				});复制代码

因为这个时候作为一个特殊的bean,tomcat容器需要优先初始化,但是此时其他bean还没有初始化完成,连接进来后是无法处理的。所以spring框架在这个地方移除了连接器。

那么被移除的连接器在那个地方启动的呢?在AbstractApplicationContext的refresh方法中,onRefresh方法后面还有一个方法finishRefresh方法。进入子类的这个方法(进入这个方法之前,所有的非lazy属性的bean已经全部完成了初始化)

@Override	protected void finishRefresh() {		super.finishRefresh();		WebServer webServer = startWebServer();		if (webServer != null) {			publishEvent(new ServletWebServerInitializedEvent(webServer, this));		}	}	private WebServer startWebServer() {		WebServer webServer = this.webServer;		if (webServer != null) {			webServer.start();		}		return webServer;	}	public void start() throws WebServerException {		...				addPreviouslyRemovedConnectors();				Connector connector = this.tomcat.getConnector();				if (connector != null && this.autoStart) {					performDeferredLoadOnStartup();				}				...		}	}复制代码

在这个方法中,我们找到了被移除的connector。spring框架将刚刚移除得到连接器又放到tomcat容器中,并且启用了他,这样的话tomcat就可以被访问到了。

tomcat的启动到这儿我们已经了解了,不知道大家有没有发现一个问题,就是我们并没有看到类似示例中添加servlet和设置servlet路径相关的代码。那这部分代码在哪里呢?

回到刚刚factory的getWebServer方法。这个方法中传入了一个参数getSelfInitializer()我们看一下这个参数是啥。

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {		return this::selfInitialize;	}	private void selfInitialize(ServletContext servletContext) throws ServletException {		prepareWebApplicationContext(servletContext);		registerApplicationScope(servletContext);		WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),				servletContext);		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {			beans.onStartup(servletContext);		}	}复制代码

这个lambda表达式应该还很好理解吧,返回了一个ServletContextInitializer实例,该实例的onStartup方法就是调用了这边的selfInitialize方法。这个selfInitialize方法里,最关键的就是getServletContextInitializerBeans方法了。但是我们从这边分析代码的话,其实不太看得出来getServletContextInitializerBeans到底获取到了那些类,所以可以取巧一下,使用IDEA的debug功能。借助debug我们看到了这边获取到的几个类,关键的是DispatcherServletRegistrationBean。也就是这个地方会调用DispatcherServletRegistrationBean的onStartup方法。

那么他的onStartup到底干了那些事呢?

@Override	public final void onStartup(ServletContext servletContext) throws ServletException {		String description = getDescription();		if (!isEnabled()) {			logger.info(StringUtils.capitalize(description)					+ " was not registered (disabled)");			return;		}		register(description, servletContext);	}	@Override	protected final void register(String description, ServletContext servletContext) {		D registration = addRegistration(description, servletContext);		if (registration == null) {			logger.info(StringUtils.capitalize(description) + " was not registered "					+ "(possibly already registered?)");			return;		}		configure(registration);	}	@Override	protected ServletRegistration.Dynamic addRegistration(String description,			ServletContext servletContext) {		String name = getServletName();		//这个地方将servlet添加进了context		return servletContext.addServlet(name, this.servlet);	}	@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)) {		//这个方法则对servlet的路径进行了配置			registration.addMapping(urlMapping);		}		registration.setLoadOnStartup(this.loadOnStartup);		if (this.multipartConfig != null) {			registration.setMultipartConfig(this.multipartConfig);		}	}复制代码

既然知道了ServletContextInitializer的作用,那么我们就追踪一下这个ServletContextInitializer被放置到了什么地方,何时调用他的方法。

@Override	public WebServer getWebServer(ServletContextInitializer... initializers) {		...		prepareContext(tomcat.getHost(), initializers);		return getTomcatWebServer(tomcat);	}	protected void prepareContext(Host host, ServletContextInitializer[] initializers) {		...		ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);		...		configureContext(context, initializersToUse);		...	}	protected void configureContext(Context context,			ServletContextInitializer[] initializers) {		TomcatStarter starter = new TomcatStarter(initializers);		if (context instanceof TomcatEmbeddedContext) {			TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;			embeddedContext.setStarter(starter);			embeddedContext.setFailCtxIfServletStartFails(true);		}		context.addServletContainerInitializer(starter, NO_CLASSES);		...	}复制代码

可以看到ServletContextInitializer被包装成了一个TomcatStarter放入了context中。在context的start方法里,我们就可以看到initializers的启动(这个地方涉及到tomcat容器的启动,如果不熟悉的话可以回顾下)。

@Override    protected synchronized void startInternal() throws LifecycleException {            ...            // Call ServletContainerInitializers            for (Map.Entry
>> entry : initializers.entrySet()) { try { entry.getKey().onStartup(entry.getValue(), getServletContext()); } catch (ServletException e) { log.error(sm.getString("standardContext.sciFail"), e); ok = false; break; } } ... }复制代码

总结

经过这几轮的分析,从SpringApplication的启动,到自动化配置,再到今天的tomcat容器的启动。我们已经窥探到了整个springboot框架的全貌。所以后面就需要对常用功能定点学习了。


转载于:https://juejin.im/post/5ca1fa2bf265da30a726e26e

你可能感兴趣的文章
C语音中关键字static的作用及类的static属性
查看>>
Hibernate映射配置:数据类型映射
查看>>
LCD驱动 15 -2
查看>>
机器学习实战之k-近邻算法(3)---如何可视化数据
查看>>
API测试利器——Postman(2. 理解和处理响应)
查看>>
大叔也说Xamarin~Android篇~调用远程API接口,发POST请求
查看>>
April Flags_Schedule
查看>>
搭建git服务器
查看>>
获取客户端电脑名称
查看>>
将系统从.Net Core2.0升级到.Net Core2.1
查看>>
JavaScript学习基础
查看>>
Volatile关键字
查看>>
理解 python 中__name__ = '__main__' 的作用
查看>>
poj 求二叉树的结点
查看>>
总结:canvas与svg的介绍以及其区别
查看>>
iOS学习之Object-C语言属性和点语法(转载收集)
查看>>
mysql20170404代码实现
查看>>
Hibernate Validator
查看>>
再谈git和github-深入理解-3
查看>>
kindeditor4.1.11的使用方法
查看>>