第二章:SpringBoot核心运行原理

  1. SpringBoot核心流程简图

SpringBoot启动会自动加载相关依赖,配置相应的初始化参数,以最快捷、简单的形式对第三方软件进行集成,这便是springboot的自动配置功能。我们从整体上看一下springboot实现该运作机制涉及的核心部分

SpringBoot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各种AutoConfiguration类,当某个AutoConfiguration类满足其注@Conditional指定的生效条件时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入到Spring容器/SpringBoot容器里,就可以完成依赖框架的自动配置。

  • @EnableAutoConfiguration:该注解由组合注解@SpringBootApplication引入,完成自动配置开启,扫描各个jar包下的spring.factories文件,并加载文件中注册的AutoConfiguration类等。

  • spring.factories:配置文件,位于jar包的META-INF目录下,按照指定格式注册了自动配置的AutoConfiguration类。spring.factories也可以包含其他类型待注册的类。该配置文件不仅仅存在于SpringBoot项目中,也可以存在于自定义的自动配置(或Starter)项目中

  • AutoConfiguration类:自动配置类,代表了springBoot中一类以XXAutoConfiguration命名的自动配置类。其中定义了三方组件继承Spring所需初始化的Bean和条件。

  • @Conditional:条件注解及其衍生注解,在AutoConfiguration类上使用,当满足该条件注解时,才会实例化AutoConfiguration类。

  • Starters:三方组件的依赖和配置,SpringBoot已经预先配置的组件。

  1. @SpringBootApplication源码及使用介绍

@SpringBootApplication注解是由多个注解组合而成


  1. @Target({ElementType.TYPE}) 用来表示注解作用范围,TYPE 表示作用范围为类或接口


  1. @Retention (RetentionPolicy.RUNTIME)


  1. @Documented 表明这个注释是由 javadoc 记录的。

  1. @Inherited 放在注解上,当父类加了 @SpringBootApplication 注解时,子类也会继承这个注解(对接口的实现类无效)。

  1. @SpringBootConfiguration @ComponetScan @EnableAutoConfiguration


@SpringBootConfiguration注解下有@Configuration,这里就是我们平时自定义的Configuration

@EanbleAutoConfiguration注解下有@AutoConfigurationPackage、@Import注解,这里是自动配置的Configuration

@ComponentScan 执行一些包路径扫描

源码方法介绍及使用:

  • exclude:根据类(Class)排除指定的自动配置,该成员属性覆盖了@SpringBootApplication中组合的@EnableAutoConfiguration中定义的exclude成员属性。

  • excludeName:根据类名排除指定的自动配置,覆盖了@EnableAutoConfiguration中的excludeName的成员属性

  • scanBasePackages:指定扫描的基础package,用于激活@Component等注解的初始化

  • scanBasePackageClasses:指定扫描的类,用于组件的初始化

  • proxyBeanMethods:指定是否代理@Bean方法以强制执行bean的生命周期行为

PS:exclude、excludeName覆盖了@EnableAutoConfiguration中的成员属性释义:表示@SpringBootApplication可以继承@EnableAutoConfiguration中的exclude、excludeName属性

  1. @EnableAutoConfiguration

@EnableConfiguration是开启自动配置的注解,在创建的SpringBoot项目中并不能直接看到这个注解,它是由组合注解@SpringBootApplication引入的。在SpringBoot入口类(单元测试除外)中,唯一的注解就是@SpringBootApplication。它是SpringBoot项目的核心注解,用于开启自动配置,准确说是通过该注解内组合的@EnableConfiguration开启了自动配置。

在未使用springBoot的情况下,Bean的生命周期由Spring来管理,然而Spring无法自动配置@Configuration注解的类。而SpringBoot的核心功能之一,就是根据约定自动管理该注解标注的类。用来实现该功能的组件之一便是@EnableAutoConfiguration注解。@EnableAutoConfiguration位于SpringBootAutoConfiguration包内,当使用@SpringBootApplication注解时,@EnableAutoConfiguration注解就会自动生效。@EnableAutoConfiguration的主要功能就是启动spring应用程序上下文时进行自动配置

@EnableAutoConfiguration注解提供了一个常量和两个成员参数的定义。

  • ·ENABLED_OVERRIDE_PROPERTY:用来覆盖开启/关闭自动配置的功能。

  • ·exclude:根据类(Class)排除指定的自动配置。

  • ·excludeName:根据类名排除指定的自动配置。


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
// 请详细关注AutoConfigurationImportSelector,这里是导入我们的自动配置类
public @interface EnableAutoConfiguration {

