关于SpringBoot的自动装配原理详解

 更新时间:2023年07月15日 08:36:01   作者:贤子磊  
这篇文章主要介绍了关于SpringBoot的自动装配原理详解,Spring Boot自动装配原理是指Spring Boot在启动时自动扫描项目中的依赖关系,根据依赖关系自动配置相应的Bean,从而简化了Spring应用的配置过程,需要的朋友可以参考下

一、@SpringBootApplication

正常情况下的启动类都会加上@SpringBootApplication注解

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

我们查看下@SpringBootApplication注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {
}

我们可以看到有@EnableAutoConfiguration注解,这个是自动装配的核心

二、@EnableAutoConfiguration

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

其中有两个注解

  • @AutoConfigurationPackage

这个注解的作用就是将注解标记的类所在包及所有子包下的组件到扫描到Spring容器中

  • @Import(AutoConfigurationImportSelector.class)
    • @Import根据配置内容有三种使用方式
      • class数组
        • 将数组内的类注入到Spring容器中,bean名称是全类名
      • ImportSelector类型
        • 实现ImportSelector接口
public interface ImportSelector {
	/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);
}

返回值:就是我们实际需要注入到容器的组件全类名(返回值可以是空数组,但是不能为null,否则会有空指针异常)

参数:当前被@Import注解标记的所有注解信息

  • ImportBeanDefinitionRegistrar类型

实现ImportBeanDefinitionRegistrar接口,例如

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        //指定bean定义信息(包括bean的类型、作用域...)
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(自己的类.class);
        //注册一个bean指定bean名字(id)
        beanDefinitionRegistry.registerBeanDefinition("自定义名称",rootBeanDefinition);
    }
}

可以自定义一个或多个bean

这里采用的是第二种方式,通过AutoConfigurationImportSelector来返回需要注入的类名数组

三、AutoConfigurationImportSelector

我们看下AutoConfigurationImportSelector类中selectImports方法

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    //1、加载spring-autoconfigure-metadata.properties信息,并保存到AutoConfigurationMetadata实例中
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        .loadMetadata(this.beanClassLoader);
    //2、获取自动装配信息
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                                                                              annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

这个方法有两步

  • 第一步:加载spring-autoconfigure-metadata.properties信息,并保存到AutoConfigurationMetadata实例中
  • 第二步:获取自动装配信息

1、加载spring-autoconfigure-metadata.properties信息

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
    return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
    try {
        Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
            : ClassLoader.getSystemResources(path);
        Properties properties = new Properties();
        while (urls.hasMoreElements()) {
            properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
        }
        return loadMetadata(properties);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
    }
}

逻辑很简单,就是加载spring-autoconfigure-metadata.properties并封装到AutoConfigurationMetadata中。spring-autoconfigure-metadata.properties存储了自动装配的条件元信息,例如

#...省略...
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration.ConditionalOnClass=com.datastax.driver.core.Cluster,reactor.core.publisher.Flux,org.springframework.data.cassandra.core.ReactiveCassandraTemplate
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration.ConditionalOnWebApplication=SERVLET
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration.ConditionalOnBean=javax.jms.ConnectionFactory
#...省略...

格式:全类名.条件=条件值

条件(列举下面三个常见的)

  • ConditionalOnClassclasspath下存在某个类才会加载
  • ConditionalOnBean:容器内下存在某个bean才会加载
  • ConditionalOnWebApplication:在什么样的web环境下才会加载

2、获取自动装配的类信息

