SpringBoot启动流程简析(四)
来源:互联网 发布:免费hifi播放软件 编辑:程序博客网 时间:2024/06/03 11:53
在我们之前的web开发中,通常都是将应用打成war包或者将编译之后的应用放到tomcat的webapps目录下(其他的web服务器放到相应的目录下),但是我们在用SpringBoot进行web开发的时候,只是启动了一个main类,然后就会神奇的发现tomcat竟然也被启动了(SpringBoot也内置了Jetty),SpringBoot是怎么做到的呢?下面我将慢慢揭开它的神秘面纱:
我们之前说过在SpringBoot中web的上下文是AnnotationConfigEmbeddedWebApplicationContext这个类,我们先看简单的一下这个类的UML图:
AnnotationConfigEmbeddedWebApplicationContext这个类继承了EmbeddedWebApplicationContext类,GenericWebApplicationContext类(这个要注意),它还实现了BeanDefinitionRegistry这个接口,还实现了ResourceLoader这个接口,这个类可以说是一个全能类了,这个个类我们先不多说,主要是看它的父类EmbeddedWebApplicationContext这个类。在这个类中重写了refresh方法、onRefresh方法、onClose方法、finishRefresh方法。这里我们先看onRefresh这个方法,
@Overrideprotected void onRefresh() { //调用父类的onRefresh方法(GenericWebApplicationContext) super.onRefresh(); try { //创建嵌入式的Servlet容器 createEmbeddedServletContainer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start embedded container", ex); }}
这里我们要重点分析的就是createEmbeddedServletContainer这个方法。
private void createEmbeddedServletContainer() { //先获取embeddedServletContainer EmbeddedServletContainer localContainer = this.embeddedServletContainer; //获取Servlet上下文 ServletContext localServletContext = getServletContext(); //EmbeddedWebApplicationContext没有为EmbeddedServletContainer和ServletContext赋初值,也之前也没有调用set方法,所以这里都是为null if (localContainer == null && localServletContext == null) { //获取EmbeddedServletContainerFactory的实例 1) EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory(); //获取EmbeddedServletContainer 2) this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer()); } else if (localServletContext != null) { try { getSelfInitializer().onStartup(localServletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } //初始化属性信息3) initPropertySources();}
我们先看1)处的代码:
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() { // Use bean names so that we don't consider the hierarchy //从BeanFactory中获取EmbeddedServletContainerFactory类型的Bean,这里没有考虑父BeanFactory String[] beanNames = getBeanFactory() .getBeanNamesForType(EmbeddedServletContainerFactory.class); //如果没有获取到EmbeddedServletContainerFactory类型的Bean,则抛出异常 if (beanNames.length == 0) { throw new ApplicationContextException( "Unable to start EmbeddedWebApplicationContext due to missing " + "EmbeddedServletContainerFactory bean."); } //如果有一个以上的EmbeddedServletContainerFactory类型的Bean,则抛出异常 if (beanNames.length > 1) { throw new ApplicationContextException( "Unable to start EmbeddedWebApplicationContext due to multiple " + "EmbeddedServletContainerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } //从BeanFactory中获取EmbeddedServletContainerFactory类型的Bean return getBeanFactory().getBean(beanNames[0], EmbeddedServletContainerFactory.class); }
上面的这一段代码,干了这样三件事,查看Spring容器中是否有EmbeddedServletContainerFactory类型的Bean,EmbeddedServletContainerFactory类型的Bean是否多于一个,从Spring容器中获取EmbeddedServletContainerFactory类型的Bean,那么EmbeddedServletContainerFactory的Bean是什么时候被注入到Spring容器中的呢?我们先看一下EmbeddedServletContainerFactory的层次结构:
从上图中我们可以看到,在SpringBoot中为我们内置了三种Web服务器的实现类,TomCat、Jetty、Undertow(没接触过)。我们之前说SpringBoot的四大神器,其中之一是自动配置的功能,关于自动配置的内容我们不做更多的展开,在SpringBoot中有这样一个类EmbeddedServletContainerAutoConfiguration
这个类配置在spring.factories中,它的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration,这里你需要知道,在SpringBoot启动的时候会加载这个类,并且这个类上带有Configuration这个注解,所以这个类会被注入到Spring容器中,然后这个类要生效还要有一个条件,即当前环境是web环境!在EmbeddedServletContainerAutoConfiguration中有这样的一段代码:
@Configuration@ConditionalOnClass({ Servlet.class, Tomcat.class })@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)public static class EmbeddedTomcat { @Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { return new TomcatEmbeddedServletContainerFactory(); }}
如果当前类路径下有Servlet和Tomcat这两个类,且在当前上下文中没有EmbeddedServletContainerFactory类型的Bean存在,则创建TomcatEmbeddedServletContainerFactory对象并注入到Spring容器中。SpringBoot中内置了TomCat相关的jar(spring-boot-starter-tomcat)。所以这里注入到Spring容器中的EmbeddedServletContainerFactory类型的Bean是TomcatEmbeddedServletContainerFactory。下面我们接着看2)处的代码:
this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer());
这里的containerFactory是TomcatEmbeddedServletContainerFactory的实例,所以这里调用的也是TomcatEmbeddedServletContainerFactory中的getEmbeddedServletContainer方法,
@Override public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) { //创建一个TomCat对象 Tomcat tomcat = new Tomcat(); //创建web容器base目录 File baseDir = (this.baseDirectory != null ? this.baseDirectory : createTempDir("tomcat")); tomcat.setBaseDir(baseDir.getAbsolutePath()); //创建一个连接器 Connector connector = new Connector(this.protocol); //向tomcat的service中添加连接器 tomcat.getService().addConnector(connector); //定制化Connector customizeConnector(connector); tomcat.setConnector(connector); //自动部署设置为false tomcat.getHost().setAutoDeploy(false); //配置Engine configureEngine(tomcat.getEngine()); //想tomcat中添加其他的Connector for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } //准备ServletContext的上下文 prepareContext(tomcat.getHost(), initializers); //创建TomcatEmbeddedServletContainer return getTomcatEmbeddedServletContainer(tomcat); }
上面的代码是不是看到很晕乎?准确的说上面的内容是TomCat的核心结构部分了。我们先从createTempDir这个简单的方法来开始:
protected File createTempDir(String prefix) { try { //创建临时文件夹 File tempDir = File.createTempFile(prefix + ".", "." + getPort()); //如果之前这个文件夹存在的话,则删除这个文件 tempDir.delete(); //创建临时文件夹 tempDir.mkdir(); //当虚拟机退出的时候删除这个临时文件夹 tempDir.deleteOnExit(); return tempDir; }}
大家第一次看到TomCat这个类的时候是不是有点奇怪,怎么会有一个类叫做TomCat呢?既然敢叫TomCat,那肯定不一般,这个类也确实不一般,它整合了TomCat的整个生命周期,串联了TomCat的各个组件。我们接下来再继续说。