此系列是针对springboot的启动,旨在于和你们一块儿来看看springboot启动的过程当中到底作了一些什么事。若是你们对springboot的源码有所研究,能够挑些本身感兴趣或者对本身有帮助的看;可是若是你们没有研究过springboot的源码,不知道springboot在启动过程当中作了些什么,那么我建议你们从头开始一篇一篇按顺序读该系列,不至于从中途插入,看的有些懵懂。固然,文中讲的不对的地方也欢迎你们指出,有待改善的地方也但愿你们不吝赐教。老规矩:一周至少一更,中途会不按期的更新一些其余的博客,多是springboot的源码,也多是其余的源码解析,也有多是其余的。html
路漫漫其修远兮,吾将上下而求索!java
github:https://github.com/youzhibinggit
码云(gitee):https://gitee.com/youzhibinggithub
你们还记得上篇博文讲了什么吗,或者说你们知道上篇博文讲了什么吗。这里帮你们作个简单回顾,主要作了两件事web
一、加载外部化配置的资源到environmentspring
包括命令行参数、servletConfigInitParams、servletContextInitParams、systemProperties、sytemEnvironment、random、application.yml(.yaml/.xml/.properties),以下所示编程
二、广播ApplicationEnvironmentPreparedEvent事件,触发相应的监听器缓存
ConfigFileApplicationListenerspringboot
添加名叫random的RandomValuePropertySource到environmentapp
添加名叫applicationConfig:[classpath:/application.yml]的OriginTrackedMapPropertySource到environment
LoggingApplicationListener
初始化日志系统
先欣赏下咱们的战绩,看看咱们对run方法完成了多少的源码解读
/** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */ public ConfigurableApplicationContext run(String... args) { // 秒表,用于记录启动时间;记录每一个任务的时间,最后会输出每一个任务的总费时 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // spring应用上下文,也就是咱们所说的spring根容器 ConfigurableApplicationContext context = null; // 自定义SpringApplication启动错误的回调接口 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // 设置jdk系统属性java.awt.headless,默认状况为true即开启 configureHeadlessProperty(); // 获取启动时监听器(EventPublishingRunListener实例) SpringApplicationRunListeners listeners = getRunListeners(args) // 触发ApplicationStartingEvent事件,启动监听器会被调用,一共5个监听器被调用,但只有两个监听器在此时作了事 listeners.starting(); try { // 参数封装,也就是在命令行下启动应用带的参数,如--server.port=9000 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); // 准备环境:一、加载外部化配置的资源到environment;二、触发ApplicationEnvironmentPreparedEvent事件 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 配置spring.beaninfo.ignore,并添加到名叫systemProperties的PropertySource中;默认为true即开启 configureIgnoreBeanInfo(environment); // 打印banner图 Banner printedBanner = printBanner(environment); // 建立应用上下文,这是本文重点 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; }
configureIgnoreBeanInfo(environment);
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) { if (System.getProperty( CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) { Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE); System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString()); } }
配置spring.beaninfo.ignore,并添加到名叫systemProperties的PropertySource中,默认为true即开启,如上图所示。至于spring.beaninfo.ignore配置这个有什么用,何时用,暂时还没体现,后续应该会有所体现,咱们暂时先将其当作一个疑问放着。
printBanner(environment);
private Banner printBanner(ConfigurableEnvironment environment) { if (this.bannerMode == Banner.Mode.OFF) { return null; } ResourceLoader resourceLoader = (this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader(getClassLoader())); SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter( resourceLoader, this.banner); if (this.bannerMode == Mode.LOG) { return bannerPrinter.print(environment, this.mainApplicationClass, logger); } return bannerPrinter.print(environment, this.mainApplicationClass, System.out); }
打印banner图,就是下面这个图
并返回Banner对象,后续还会用到。
经过前面两道前菜,我相信咱们已经胃口大开了,那么请开始咱们的正餐 - createApplicationContext
源代码
/** * Strategy method used to create the {@link ApplicationContext}. By default this * method will respect any explicitly set application context or application context * class before falling back to a suitable default. * @return the application context (not yet refreshed) * @see #setApplicationContextClass(Class) */ protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch (this.webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_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); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); }
根据SpringApplication的webApplicationType来实例化对应的上下文;若是webApplicationType的值是SERVLET,那么实例化AnnotationConfigServletWebServerApplicationContext,若是是REACTIVE则实例化AnnotationConfigReactiveWebServerApplicationContext(响应式编程,后续再看),若是既不是SERVLET、也不是REACTIVE,那么则是默认状况(也就是咱们所说的非web引用),实例化AnnotationConfigApplicationContext。还记得webApplicationType的值是怎么获取的吗,请点这里。很显然咱们目前的应用类型是SERVLET,那么实例化AnnotationConfigServletWebServerApplicationContext。
利用反射调用AnnotationConfigServletWebServerApplicationContext的构造方法进行实例化,期间有对构造方法进行可访问性设置,同时还进行了Kotlin的校验。咱们目前应用中不涉及Kotlin,先放着不用看。
AnnotationConfigServletWebServerApplicationContext类图
AnnotationConfigServletWebServerApplicationContext父级类
从类图中咱们可知,类结构比较深,咱们从上往下来看各个父类的构造方法(实现的接口先不看),看看构造方法体里面所作的事。
DefaultResourceLoader,默认资源加载器
获取默认的类加载器,获取的是当前线程的上下文类加载器。
AbstractApplicationContext,抽象应用上下文
初始化属性resourcePatternResolver,也就是资源模式解析器;实际类型是PathMatchingResourcePatternResolver,它是基于模式匹配的,默认使用AntPathMatcher进行路径匹配,它除了支持ResourceLoader支持的前缀外,还额外支持“classpath*:”用于加载全部匹配的类路径Resource。
另外beanFactoryPostProcessors属性此时已经初始化了,后续确定会用到,你们注意下。
GenericApplicationContext,通用应用上下文
初始化属性beanFactory,其类型是DefaultListableBeanFactory,DefaultListableBeanFactory类图以下
DefaultListableBeanFactory的父级类
咱们根据上图,从上往下读
SimpleAliasRegistry,简单别名注册器
没有明确的定义构造方法,也就是只有默认的无参构造方法,咱们可认为只是实例化了本身。
DefaultSingletonBeanRegistry,默认单例bean注册器,用于注册共享的单例bean
没有明确的定义构造方法,也就是只有默认的无参构造方法,咱们可认为只是实例化了本身。
FactoryBeanRegistrySupport,工厂bean注册器支持,用于注册工厂bean单例
没有明确的定义构造方法,也就是只有默认的无参构造方法,咱们可认为只是实例化了本身。
AbstractBeanFactory,抽象bean工厂
无参构造方法体内为空,咱们可认为只是实例化了本身。
AbstractAutowireCapableBeanFactory,抽象的有自动装配装配能力的bean工厂,赋予了自动装配功能
该类提供bean建立(具备构造函数解析),属性填充,接线(包括自动装配)和初始化。 处理运行时bean引用,解析托管集合,调用初始化方法等。支持自动装配构造函数,按名称的属性和按类型的属性。
无参构造方法中,添加了三个非自动装配的接口:BeanNameAware、BeanFactoryAware和BeanClassLoaderAware。
DefaultListableBeanFactory,ListableBeanFactory的默认实现
该类用于注册全部bean定义、也可用做独立的bean工厂,固然也能够用做咱们自定义bean工厂的父类。
无参构造方法中也只是调用了super(),咱们可认为只是实例化了本身。
GenericWebApplicationContext,通用web应用上下文,在GenericApplicationContext基础上增长web支持
无参构造方法中,只是调用了super(),咱们可认为只是实例化了本身。
ServletWebServerApplicationContext,servlet web服务应用上下文,可以从自身引导,建立,初始化和运行WebServer
无参构造方法中是空内容,咱们可认为只是实例化了本身。
DefaultListableBeanFactory类图中,有不少类的属性值得咱们留意,好比SimpleAliasRegistry的aliasMap、DefaultSingletonBeanRegistry的singletonObjects、singletonFactories和earlySingletonObjects、FactoryBeanRegistrySupport的factoryBeanObjectCache、AbstractBeanFactory的beanPostProcessors、AbstractAutowireCapableBeanFactory的ignoredDependencyInterfaces、DefaultListableBeanFactory中的属性beanDefinitionMap和beanDefinitionNames。
AnnotationConfigServletWebServerApplicationContext类图中,也有不少类的属性值得咱们留意,好比AbstractApplicationContext的beanFactoryPostProcessors、GenericApplicationContext的beanFactory(就是DefaultListableBeanFactory)、GenericWebApplicationContext的servletContext、ServletWebServerApplicationContext的webServer和servletConfig。
AnnotationConfigServletWebServerApplicationContext构造方法
/** * Create a new {@link AnnotationConfigServletWebServerApplicationContext} that needs * to be populated through {@link #register} calls and then manually * {@linkplain #refresh refreshed}. */ public AnnotationConfigServletWebServerApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); // 实例化注解bean定义读取器 this.scanner = new ClassPathBeanDefinitionScanner(this); // 实例化类路径bean定义扫描器 }
构造方法中的内容也比较简单,就是实例化两个bean,并赋值给本身的属性。咱们接着往下看,AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner究竟是什么,构造方法中到底作了什么?
AnnotatedBeanDefinitionReader
从类注释上来看,做用就是用于编程式注解bean的注册,例如咱们平时用到的@Component,还有@Configuration类下的@Bean等。
构造方法中调用getOrCreateEnvironment(registry)来获取environment;你们调试跟进的话会发现,此处新实例化了StandardServletEnvironment,你们还记得SpringApplication中的environment吗,它也是StandardServletEnvironment实例,那么此处为何还要新new一个StandardServletEnvironment呢,总结中给你们答案。
属性ConditionEvaluator conditionEvaluator,你们留意下属性类型ConditionEvaluator ,一般用来评估@Conditional。
另外还注册了注解配置处理器:AnnotationAwareOrderComparator、ContextAnnotationAutowireCandidateResolver、ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、EventListenerMethodProcessor、DefaultEventListenerFactory。
此时beanFactory属性以下
ClassPathBeanDefinitionScanner
从类注释上来看,就是一个bean定义扫描器,用来扫描类路径下的bean侯选者。
其中registerDefaultFilters,注册了两个AnnotationTypeFilter:一个针对@Component,一个针对@ManagedBean
此时beanFactory属性以下
此时虽然已经建立了应用上下文,但还只是具备一个骨架(填充了少部份内容),后续会往这个骨架上填充器官和肉体,来构成一个完整的应用。那往哪填充呢?就是咱们上面提的到个各个类中的属性。
上面讲了那么多,相信你们此时有点蒙,看似一个简单的createApplicationContext,却引起了一系列类的实例化;你们主要关注上述两个类图中父级类,对每一个类进行一遍通读,大体了解下每一个类中有些什么属性。后续确定会对这些属性进行填充,并利用这些属性完成咱们的应用。
有时候,不是对手有多强大,只是咱们不敢去尝试;勇敢踏出第一步,你会发现本身比想象中更优秀!诚如海因斯第一次跑进人类10s大关时所说:上帝啊,原来那扇门是虚掩着的!
一、文中疑问
AnnotatedBeanDefinitionReader中为何还要实例化一个StandardServletEnvironment?
咱们能够把这个问题变一下,为何不把SpringApplication中的environment直接注入到AnnotatedBeanDefinitionReader,而是要在AnnotatedBeanDefinitionReader中实例化新的StandardServletEnvironment?
咱们看下类所在的包可知,SpringApplication是Spring boot的特有的类,而AnnotatedBeanDefinitionReader是spring中的类,咱们知道spring boot依赖spring,但spring不依赖spring boot,那么咱们在spring中能用spring boot特有的内容吗?咱们可能又有另外的疑问了,那为何不先实例化spring中的StandardServletEnvironment,而后将它赋值给SpringApplication,就目前而言,我也不知道,不过在后续应该能找到答案,咱们暂且先当一个疑问留着。
二、AnnotatedBeanDefinitionReader与ClassPathBeanDefinitionScanner
前者是注解bean定义读取器,用于编程式注解bean的注册;后者是类路径bean定义扫描器,用于检测类路径上的bean候选者。
AnnotatedBeanDefinitionReade用来加载class类型的配置,在它初始化的时候,会预先注册一些BeanPostProcessor和BeanFactoryPostProcessor,这些处理器会在接下来的spring初始化流程中被调用。ClassPathBeanDefinitionScanner是一个扫描指定类路径中注解Bean定义的扫描器,在它初始化的时候,会初始化一些须要被扫描的注解。
三、BeanDefinition
Spring容器里经过BeanDefinition对象来表示Bean,BeanDefinition描述了Bean的配置信息;根据BeanDefinition实例化bean,并放到bean缓存中。
四、spring bean配置方式
有三种:基于XML的配置方式 、基于注解的配置方式和基于Java类的配置方式。
基于XML,这个咱们都很熟,相似:<bean id="xx" class="xxx" />
基于注解,这个咱们也用的比较多,入@Component、@Service、@Controller等
基于java类,spring的推荐配置方式,@Configuration配合@Bean
五、createApplicationContext到底作了什么
说的简单点:建立web应用上下文,对其部分属性:reader、scanner、beanFactory进行了实例化;reader中实例化了属性conditionEvaluator;scanner中添加了两个AnnotationTypeFilter:一个针对@Component,一个针对@ManagedBean;beanFactory中注册了8个注解配置处理器。这些就目前而言,可能没体现其做用,后续确定会用到的。
Spring boot 源码