乐趣区

SpringBoot自动配置原理面试高频

SpringBoot 自动配置

我们都知道一个 SpringBoot 主配置类只要标注上 <u>@SpringBootApplication</u> 的注解,Spring 就会帮我们自动配置各个组件和实例化 Bean,我们来通过源码分析一下 SpringBoot 自动配置原理。

首先我们要知道,SpringBoot 将符合条件的 @Configuration 类都加载到 Spring 容器中,就像一只八爪鱼,我们的启动类就是一个典型的 @Configuration 类。

@SpringBootApplication

包括下面两个关键的注解

@SpringBootConfiguration
@EnableAutoConfiguration

其中 @SpringBootConfiguration 就是 get 主配置类添加上 @Configuration 注解让主配置类的自动配置能被扫描到

下面我们主要分析一下 @EnableAutoConfiguration 注解

@EnableAutoConfiguration

其中也包含两个关键注解

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
  • 第一个用作包扫描自动配置
  • 第二个导入 AutoConfigurationImportSelector 类用作 SpringBoot 提供的其他组件的自动配置选择器

我们先看一下第一个

@AutoConfigurationPackage

这个注解导入了 SpringBoot 中的 Registrar 类 用作包路径下的 Bean 扫描并注册到 BeanFactory 中

@Import({Registrar.class})

详细看一下这个类

Registrar 注册类

其中主要的方法是 registerBeanDefinitions

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 获取到元信息的包名传入注册器
        AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
    }

传入两个参数:

  • metadata 启动类元信息
  • registry 用作注册的 Bean 注册器

目录结构如下:

元信息如下:

new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()获取到该启动类所在路径的包名,传如 register 方法注册该包名下的所有需要注册并实例化的 Bean(包括 @Component @Service @Mapper @Repository 等)

AutoConfigurationPackages 中 register 方法
public static void register(BeanDefinitionRegistry registry, String... packageNames) 

根据传入的 register 和包名 packageName 注册该包名下的所有需要注册并实例化的 Bean

其中我们要关注的是下面这段代码:

  • GenericBeanDefinition 创建 Bean 的一站式组件,包括 Bean 的参数、属性、类的信息
// 新建一个 GenericBeanDefinition 描述 Bean 的实例
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();       // 设置 bean 的类名称
beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
// 获取构造器参数并保存
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
//Bean 的角色 感兴趣的可以去了解一下,有 0,1,2 对应三种不同角色
beanDefinition.setRole(2);
// 参数设置完调用 registerBeanDefinition 注册并实例化 Bean
registry.registerBeanDefinition(BEAN, beanDefinition);
}
getConstructorArgumentValues

这个方法用于获取构造器参数并保存

  • ConstructorArgumentValues 是一个构造器参数保存器,保存 Bean 的构造方法的参数
public ConstructorArgumentValues getConstructorArgumentValues() {if (this.constructorArgumentValues == null) {
        // 创建一个新的构造器参数保存器
        this.constructorArgumentValues = new ConstructorArgumentValues();}
    return this.constructorArgumentValues;
}
DefaultListableBeanFactory 中实现的 registerBeanDefinition 方法

该方法对 GenericBeanDefinition 创建的 Bean 进行注册到 BeanFactory

传入 beanName 和 beanDefinition

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
  • 检查完 Bean 是否合法后先判断是否存在相同 Bean 的注册,存在抛出异常,不存在执行如下

其中主要代码如下:

// 开始注册 Bean
// 如果已启动注册状态则要加锁注册单例 singleton
if (this.hasBeanCreationStarted()) {synchronized(this.beanDefinitionMap) {
        // 把 Bean 存入 beanDefinitionMap
        this.beanDefinitionMap.put(beanName, beanDefinition);
        List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
        // 把需要注册的 Bean 添加到 map 中
        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);
}

下图就是 beanDefinitionMap 返回的值,里面除了 Spring 框架提供的一些必要的 Bean 需要注册外,就是我们主启动类所在包下的所有需要扫描的 Bean,我只有一个主启动类和一个 controller 下面标出

当我尝试把写的 HelloWorldController 的 @RestController 注解注释掉以后,SpringBoot 没有扫描到这个 Controller,也就没有把它注册到 BeanFactory 中

AutoConfigurationImportSelector