这里的消息包括两个:最终需要自动装配的类列表,和被排除的类列表

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
                                                           AnnotationMetadata annotationMetadata) {
    //1、判断是否允许自动装配,如果否则直接返回空对象
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    //2、获取@EnableAutoConfiguration注解上标注的类的元信息
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //3、获取自动装配的候选类名集合
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    //4、可能存在重复,所以这里进行去重(因为是从配置文件类名获取的,可能会配置重,所有先去下重)
    configurations = removeDuplicates(configurations);
    //5、获取自动装配组件的排除名单
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    //6、检查排除名单是否合法
    checkExcludedClasses(configurations, exclusions);
    //7、排除exclusions中的类
    configurations.removeAll(exclusions);
    //8、执行过滤操作,依赖前面AutoConfigurationMetadataLoader.loadMetadata获取的autoConfigurationMetadata
    configurations = filter(configurations, autoConfigurationMetadata);
    //9、触发自动装配的导入事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    //10、返回信息
    return new AutoConfigurationEntry(configurations, exclusions);
}

分为一下10个步骤

2.1)判断是否允许自动装配,如果否则直接返回空对象

//1、判断是否允许自动装配,如果否则直接返回空对象
if (!isEnabled(annotationMetadata)) {
    return EMPTY_ENTRY;
}
protected boolean isEnabled(AnnotationMetadata metadata) {
    if (getClass() == AutoConfigurationImportSelector.class) {
        return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
    }
    return true;
}

如果是AutoConfigurationImportSelector类型,则需要判断配置中的spring.boot.enableautoconfiguration是否为true,否则直接返回空对象

2.2)获取@EnableAutoConfiguration注解上标注的类的元信息

//2、获取@EnableAutoConfiguration注解上标注的类的元信息
AnnotationAttributes attributes = getAttributes(annotationMetadata);
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
    String name = getAnnotationClass().getName();
    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;
}

获取注解上的元信息

2.3)获取自动装配的候选类名集合

//3、获取自动装配的候选类名集合
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    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;
}

核心方法是SpringFactoriesLoader中的loadFactoryNames方法

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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 factoryTypeName = ((String) entry.getKey()).trim();
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                           FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

搜索指定ClassLoader下所有的META/spring.factories资源内容,可能会返回多个

spring.factories中存放了所有自动装配的候选类,这些类名格式都是XXXAutoConfiguration

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
......

将这些资源内容作为Properties文件读取,合并为一个Key为接口的全类名,Value是实现全类名列表的Map,作为loadSpringFactories的返回值

再从上一步返回的Map中查找并返回方法指定类名所映射的实现类全类名列表

2.4)去重

//4、可能存在重复,所以这里进行去重(因为是从配置文件类名获取的,可能会配置重,所有先去下重)
configurations = removeDuplicates(configurations);

因为配置文件可能存在重复,因此这里手动做了去重处理

protected final <T> List<T> removeDuplicates(List<T> list) {
    return new ArrayList<>(new LinkedHashSet<>(list));
}

使用到LinkedHashSet来去重

2.5)获取自动装配组件的排除名单

//5、获取自动装配组件的排除名单
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    Set<String> excluded = new LinkedHashSet<>();
    excluded.addAll(asList(attributes, "exclude"));
    excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
    excluded.addAll(getExcludeAutoConfigurationsProperty());
    return excluded;
}
private List<String> getExcludeAutoConfigurationsProperty() {
    if (getEnvironment() instanceof ConfigurableEnvironment) {
        Binder binder = Binder.get(getEnvironment());
        return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList)
            .orElse(Collections.emptyList());
    }
    String[] excludes = getEnvironment().getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
    return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}

排除名单来源于三个地方

  • @EnableAutoConfiguration注解中的exclue属性
  • @EnableAutoConfiguration注解中的excludeName属性
  • spring.autoconfigure.exclude配置

2.6)检查排除名单是否合法

//6、检查排除名单是否合法
checkExcludedClasses(configurations, exclusions);
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
    List<String> invalidExcludes = new ArrayList<>(exclusions.size());
    for (String exclusion : exclusions) {
        if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
            invalidExcludes.add(exclusion);
        }
    }
    if (!invalidExcludes.isEmpty()) {
        handleInvalidExcludes(invalidExcludes);
    }
}
protected void handleInvalidExcludes(List<String> invalidExcludes) {
    StringBuilder message = new StringBuilder();
    for (String exclude : invalidExcludes) {
        message.append("\t- ").append(exclude).append(String.format("%n"));
    }
    throw new IllegalStateException(String.format(
        "The following classes could not be excluded because they are not auto-configuration classes:%n%s",
        message));
}

