SpringBoot,春天里的雪地靴,把面试问题踩的吱吱响(2021届秋招)

大家好,我是方圆
不废话,直入正题!


1. SpringBoot的核心注解是哪个?

启动类上的注解为@SpringBootApplication,它由@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan组成。
“春天里的瑞士军刀”,《Spring实战》(一)

2. SpringBoot自动配置的原理

2.1 绕不开的@EnableAutoConfiguration

在这里插入图片描述

2.1.1 @AutoConfigurationPackage

被该注解修饰的包,应该在AutoConfigurationPackages中注册

咱从一开始学 SpringBoot 就知道一件事:主启动类必须放在所有自定义组件的包的最外层,以保证Spring能扫描到它们。由此可知是它起的作用。
在这里插入图片描述进入AutoConfigurationPackages.Registrar,其中有一个register方法如下

private static final String BEAN = AutoConfigurationPackages.class.getName();

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    // 判断 BeanFactory 中是否包含 AutoConfigurationPackages
    if (registry.containsBeanDefinition(BEAN)) {
        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
        ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
        // addBasePackages:添加根包扫描包
        constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
    }
    else {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(BasePackages.class);
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(BEAN, beanDefinition);
    }
}

它的作用就是获取主启动类所在包及其子包下的组件,也解释了启动器类在最外层的原因


2.2 @Import(AutoConfigurationImportSelector.class)

在这里插入图片描述DeferredImportSelector 处理自动配置。如果需要自定义扩展 @EnableAutoConfiguration,则也可以编写该类的子类。
在这里插入图片描述
DeferredImportSelector 的执行时机,是在 @Configuration 注解中的其他逻辑被处理完毕之后(包括对 @ImportResource、@Bean 这些注解的处理)再执行,换句话说,DeferredImportSelector 的执行时机比 ImportSelector 更晚。


回到 AutoConfigurationImportSelector,它的核心部分,就是 ImportSelector 的 selectImport 方法

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
    // 加载自动配置类
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, 
            annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

我们再进入getAutoConfigurationEntry方法

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
         AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 【核心】加载候选的自动配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

在向下进入getCandidateConfigurations方法,其中getSpringFactoriesLoaderFactoryClass()传入的便是@EnableAutoConfiguration

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // SPI机制加载自动配置类
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
             getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
             + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

再进入loadFactoryNames方法

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    //     ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        // ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
        Enumeration<URL> urls = (classLoader != null ?
                 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryClassName = ((String) entry.getKey()).trim();
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryClassName, factoryName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                       FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

源码中使用 classLoader 去加载了指定常量路径下的资源: FACTORIES_RESOURCE_LOCATION ,而这个常量指定的路径实际是:META-INF/spring.factories

2.3 这些jar包的位置

它的位置如下,我们可以通过Maven导入的依赖,找到它的位置
在这里插入图片描述
spring.factories文件中,内容如下
在这里插入图片描述
它都是一些以xxxAutoConfiguration的形式结尾JavaConfig配置类

2.3 自动配置的生效

我们以WebServicesAutoConfiguration为例来说明,我们可以从下图中看见有@EnableConfigurationProperties注解修饰,该注解有(WebServicesProperties.clss)
在这里插入图片描述
我们进去看看WebServicesProperties(xxxProperties),如下
在这里插入图片描述
我们很快的发现有一个@ConfigurationProperties的注解,其中写了前缀的格式,那么我们看右边application.properties配置文件中,我们可以根据它的前缀标识,来对配置类中的字段属性进行修改。

  • 我们屡一下思路,@ConfigurationProperties实现的就是将配置文件中的属性绑定到对应的bean上,@EnableConfigurationProperties负责将绑定了属性的配置类,放到spring容器中,真正控制着配置类属性的是xxxProperties.class

2.4 翻译成口语我们该怎么说

@EnableAutoConfiguration注解是一个派生注解,它包括@AutoConfigurationPackage,它的作用是获取主启动类所在包及其子包下的组件,也解释了启动器类在最外层的原因,还有@Import注解导入了AutoConfigurationImportSelector.class,其中有selectImport方法,它会调用loadFactoryNames方法,进入META-INF目录下的spring.factories文件,对其进行加载,这些配置类命名形式都是结尾都是AutoConfiguration,它们都是JavaConfig的形式,通过@EnableConfigurationProperties注解修饰,可以看见xxxProperties结尾的类获取配置文件的属性,而XxxxProperties类是通过与全局配置文件中对应的属性进行绑定的。(配置类上还有Condition条件)
在这里插入图片描述
Spring Boot面试杀手锏————自动配置原理

3. Spring-boot-starter-parent有什么用?

我们创建一个SpringBoot的项目,其中默认都是有parent,这个parent就是Spring-boot-starter-parent,它有以下作用

  1. 继承自spring-boot-dependencies,这个里边定义了依赖的版本,也正是因为继承了这个依赖,所以我们在写依赖时才不需要写版本号。
  2. 针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml

加油儿!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方圆想当图灵

嘿嘿,小赏就行,不赏俺也不争你

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值