Springboot 启动原理探索-@SpringBootApplication注解解析

Springboot 启动原理探索-@SpringBootApplication注解解析

参考网上Springboot相关启动原理的部分讲解,总结了一下,写出来和大家分享。

1.@SpringBootApplication注解

对于Springboot项目,它有一个启动类。一般如下(此处以Springboot演示案例为例):

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

从上述代码里看出,启动类引用了一个注解@SpringBootApplication,而
@SpringBootApplication实际上是一个复合注解,它的类定义如下所示:

@Target(ElementType.TYPE)         
@Retention(RetentionPolicy.RUNTIME)
@Documented                     
@Inherited                        
@SpringBootConfiguration             // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration             // 开启自动配置
@ComponentScan(excludeFilters = {    // 扫描组件
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}

实际上@SpringBootApplication内的核心注解有三个:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan

图1 @SpringBootApplication 注解分解图

1.1 @SpringBootConfiguration

@SpringBootConfiguration实际上是引入的@Configuration,而@Configuration是Spring的注解类,用于定义配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到srping容器中,并且实例名就是方法名,等同于spring的XML配置文件。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

@Configuration与XML配置的简单对比如下:

@Configuration
public class SpringDemo{

}

等价于XML形式的<beans></beans>

<?xml version="1.0" encoding="UTF-8" ?>
<beans   xmlns="http://www.springframework.org/schema/beans" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
</beans>
package com.spring.demo
public class Service {
    
}
----------------------------------------------------------------------------------------------
package com.spring.demo
@Configuration
public class SpringDemo{
    @Bean
    public Service service(){
        return new Service();
    }
}

等价于

<?xml version="1.0" encoding="UTF-8" ?>
<beans   xmlns="http://www.springframework.org/schema/beans" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
        <bean id="service" class="com.spring.demo.Service"/>
</beans>

1.2 @EnableAutoConfiguration

@EnableAutoConfiguration是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,它也是复合注解,定义如下所示:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage      //自动配置包
@Import({AutoConfigurationImportSelector.class})  //借助AutoConfigurationImportSelector自动配置类
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

@EnableAutoConfiguration里重要的注解分别是@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class),看名知意,@AutoConfigurationPackage:自动配置包,AutoConfigurationImportSelector:自动配置组件的导入。

1.2.1@AutoConfigurationPackage

@AutoConfigurationPackage具体定义如下所示:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

由@AutoConfigurationPackage定义可以看出,实际是借助Import导入了Registrar,而Registrar中主要调用了registerBeanDefinition方法来进行bean定义的注册。
Registrar类定义如下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            //此处是注册了一个Bean的定义。
            //getPackageName()其实返回了当前主程序类的 同级以及子级的包组件。
            AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
           
        }
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
        }
    }

Registrar里registerBeanDefinitions实际是调用AutoConfigurationPackages的register方法,其定义如下:

 public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        } else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(2);
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }

    }

AutoConfigurationPackages的register主要调用registerBeanDefinition,
它是接口BeanDefinitionRegistry中的方法,该接口的实现类有三个:DefaultListableBeanFactoryGenericApplicationContextSimpleBeanDefinitionRegistry
,通过Idea里调试,实现类用的是DefaultListableBeanFactory,其定义如下:

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");
        if (beanDefinition instanceof AbstractBeanDefinition) {
            try {
                ((AbstractBeanDefinition)beanDefinition).validate();
            } catch (BeanDefinitionValidationException var9) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", var9);
            }
        }

        BeanDefinition existingDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName);
        if (existingDefinition != null) {
            if (!this.isAllowBeanDefinitionOverriding()) {
                throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
            }

            if (existingDefinition.getRole() < beanDefinition.getRole()) {
                if (this.logger.isInfoEnabled()) {
                    this.logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
                }
            } else if (!beanDefinition.equals(existingDefinition)) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
                }
            } else if (this.logger.isTraceEnabled()) {
                this.logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
            }

            this.beanDefinitionMap.put(beanName, beanDefinition);
        } else {
            if (this.hasBeanCreationStarted()) {
                Map var4 = this.beanDefinitionMap;
                synchronized(this.beanDefinitionMap) {
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    if (this.manualSingletonNames.contains(beanName)) {
                        Set<String> updatedSingletons = new LinkedHashSet(this.manualSingletonNames);
                        updatedSingletons.remove(beanName);
                        this.manualSingletonNames = updatedSingletons;
                    }
                }
            } else {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                this.beanDefinitionNames.add(beanName);
                this.manualSingletonNames.remove(beanName);
            }

            this.frozenBeanDefinitionNames = null;
        }

        if (existingDefinition != null || this.containsSingleton(beanName)) {
            this.resetBeanDefinition(beanName);
        }

    }

