最近面试中会聊到这个话题,说实话之前已经做过大量spring boot源码级的分享,如果你在之前看过Spring Boot 容器启动原理揭秘,想必已经有了一个大概的认识。启动tomcat或jetty其实也跳不出这个套路,或者讲它仅仅是其中的一个环节,通过AbstractApplicationContext.onRefresh()完成的。
知识回顾
Spring Boot的一个及其吸引人的特性是fat-jar技术,解下jar
解压包的MANIFEST.MF文件我们会发现Main-Class
及Start-Class,而Start-Class就是我们程序中的main
函数类DemoApplication。
SpringApplication
而这个启动类中重点关注SpringApplication类,有两个非常重要的方法:
public class SpringApplication {
public SpringApplication(Object... sources) {
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.initialize(sources);//关注点
}
//初始化启动参数变量
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = this.deduceWebEnvironment();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
//启动ioc容器与tomcat容器
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[]{source}, args);
}
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return (new SpringApplication(sources)).run(args);
}
//run真正执行的方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
Banner printedBanner = this.printBanner(environment);
//创建ioc容器
context = this.createApplicationContext();
new FailureAnalyzers(context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新ioc容器上下文
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
listeners.finished(context, (Throwable)null);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
throw new IllegalStateException(var9);
}
}
private void refreshContext(ConfigurableApplicationContext context) {
this.refresh(context);//此次很关键
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException var3) {
;
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//这个一定不陌生吧
((AbstractApplicationContext)applicationContext).refresh();
}
//会创建ioc为AnnotationConfigEmbeddedWebApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
Class> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class.forName(this.webEnvironment ? "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext" : "org.springframework.context.annotation.AnnotationConfigApplicationContext");
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
return (ConfigurableApplicationContext)BeanUtils.instantiate(contextClass);
}
}
要解答本文的疑问,问题有回到了AbstractApplicationContext.refresh(),如果你对它里面干的事情比较熟悉的话,后续可以不用继续看下去了。
AbstractApplicationContext.refresh()
它主要是通过工厂类EmbeddedServletContainerFactory生成一个EmbeddedServletContainer实例,再调用start方法进行启动,最终完成tomcat、jetty的启动。
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean {
//ioc的最重要的一些组件
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();//重点关注这里
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();//重点关注这里
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
protected void onRefresh() throws BeansException {//没错,实现在子类中
// For subclasses: do nothing by default.
}
}
public class AnnotationConfigEmbeddedWebApplicationContext extends EmbeddedWebApplicationContext {
}
public class EmbeddedWebApplicationContext extends GenericWebApplicationContext {
//重写了AbstractApplicationContext的onRefresh
protected void onRefresh() {
super.onRefresh();
try {
this.createEmbeddedServletContainer();//看名字是不是有种柳暗花明的感觉
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start embedded container", var2);
}
}
//没错就是创建tomcat的核心类EmbeddedServletContainer
private void createEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
ServletContext localServletContext = this.getServletContext();
if (localContainer == null && localServletContext == null) {
EmbeddedServletContainerFactory containerFactory = this.getEmbeddedServletContainerFactory();
this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(new ServletContextInitializer[]{this.getSelfInitializer()});
} else if (localServletContext != null) {
try {
this.getSelfInitializer().onStartup(localServletContext);
} catch (ServletException var4) {
throw new ApplicationContextException("Cannot initialize servlet context", var4);
}
}
this.initPropertySources();
}
//获取Sevlet工程中获取web容器
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
String[] beanNames = this.getBeanFactory().getBeanNamesForType(EmbeddedServletContainerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.");
} else if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start EmbeddedWebApplicationContext due to multiple EmbeddedServletContainerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
} else {
return (EmbeddedServletContainerFactory)this.getBeanFactory().getBean(beanNames[0], EmbeddedServletContainerFactory.class);
}
}
//重写了AbstractApplicationContext的finishRefresh
protected void finishRefresh() {
super.finishRefresh();
//关注点
EmbeddedServletContainer localContainer = this.startEmbeddedServletContainer();
if (localContainer != null) {
this.publishEvent(new EmbeddedServletContainerInitializedEvent(this, localContainer));
}
}
//启动tomcat
private EmbeddedServletContainer startEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
if (localContainer != null) {
localContainer.start();//start方法
}
return localContainer;
}
}
EmbeddedServletContainerFactory
此时你觉得EmbeddedServletContainerFactory有点突兀,它从哪里来,又是做什么的呢?首先看它的实现:
它来自spring的SPI(spring.factories),Auto Configure的配置key=EnableAutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {//这就是为什么可以在ioc中可以获取了
return new TomcatEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Jetty is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
WebAppContext.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Undertow is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
return new UndertowEmbeddedServletContainerFactory();
}
}
}
public class TomcatEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private static final Set> NO_CLASSES = Collections.emptySet();
public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
private File baseDirectory;
private List engineValves = new ArrayList();
private List contextValves = new ArrayList();
private List contextLifecycleListeners = new ArrayList();
private List tomcatContextCustomizers = new ArrayList();
private List tomcatConnectorCustomizers = new ArrayList();
private List additionalTomcatConnectors = new ArrayList();
private ResourceLoader resourceLoader;
private String protocol = "org.apache.coyote.http11.Http11NioProtocol";
private Set tldSkipPatterns;
private Charset uriEncoding;
private int backgroundProcessorDelay;
public TomcatEmbeddedServletContainerFactory() {
this.tldSkipPatterns = new LinkedHashSet(TldSkipPatterns.DEFAULT);
this.uriEncoding = DEFAULT_CHARSET;
}
//上面的调用:创建Tomcat的容器
public EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
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);
}
this.prepareContext(tomcat.getHost(), initializers);
return this.getTomcatEmbeddedServletContainer(tomcat);
}
}
//Tomcat的容器
public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {
public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
this.monitor = new Object();
this.serviceConnectors = new HashMap();
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.initialize();//关键点
}
//tomcat初始化
private void initialize() throws EmbeddedServletContainerException {
logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
Object var1 = this.monitor;
synchronized(this.monitor) {
try {
this.addInstanceIdToEngineName();
try {
final Context context = this.findContext();
context.addLifecycleListener(new LifecycleListener() {
public void lifecycleEvent(LifecycleEvent event) {
if (context.equals(event.getSource()) && "start".equals(event.getType())) {
TomcatEmbeddedServletContainer.this.removeServiceConnectors();
}
}
});
this.tomcat.start();
this.rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, this.getNamingToken(context), this.getClass().getClassLoader());
} catch (NamingException var5) {
;
}
this.startDaemonAwaitThread();
} catch (Exception var6) {
containerCounter.decrementAndGet();
throw var6;
}
} catch (Exception var7) {
this.stopSilently();
throw new EmbeddedServletContainerException("Unable to start embedded Tomcat", var7);
}
}
}
//启动方法
public void start() throws EmbeddedServletContainerException {
Object var1 = this.monitor;
synchronized(this.monitor) {
if (!this.started) {
boolean var10 = false;
try {
var10 = true;
this.addPreviouslyRemovedConnectors();
Connector var2 = this.tomcat.getConnector();
if (var2 != null && this.autoStart) {
this.performDeferredLoadOnStartup();
}
this.checkThatConnectorsHaveStarted();
this.started = true;
logger.info("Tomcat started on port(s): " + this.getPortsDescription(true));
var10 = false;
} catch (ConnectorStartFailedException var11) {
this.stopSilently();
throw var11;
} catch (Exception var12) {
throw new EmbeddedServletContainerException("Unable to start embedded Tomcat servlet container", var12);
} finally {
if (var10) {
Context context = this.findContext();
ContextBindings.unbindClassLoader(context, this.getNamingToken(context), this.getClass().getClassLoader());
}
}
Context context = this.findContext();
ContextBindings.unbindClassLoader(context, this.getNamingToken(context), this.getClass().getClassLoader());
}
}
}
}
Spring Boot里面知识点其实是非常多的,很多时候我们在研究源码的时候,一定要大胆猜测,最后通过调试来完成最终的验证。