springboot源码1--启动流程

之前在使用spring boot框架搭建一个web服务时,一直想详细研究下spring boot的源码,主要是bean加载到IOC容器和Spring Aop这两个功能的具体实现,最近有时间就在家看了下spring关于这两个功能的源码,也在网上找了些资料去看,发现大部分资料写的都是偏重于某一块源码的讲解,我是希望能够按照spring boot的启动流程来分析这两个功能,这样的话能够前后连贯,理解起来也会更容易,否则单独讲这两部分的话,很多东西不知道在哪完成初始化的,因此关于spring 源码学习的第一篇文章就从spring boot框架的启动开始讲解。

@SpringBootApplication
public class Chapter1Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter1Application.class, args);
    }
}

通过调用SpringApplication的run方法就可以快速启动一个web应用。这个run方法里最后会调用下面这个run方法。

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        return new SpringApplication(sources).run(args);
    }

    public SpringApplication(Object... sources) {
        initialize(sources);
    }

    private void initialize(Object[] sources) {
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }
        this.webEnvironment = deduceWebEnvironment();
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

这个run方法中new了一个SpringApplication实例,在构造函数中调用了initialize方法,在这个方法里主要做了三件事:
(1)检查当前启动的应用是否是一个web应用。如果是webEnvironment 变量的值为true,检测的方法是能否通过反射实例化下面这两个类来判断。

"javax.servlet.Servlet",        "org.springframework.web.context.ConfigurableWebApplicationContext"

(2) 设置ApplicationContextInitializer实现类,这些实现类是配置在META-INF/spring.factories文件中,这些实现类后面会使用。

org.springframework.context.ApplicationContextInitializer=
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,
org.springframework.boot.context.ContextIdApplicationContextInitializer,
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer

(3) 设置listener监听器,这些监听器也是配置在META-INF/spring.factories下如下:

org.springframework.context.ApplicationListener=
org.springframework.boot.ClearCachesApplicationListener,
org.springframework.boot.builder.ParentContextCloserApplicationListener,
org.springframework.boot.context.FileEncodingApplicationListener,
org.springframework.boot.context.config.AnsiOutputApplicationListener,
org.springframework.boot.context.config.ConfigFileApplicationListener,
org.springframework.boot.context.config.DelegatingApplicationListener,
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,
org.springframework.boot.logging.ClasspathLoggingApplicationListener,
org.springframework.boot.logging.LoggingApplicationListener

这些监听器是spring启动时,自动加载的。用户也可以自己实现ApplicationListener接口,按照业务的要求实现具体的监听器,对spring的事件监听机制不熟悉的可以参考这篇文章sping监听器

初始化SpringApplication实例后,调用SpringApplication的run方法,看下这个run方法实现。

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            listeners.finished(context, null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }
    }

这个方法比较长,spring的启动就是在这个方法中完成的,我们还是按照步骤来分析这个方法。
(1)获取SpringApplicationRunListeners对象,获取方式与之前获取ApplicationListener实现类相同,也是从spring.factories中获取具体的SpringApplicationRunListeners实现类。

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

EventPublishingRunListener这个类的主要作用是根据spring启动的不同时期,触发不同的事件类型。之前设置的监听器,每个监听器对应不同的事件,事件类型匹配时会触发对应监听器的onApplicationEvent方法,这是一个典型的观察者模式。这个类的具体代码可以参考EventPublishingRunListener

(2)触发EventPublishingRunListener的starting方法。

    public void starting() {
        this.initialMulticaster
                .multicastEvent(new ApplicationStartedEvent(this.application, this.args));
    }

这个方法就会触发一个ApplicationStartedEvent事件,通知这个事件对应的监听器完成具体的业务。

(3)创建ApplicationContext对象。
这个对象非常重要,Spring IOC的实现主要就是基于这个对象。看下怎么创建的。

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                contextClass = Class.forName(this.webEnvironment
                        ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
            }
            catch (ClassNotFoundException ex) {
            
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
    }