GenericApplicationContext实现的也是DefaultListableBeanFactory的registerBeanDefinition方法。

...
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private final DefaultListableBeanFactory beanFactory;
...

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
        this.beanFactory.registerBeanDefinition(beanName, beanDefinition);
    }
...
}

SimpleBeanDefinitionRegistry里registerBeanDefinition方法的定义如下所示:

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
        Assert.hasText(beanName, "'beanName' must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }

@AutoConfigurationPackage小结(图解)

图2 @AutoConfigurationPackage

1.2.2 AutoConfigurationImportSelector

AutoConfigurationImportSelector的定义如下所示:

图3 AutoConfigurationImportSelector 定义截取

AutoConfigurationImportSelector的selectImports方法,主要是自动加载配置文件META-INF/spring.factories里的类,而此处是加载spring-boot-autoconfigue2.1.3.RELEASE.jar/META-INF/spring.factories里org.springframework.boot.autoconfigure.EnableAutoConfiguration所对应的自动配置类。
spring.factories内容如下图所示(部分截图):

图4 spring.factories内容解析

selectImports方法调用图解如下图所示:


图 5 selectImports方法调用关系图

selectImports实际是借助SpringFactoriesLoader类去实现自动加载功能。SpringFactoriesLoader的loadFactoryNames方法,通过传入工厂类的名字:EnableAutoConfiguration.class,去配置文件spring.factories里搜索并自动加载,loadFactoryNames相关定义如下图所示:

图6 loadFactoryNames方法解析

loadFactoryNames里通过loadSpringFactories方法实现具体加载功能,其定义如下所示:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
    } else {
try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();

while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

while(var6.hasNext()) {
                    Entry<?, ?> entry = (Entry)var6.next();
                    String factoryClassName = ((String)entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;

for(int var11 = 0; var11 < var10; ++var11) {
                        String factoryName = var9[var11];
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }

            cache.put(classLoader, result);
return result;
        } catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
        }
    }
}

AutoConfigurationImportSelector小结:
调用selectImports方法,传入工厂类名作为参数以借助SpringFactoriesLoader类的loadSpringFactories方法去spring.factories配置文件里根据传参去查找需要自动配置的类,注册到Spring的IoC容器里。

@EnableAutoConfiguration总结(图解):

图7 @ EnableAutoConfiguration简析图

1.3 @ComponentScan

@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。

注意:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。

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

推荐阅读更多精彩内容

  • springboot 概述 SpringBoot能够快速开发,简化部署,适用于微服务 参考嘟嘟大神SpringBo...
    一纸砚白阅读 5,408评论 2 20
  • 1. Spring Boot 入口——main方法 从上面代码可以看出,Annotation定义(@SpringB...
    徐志毅阅读 20,155评论 9 52
  • SpringMVC原理分析 Spring Boot学习 5、Hello World探究 1、POM文件 1、父项目...
    jack_jerry阅读 1,282评论 0 1
  • Part III. Using Spring Boot 文档说明:文档对应的版本为 2.1.0.M3这不是文档的完...
    icameisaw阅读 2,045评论 0 0
  • 7 vue-cli初步 1.安装node.js下载安装node.js,一路默认安装即可,安装完毕,启动命令行,输入...
    诅咒猎豹阅读 121评论 0 0