看完 @AutoConfigurationPackage 注解我们看一下 @EnableAutoConfiguration 另一个注解 @Import({AutoConfigurationImportSelector.class}) 该注解导入了 SpringBoot 中 AutoConfigurationImportSelector 类(自动配置选择器)用作选择 SpringBoot 提供的所需组件 Bean 的选择并自动配置

主要是下面的方法

getAutoConfigurationEntry 方法

传入两个参数

  • autoConfigurationMetadata 自顶配置元信息
  • annotationMetadata 注解元信息

注解元信息的参数(配置类上添加的 @ComponentScan(Exclude)):

excludeName 和 exclude 表示需要排除扫描自动配置的类,String[0]表示了没有需要排除的

// 获取注解元信息参数
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 调用 getCandidateConfigurations 获取需要自动配置的类或者功能
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = this.removeDuplicates(configurations);
// 检查并排除 exclude 类
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
   

getCandidateConfigurations

负责加载 META-INF/spring.factories 中的配置的类,这些类就是 SpringBoot 提供的所需要加载的那些 *AutoConfiguration 类,也就是要注册的 Bean 或功能,获取到候选类的 BeanName 返回一个 List

借助 SpringFactoriesLoader 类实现加载自动配置类

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 加载类路径下 META-INF/spring.factories 中的自动配置类
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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

SpringBoot 使用 ClassLoader 类加载机制加载 META-INF/spring.factories

将根据 EnableAutoConfiguration 类名称去加载需要的类或者功能

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

SpringFactoriesLoader 的实现机制跟 util 包下的 ServiceLoader(SPL)实现机制类似,是一种服务查找机制,为接口查找服务实现类,感兴趣的可以去了解一下

DependsOn 注解

DependsOn 注解是一个标注在类上的注解,可以帮助我们定义依赖 bean 之间的配置注册顺序

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DependsOn {String[] value() default {};

}

比如:

一般加载:

我们在 SearchController 中注入了 WordConifg 这个 bean,那么在配置时,会优先配置 WordConfig 这个类,之后才会继续配置 SearchController 这个类,然后加载 SearchController 时会先检查是否在 IOC 容器中存在 WordConfig 这个 bean,有就加载 Controller,没有就报错

@DependsOn({"wordConfig"})
public class SearchController  {

    @Autowired
    private WordConfig wordConfig;
}

但是如果是这样,IOC 就不能正确判断优先加载哪个 bean,(Service 中相互引用),但是这两个类中都有两个初始化方法:

  1. afterPropertiesSet
  2. 或者 postConstruct 方法(先于 afterPropertiesSet 执行)
@Service
public class A implements InitializingBean{
    @Autowired
    private B b;
    public void afterPropertiesSet(){}
}

@Service
public class B implements InitializingBean {
    @Autowired
    private A a;
    public void afterPropertiesSet(){}
}

如果此时我们想要 A 的初始化方法 afterPropertiesSet 先于 B 的初始化方法执行,那么我们需要使用到 @DependsOn 注解,

如下:

标注上 @DependsOn({“b”})注解,说明优先加载 beanName 为 b 的 component

@Service
@DependsOn({"b"})
public class A implements InitializingBean{
    @Autowired
    private B b;
    public void afterPropertiesSet(){}
}

我们看看它的源码:

AbstractBeanFactory.doGetBean 方法中有下面这段代码

try {final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                checkMergedBeanDefinition(mbd, beanName, args);

                // Guarantee initialization of beans that the current bean depends on.
                String[] dependsOn = mbd.getDependsOn();
                if (dependsOn != null) {for (String dep : dependsOn) {if (isDependent(beanName, dep)) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "Circular depends-on relationship between'" + beanName + "'and'" + dep + "'");
                        }
                        registerDependentBean(dep, beanName);
                        try {getBean(dep);
                        }
                        catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "'"+ beanName +"' depends on missing bean '"+ dep +"'", ex);
                        }
                    }
                }
    
    ---------------------------------------
        // 上面逻辑结束加载该 bean
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

分析下这里的逻辑:

  1. 拿到要加载 bean1BeanDefinition
  2. 检查该 BeanDefinition 上是否存在 dependson 注解
  3. 如果不为空,根据注解中的 beanName 拿到相应的beans(多个),注册 beans
  4. 依赖 bean 注册加载结束,执行 bean1 的加载和注册
退出移动版