根据是否是一个web应用,确定ApplicationContext的具体类型。本文以web应用为例,初始化一个AnnotationConfigEmbeddedWebApplicationContext类,具体初始化过程参考ApplicationContext对象初始化过程

(4)执行prepareContext操作。

    private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        applyInitializers(context);
        listeners.contextPrepared(context);
        。。。
        // Load the sources
        Set<Object> sources = getSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[sources.size()]));
        listeners.contextLoaded(context);
    }
    }

这个方法中主要对上一步创建的ApplicationContext做一些初始化操作,调用applyInitializers方法。

    protected void applyInitializers(ConfigurableApplicationContext context) {
        for (ApplicationContextInitializer initializer : getInitializers()) {
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                    initializer.getClass(), ApplicationContextInitializer.class);
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            initializer.initialize(context);
        }
    }

这个方法中会遍历之前添加的实现ApplicationContextInitializer的初始化子类,然后调用这些子类的initialize方法,完成对ApplicationContext的一些初始化操作。
例如:
ContextIdApplicationContextInitializer类会为ApplicationContext生成一个唯一的id。
DelegatingApplicationContextInitializer类会从application.properties配置文件中读取key为context.initializer.classes的实现ApplicationContextInitializer的初始化子类,这样就允许开发者自己添加ApplicationContextInitializer子类。

遍历完初始化子类后,执行listeners.contextPrepared(context),这个是触发监听器操作的,这个方法目前是空,不会触发监听器的执行。

然后执行load方法,加载Spring boot框架的主函数所在的类到ApplicationContext中,看下加载过程。

    protected void load(ApplicationContext context, Object[] sources) {
        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
        }
        BeanDefinitionLoader loader = createBeanDefinitionLoader(
                getBeanDefinitionRegistry(context), sources);
        if (this.beanNameGenerator != null) {
            loader.setBeanNameGenerator(this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            loader.setResourceLoader(this.resourceLoader);
        }
        if (this.environment != null) {
            loader.setEnvironment(this.environment);
        }
        loader.load();
    }

初始化一个BeanDefinitionLoader类,然后调用load方法,省略中间调用过程,调用下面这个load方法。

    private int load(Class<?> source) {
        。。。
        if (isComponent(source)) {
            this.annotatedReader.register(source);
            return 1;
        }
        return 0;
    }

首先加载的类是否含有@Component注解,然后调用annotatedReader.register(source)方法,annotatedReader实例对应的实现类是AnnotatedBeanDefinitionReader类,忽略中间调用过程,最终会调用下面这个register方法。

    public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
        if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
            return;
        }
        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
        abd.setScope(scopeMetadata.getScopeName());
        String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
        AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
        if (qualifiers != null) {
            for (Class<? extends Annotation> qualifier : qualifiers) {
                if (Primary.class == qualifier) {
                    abd.setPrimary(true);
                }
                else if (Lazy.class == qualifier) {
                    abd.setLazyInit(true);
                }
                else {
                    abd.addQualifier(new AutowireCandidateQualifier(qualifier));
                }
            }
        }

        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
    }

首先创建一个基于注解的AnnotatedGenericBeanDefinition实现类,这个类中保存了bean的所有信息。
然后调用this.scopeMetadataResolver.resolveScopeMetadata(abd)这个方法。

    @Override
    public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
        ScopeMetadata metadata = new ScopeMetadata();
        if (definition instanceof AnnotatedBeanDefinition) {
            AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
            AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
                    annDef.getMetadata(), this.scopeAnnotationType);
            if (attributes != null) {
                metadata.setScopeName(attributes.getString("value"));
                ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
                if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
                    proxyMode = this.defaultProxyMode;
                }
                metadata.setScopedProxyMode(proxyMode);
            }
        }
        return metadata;
    }

