前言:springboot相信基本上所有的人都使用过,但是对于一些初学者可能只是知道如何使用,但是对于它实现的原理不太熟悉,今天跟大家一起去分析下它的启动源码。其实也是比较简单,相信通过这篇文章能对一些初学者有一些帮助,在学习这篇文章之前最好有spring的基础知识。
一、引入问题
今天我们是以web应用为例来分析springboot的启动原理,首先我们看如下的代码
代码语言:javascript复制@SpringBootApplication
public class HellobootApplication {
public static void main(String[] args) {
SpringApplication.run(HellobootApplication.class, args);
}
}
我们知道当我们run这个main方法时,web应用程序就能启动。那么首先请大家思考如下问题
1、springboot如何和spring容器关联上
2、我们没有看到有tomcat容器,为什么能够支撑起web应用
3、当我们引用第三方的starter时,为什么会自动实例化一些类,我们并没有扫描到第三方的包,甚至我们对第三方的包的路径都不知道
带着上面三个问题,我们一起来看下springboot的启动的原理吧
二、源码分析(1)
首先分析上面第一个问题(springboot如何和spring容器关联上)
SpringApplication#run()
代码语言:javascript复制public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
//看这里
return run(new Class<?>[] {
primarySource }, args);
}
run()
代码语言:javascript复制public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
//看这个run方法
return new SpringApplication(primarySources).run(args);
}
继续记者往下看
run()
代码语言:javascript复制public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
//从这个方法名字看,大概就能猜测出来意思了,这里就是创建spring的context
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] {
ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//这个方法名字,看起来是不是很熟悉
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
这个方法,我们重点看两个地方
(1)createApplicationContext()
(2)refreshContext(context)
如果对spring源码有了解的话,我们大概能猜到这两个方法的意思,第一个时创建spring上下文,第二个就是刷新上下文,这两个地方其实就实现了springboot–>spring容器
比如我们单元测试的时候,经常编写如下代码进行启动spring容器
代码语言:javascript复制ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("xxx.xml")
1、我们先看createApplicationContext()方法
代码语言:javascript复制protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
//重点看加载的这个class的路径
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
"please specify an ApplicationContextClass",
ex);
}
}
//后面就是通过反射创建ApplicationContext上下文了,后面不再继续跟了
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
这里我们重点看一下加载的是哪个ApplicationContext的上下文,因为ApplicationContext有很多子类,我们看下常量(DEFAULT_SERVLET_WEB_CONTEXT_CLASS)对应的class是哪个
代码语言:javascript复制/** * The class name of application context that will be used by default for web * environments. */
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
"web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
所以这里创建的ApplicationContext的上下文是AnnotationConfigServletWebServerApplicationContext,这里很重要,大家先记一下(不同的ApplicationContext有些方法实现的细节是不一样的)
2、接着看上面的refreshContext(context)
代码语言:javascript复制private void refreshContext(ConfigurableApplicationContext context) {
//继续看该方法
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
refresh()
代码语言:javascript复制protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//这里就调用了spring的context包中的容器刷新了
((AbstractApplicationContext) applicationContext).refresh();
}
分析到这里,相信大家都知道springboot如何和spring容器相关联的了
三、源码分析(2)
上面介绍了springboot如何与spring容器进行关联的,接着我们看web应用时,我们没有将应用放入tomcat中时,为何能实现web应用
上面有一个地方,我让大家重点记住创建ApplicationContext时,到底是创建了哪个ApplicationContext的子类,其实是创建了AnnotationConfigServletWebServerApplicationContext那么我们来看下ApplicationContext.refresh()方法
AbstractApplicationContext.refresh()
代码语言:javascript复制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.
//咱们重点看这里,上面的英文注释写的也很详细,可以在子类中实例化其他bean
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();
}
}
}
这个方法我相信对spring源码有了解的人都非常熟悉这个方法,这个方法就是spring代码的入口,非常重要。这里其实是实现了模板方法设计模式,这里是定义了基本的骨架,有些方法可以在不同的子类有不同的实现,我们上面看到创建的springboot的web应用程序默认创建的是AnnotationConfigServletWebServerApplicationContext,我们来看下它的onRefresh()方法(在他的父类ServletWebServerApplicationContext中)
ServletWebServerApplicationContext.onRefresh()
代码语言:javascript复制@Override
protected void onRefresh() {
super.onRefresh();
try {
//重点看这里
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
咱们重点看createWebServer(),这里从方法名就能看出创建web服务
代码语言:javascript复制private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
//创建web服务在这里,这里是工厂方法
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
上面是工厂方法,有Jetty和tomcat,我们这里点tomcat进去看看
TomcatServletWebServerFactory.getWebServer()
代码语言:javascript复制@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
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);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
这里相信大家就很清晰了,这里自己创建了一个Tomcat,最后会调用tomcat.start()这里我就不继续往下跟了,感兴趣的可以继续往下看看。另外如果感兴趣的同学可以看看这里的Tomcat的设置和我们下载的tomcat中的server.xml文件进行对比一下,看看有什么相似的地方
这篇文章就先分析到这里吧,还留了一个最后一个问题(如何自动加载第三方的starter),我们到下一篇文章再详细的分析
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/111213.html原文链接:https://javaforall.cn