    /**
     * Environment property that can be used to override when auto-configuration is
     * enabled.
     * 标志是否打开自动配置项
     */
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     * 根据类名进行排除
     */
    Class<?>[] exclude() default {};

    /**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     * 根据名称进行排除
     */
    String[] excludeName() default {};

}
  1. AutoConfigurationImportSelector

@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。从源码得知@Import(AutoConfigurationImportSelector.class)是@EnableAutoConfiguration注解的组成部分,也是自动配置功能的核心实现者。


从上图我们可以看出,AutoConfigurationImportSelector是ImportSelector接口的子类。这里我们可以通过@Import引入@Configuration注解的类,也可以导入实现了ImportSelector或ImportBeanDefinitionRegistrar的类,还可以通过@Import导入普通的POJO。

@Import的许多功能都需要借助接口ImportSelector来实现,ImportSelector决定可引入哪些@Configuration。

AutoConfigurationImportSelector作为ImportSelector的子类,当AutoConfigurationImportSelector被@Import注解引入后,它的selectImports方法就会被调用,并执行其实现的自动装配逻辑。selectImports方法几乎涵盖了组件自动装配的所有处理逻辑。


对于selectImports来说,有两种形式。一种是实现了DeferredImportSelector,一种是实现了ImportSelector。DeferredImportSelector来说,不会调用selectImports方法进行配置加载,ImportSelector形式会通过selectImports方法进行配置加载;

DeferredImportSelector这种是先进行我们的@Configuration配置的加载,然后进行其他(AutoConfig相关类)的加载;ImportSelector先加载AutoConfig相关类,然后加载@Configuration类。

// 部分代码已省略。。。
// 这里是跳转到上图selectImports方法的入口
// 当实现了DeferredImportSelector也就走不到selectImports
if (selector instanceof DeferredImportSelector) {
    this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
    // 只有实现ImportSelector才可以走到selectImports
    String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
    Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
    processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
  1. getAutoConfigurationEntry

上面我们分析了selectImports的入口,在selectImports方法中,有着我们至关重要的getAutoConfigurationEntry方法。它是获取我们所有的自动配置

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // 自动配置是否已启用
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 获取注解的属性 (exclude 和 excludeName)
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 获取自动配置类,从spring.factories中获取
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 去除重复的配置类,List转换成LinkedHashSet
    configurations = removeDuplicates(configurations);
    // 通过我们的注解属性,获取exclude的类(排除的配置类)
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    //检测 exclusions里面的类是否是正规的类
    checkExcludedClasses(configurations, exclusions);
    // 从集合中删除所有要排除的类
    configurations.removeAll(exclusions);
    // 筛选Configurations,剔除了我们不需要加载的配置类
    configurations = getConfigurationClassFilter().filter(configurations);
    // 事件发布
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}
  1. getAttributes
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
    // 获取EnableAutoConfiguration.class 全类名
    String name = getAnnotationClass().getName();
    // 拿到指定类型注解的元数据信息
    // getAnnotationAttributes获取指定注解的所有属性-值(k-v)
    // 当第二个参数为true表示Class用它的字符串的全类名来表示,这样可以避免Class被提前加载
    AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
    Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
            + " annotated with " + ClassUtils.getShortName(name) + "?");
    return attributes;
}
  1. getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // getSpringFactoriesLoaderFactoryClass 获取EnableAutoConfiguration.class
    // getBeanClassLoader 获取当前类的类加载器
    List<String> configurations = new ArrayList<>(
            SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
    ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
    Assert.notEmpty(configurations,
            "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    // 类加载器赋值给classLoaderToUse
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    // 获取EnableAutoConfiguration类的全类名
    String factoryTypeName = factoryType.getName();
    // 用classLoaderToUse类加载器加载我们的配置类
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

getOrDefault返回的是一个map类型,这里值得注意的是为什么需要返回map类型?spring.factories文件中所有的配置类都是以键值对形势展开,而我们需要读取spring.factories文件,使用Map<String, List<String>>也就正好对应了spring.factories的文件结构


上面我们知道了spring.factories的文件类型,那么就让我们看一下loadSpringFactories是如何进行加载和封装的

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    // 获取当前classLoad加载过的result
    Map<String, List<String>> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    result = new HashMap<>();
    try {
        // public tatic final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
        // 这里就是加载的spring.factories的入口
        Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            // 封装成UrlResource
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                String[] factoryImplementationNames =
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                for (String factoryImplementationName : factoryImplementationNames) {
                    // 一直到这里才会封装成一个 Map<String, List<String>>
                    result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                            .add(factoryImplementationName.trim());
                }
            }
        }

        // Replace all lists with unmodifiable lists containing unique elements
        result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
        // 然后在这里添加进了缓存中
        cache.put(classLoader, result);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result;
}