这个方法主要是判断类中是否有@Scope注解,如果有这个注解会获取这个注解对应的value和proxyMode属性,关于这个注解可以参考@Scope。获取完@Scope注解属性后返回ScopeMetadata对象。根据返回的ScopeMetadata对象,设置AnnotatedGenericBeanDefinition中这个bean的Scope属性。

调用AnnotationConfigUtils.processCommonDefinitionAnnotations(abd)这个方法完成对这个类中注解的解析,代码如下。

    static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
        if (metadata.isAnnotated(Lazy.class.getName())) {
            abd.setLazyInit(attributesFor(metadata, Lazy.class).getBoolean("value"));
        }
        else if (abd.getMetadata() != metadata && abd.getMetadata().isAnnotated(Lazy.class.getName())) {
            abd.setLazyInit(attributesFor(abd.getMetadata(), Lazy.class).getBoolean("value"));
        }

        if (metadata.isAnnotated(Primary.class.getName())) {
            abd.setPrimary(true);
        }
        if (metadata.isAnnotated(DependsOn.class.getName())) {
            abd.setDependsOn(attributesFor(metadata, DependsOn.class).getStringArray("value"));
        }

        if (abd instanceof AbstractBeanDefinition) {
            AbstractBeanDefinition absBd = (AbstractBeanDefinition) abd;
            if (metadata.isAnnotated(Role.class.getName())) {
                absBd.setRole(attributesFor(metadata, Role.class).getNumber("value").intValue());
            }
            if (metadata.isAnnotated(Description.class.getName())) {
                absBd.setDescription(attributesFor(metadata, Description.class).getString("value"));
            }
        }
    }

解析完常用注解后,将这个AnnotatedGenericBeanDefinition放入BeanDefinitionHolder对象中,然后调用AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry)。

    static BeanDefinitionHolder applyScopedProxyMode(
            ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {

        ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
        if (scopedProxyMode.equals(ScopedProxyMode.NO)) {
            return definition;
        }
        boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
        return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
    }

如果bean中含有@Scope注解,则根据proxyMode属性生成一个代理对象。
生成代理对象后,将这个代理对象放入ApplicationContext中DefaultListableBeanFactory中的beanDefinitionMap这个map中。

到这里prepareContext方法的大部分功能就完成了。

(5)refresh Context
上一步只是初始化了context的一些参数,创建了context里创建bean的DefaultListableBeanFactory,将sping boot启动的主函数类放入beanDefinitionMap这个map中,项目中的其他定义的bean还未被扫描放入beanDefinitionMap,以及beanDefinitionMap中放入的bean还没有被实例化。这些操作其实是在refresh Context这一步完成。
这一步的内容比较多,也比较复杂,我们在bean加载详细讲解。

我们接下来还是继续分析启动的主流程。
(6)最后调用listeners.finished(context, null)
看下fininshed这个方法

    public void finished(ConfigurableApplicationContext context, Throwable exception) {
        SpringApplicationEvent event = getFinishedEvent(context, exception);
        if (context != null && context.isActive()) {
            // Listeners have been registered to the application context so we should
            // use it at this point if we can
            context.publishEvent(event);
        }
        else {
            // An inactive context may not have a multicaster so we use our multicaster to
            // call all of the context's listeners instead
            if (context instanceof AbstractApplicationContext) {
                for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
                        .getApplicationListeners()) {
                    this.initialMulticaster.addApplicationListener(listener);
                }
            }
            if (event instanceof ApplicationFailedEvent) {
                this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
            }
            this.initialMulticaster.multicastEvent(event);
        }
    }

其实就是发送一个ApplicationReadyEvent事件触发监听此事件的监听器,我看了下之前加载的监听器好像没有监听此事件的监听器,当然用户也可以自己定义关于此事件的监听器。

到这里springboot 启动的大概流程就分析完了,大部分的文章对这部分都是一概而过,直接去讲bean的加载,但是这里还是有很多内容需要清楚的,这样才能更明白类的调用关系、监听器模式、bean是如何加载的。

下一节就具体讲bean加载

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342