转载

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注册器

目录结构如下:

SpringBoot自动配置原理,面试高频!

元信息如下:

SpringBoot自动配置原理,面试高频!

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

SpringBoot自动配置原理,面试高频!

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 下面标出

SpringBoot自动配置原理,面试高频!

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

SpringBoot自动配置原理,面试高频!

AutoConfigurationImportSelector

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

主要是下面的方法

getAutoConfigurationEntry方法

传入两个参数

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

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

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

SpringBoot自动配置原理,面试高频!

//获取注解元信息参数
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);

SpringBoot自动配置原理,面试高频!

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;
    }

返回结果如下:

SpringBoot自动配置原理,面试高频!

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 的加载和注册
原文  https://segmentfault.com/a/1190000021009680
正文到此结束
Loading...