当排除类存在于当前ClassLoader,且不在自动装配的候选类名单上,则当前排除类非法,会触发IllegalStateException异常

2.7)排除exclusions中的类

//7、排除exclusions中的类
configurations.removeAll(exclusions);

2.8)执行过滤操作

//8、执行过滤操作,依赖前面AutoConfigurationMetadataLoader.loadMetadata获取的autoConfigurationMetadata
configurations = filter(configurations, autoConfigurationMetadata);
  • 第一个参数:自动装配候选名单
  • 第二个参数:自动装配的条件元信息
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    //将候选名单转化为数组,目的是为了下面的参数匹配
    String[] candidates = StringUtils.toStringArray(configurations);
    //每个位置都标记是否需要过滤
    boolean[] skip = new boolean[candidates.length];
    //是否发生过过滤,目的是如果没有发生过,则不需要遍历skip数组进行筛选
    boolean skipped = false;
    //获取当前beanClassLoader下的所有AutoConfigurationImportFilter
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        invokeAwareMethods(filter);
        //匹配计算出结果
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        //根据结果更新skip
        for (int i = 0; i < match.length; i++) {
            //如果不匹配,则表示当前位置的类不满足条件,需要过滤掉
            if (!match[i]) {
                skip[i] = true;
                //这里是手动释放引用,方便下次GC回收
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    if (!skipped) {
        return configurations;
    }
    //过滤出需要加载的类
    List<String> result = new ArrayList<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }
    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 new ArrayList<>(result);
}
//获取当前beanClassLoader下的所有AutoConfigurationImportFilter
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}

该方法核心就是根据spring-autoconfigure-metadata.properties中配置的条件进行候选名单的过滤,目的是减少不必要的类加载,提高启动速度

2.9)触发自动装配的导入事件

//9、触发自动装配的导入事件
fireAutoConfigurationImportEvents(configurations, exclusions);
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
    List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
    if (!listeners.isEmpty()) {
        AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
        for (AutoConfigurationImportListener listener : listeners) {
            invokeAwareMethods(listener);
            listener.onAutoConfigurationImportEvent(event);
        }
    }
}
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}

获取当前beanClassLoader下的所有的AutoConfigurationImportListener实例,执行对应的监听方法

2.10)返回自动装配信息

//10、返回信息
return new AutoConfigurationEntry(configurations, exclusions);

返回的信息内容

  • List<String> configurations:最终需要自动装配的类列表
  • Set<String> exclusions:排除名单

四、总结(自动装配流程)

  • 查询配置spring.boot.enableautoconfiguration,如果是true则继续,否则表示不启用自动装配,直接返回空对象
  • 读取所有META-INF/spring-autoconfigure-metadata.properties资源,保存为自动装配的条件元信息,后续用来做最后的过滤
  • 读取所有META-INF/spring.factories资源中@EnableAutoConfiguration所关联的自动装配Class集合
  • 读取当前配置类所标注的@EnableAutoConfiguration属性exclude和excludeName,以及spring.autoconfigure.exclude配置属性合并为自动装配Class排除集合
  • 检查自动装配Class排除集合是否合法
  • 排除候选自动装配Class集合中的排除名单
  • 使用之前加载的条件元信息,再次过滤候选自动装配Class集合中Class不存在的成员
  • 自动装配Class集合过滤完成后,触发AutoConfigurationImportEvent监听器执行
  • 返回装配Class集合+排除名单

到此这篇关于关于SpringBoot的自动装配原理详解的文章就介绍到这了,更多相关SpringBoot的自动装配内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论