在上面我们知道了spring.factories是怎么进行加载的。那么META-INF/spring.factories这个东西什么时候才需要使用呢?我们自己的项目中并没有这个文件的添加。什么情况下我们才需要这部分配置写到自己的项目的spring.factories里面呢?

  1. 首先确定spring.factories文件,它能够让我们springboot加载我们的一些配置信息。在这里我们除了使用spring.factories这种形式,还能够使用@Configuration等形式进行配置的加载啊?

  1. 这里需要思考的是,我们在pom文件中引入了一个第三方的jar包,并且我们需要将该jar包里面的某一个或者多个配置类 进行我们当前项目的springboot的配置注入,我们应该怎么办?用spring.factories。这个时候我们就用不到@Configuration

  1. getExclusions

通过我们的注解属性,获取exclude的类(排除的配置类)

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    Set<String> excluded = new LinkedHashSet<>();
    excluded.addAll(asList(attributes, "exclude"));
    excluded.addAll(asList(attributes, "excludeName"));
    // 这里需要注意的是spring为我们考虑了如果在配置文件中加入了排除项也会被考虑进来
    excluded.addAll(getExcludeAutoConfigurationsProperty());
    return excluded;
}
  1. checkExcludedClasses
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
    // 获取所有的排除项的集合
    List<String> invalidExcludes = new ArrayList<>(exclusions.size());
    // 遍历
    for (String exclusion : exclusions) {
        // 判断是否被JVM加载过
        // 判断是否在原始的configurations中
        // 这里是一个安全的校验
        if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
            invalidExcludes.add(exclusion);
        }
    }
    // 当上面出现了安全风险,这里就会抛出异常
    if (!invalidExcludes.isEmpty()) {
        handleInvalidExcludes(invalidExcludes);
    }
}
  1. getConfigurationClassFilter().filter(configurations);

getConfigurationClassFilter获取所有的过滤器,这些过滤器是在spring-boot-antoconfigure的jar包中的spring.factories文件中获取的

为什么叫做OnXXXCondition?On就是表示你引入了么?Condition是否符合某个条件


上面我们通过getConfigurationClassFilter方法获取了三个自动配置的过滤器,下面就让我们看一下filter方法

List<String> filter(List<String> configurations) {
    long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean skipped = false;
    for (AutoConfigurationImportFilter filter : this.filters) {
        // match 判断是否为ture,如果为ture表示需要加载,flase表示不加载
        boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    if (!skipped) {
        return configurations;
    }
    List<String> result = new ArrayList<>(candidates.length);
    for (String candidate : candidates) {
        if (candidate != null) {
            result.add(candidate);
        }
    }
    if (logger.isTraceEnabled()) {
        int numberFiltered = configurations.size() - result.size();
        logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
    }
    return result;
}

上面我们看到了filter中的match方法,在springboot中专门为此写了一个子类实现FilteringSpringBootCondition

@Override
    public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
        ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
        // 这里把spring自带的配置进行了一个判断,需要加载的设置为ture,不需要加载的设置为flase
        // 这里也是我们的关键方法,下面只是针对此结果集进行的一些判断处理
        // getOutcomes方法,是一个抽象方法,有三个实现类,OnXXXCondition正好对象上面的三个过滤器,并且在其中进行一个过滤
        ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
        boolean[] match = new boolean[outcomes.length];
        for (int i = 0; i < outcomes.length; i++) {
            match[i] = (outcomes[i] == null || outcomes[i].isMatch());
            if (!match[i] && outcomes[i] != null) {
                logOutcome(autoConfigurationClasses[i], outcomes[i]);
                if (report != null) {
                    report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
                }
            }
        }
        return match;
    }
  1. other

到这里我们的springboot核心运行流程已经阅读完毕,回顾最开始的核心流程图。我们阅读完了@SpringBootApplication、@EnableAutoConfiguration以及@Import注解,那么还剩下@EnableAutoConfiguration下的@AutoConfigurationPackage还没有阅读。

不过之前已经阅读过spring源码 ,这里的功能无外乎就是把我们的AutoConfiguration的bean信息注册到beanFactory中


注册beanDefinition到beanFactory中

调用registerBeanDefinition方法

进入DefaultListableBeanFactory,这里就是我们熟悉的spring源码环节

回顾以上阅读环节。@EnableAutoConfiguration注解其实就是把我们的AutoConfiguration这些东西进行一个bean的注册,beanDefinition注册完成以后,通过AutoConfigurationImportSelector进行一个筛选加载

到此,我们的springboot的核心运行流程已经全部阅读完毕

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值