Spring Boot自动装配原理

什么是Spring Boot

Spring是一个为了解决企业应用级开发的复杂性而创建的简化开发的开源框架,随着Spring的不断发展,涉及的领域越来越广,项目整合所需要的配置文件越来越复杂,慢慢的变得不那么简单易用,甚至被称为配置地狱。Spring Boot就是在这样的背景下被抽象处理来的开发框架,目的是让大家更容易的使用Spring。Spring Boot以约定大于配置为核心思想,默认帮我们做了很多配置,就像Maven整合了所有的jar包,而Spring Boot整合了所有的框架。

自动装配原理

在Spring4以后,官方推荐使用JavaConfig来代替applicationContext.xml声明将Bean交给Spring容器管理,Spring Boot的自动装配完全基于JavaConfig。每个场景启动器都配置了一个JavaConfig,Spring Boot启动时将这些JavaConfig配置类加入到IOC容器,从而完成自动注入。

Spring Boot通过main()方法启动SpringApplication类的静态方法run()来启动项目,run()方法是在默认配置已经完成的基础上(通过注解@SpringBootApplication实现)启动SpringApplication的。所以@SpringBootApplication注解是自动装配的入口。

1. @SpringBootApplication注解

@SpringBootApplication是一个复合注解,除了4个元注解外,其他三个注解比较重要:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan。

@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) })
public @interface SpringBootApplication {}

1.1 @SpringBootConfiguration注解

标注当前类为配置类,它的底层是一个@Configuration注解,两者功能一致,都是将当前类内声明的以@Bean注解标记的方法的返回实例纳入到spring容器中。

1.2 @ComponentScan注解

实现注解扫描,默认扫描当前类所在的包及其子包下的注解,将@Controller、@Service、@Component、@Repository等注解标注的类加载到IOC容器中;

1.3 @EnableAutoConfiguration注解

这个注解也是一个复合注解,里面包含两个重要注解:@AutoConfigurationPackage和@Import。

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

1.3.1 @AutoConfigurationPackage注解

和@ComponentScan作用一样,也是将主配置类所在的包及其子包里面的组件扫描到IOC容器中,但是二者扫描的对象不一样。

1.3.2 @Import注解

@Import注解是Spring提供用来给IOC导入Bean的一种方式,这里导入了一个类名称为AutoConfigurationImportSelector的Bean,从名字来看这个类的功能是用来完成自动装配导入工作。Spring Boot项目启动时会回调AutoConfigurationImportSelector的process()方法(Spring Boot 1.x是回调selectImports()方法)。

process()方法

@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                 () -> String.format("Only %s implementations are supported, got %s",
                                     AutoConfigurationImportSelector.class.getSimpleName(),
                                     deferredImportSelector.getClass().getName()));
    
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(annotationMetadata);
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}

继续往下走,进入getAutoConfigurationEntry()方法。

getAutoConfigurationEntry()方法

protected AutoConfigurationEntry getAutoConfigurationEntry(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 = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

继续往下走,进入getCandidateConfigurations()方法

getCandidateConfigurations()方法

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

继续往下走,进入loadFactoryNames()方法

loadFactoryNames()方法

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

继续往下走,进入loadSpringFactories()方法

loadSpringFactories()方法

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

可以看出,getCandidateConfigurations()方法使用SpringFactoriesLoader来加载JAR包下面的META-INF/spring.factories文件,在spring-boot-autoconfigure的JAR包下就有这个配置文件,文件是key=value的形式,其中key为EnableAutoConfiguration的全类名,value是所有的自动配置类xxxAutoConfiguration类的全类名,以逗号分割。至此,找到了所有JavaConfig配置类全限定名,然后解析@Configuration + @Bean给IOC容器注入Bean。整体的流程图如下所示:

自动配置生效

每一个xxxAutoConfiguration自动配置类都是在某些条件之下才会生效的,这些条件以注解的形式给出。常见的条件注解有以下几种:

@ConditionalOnBean:当容器里有指定的bean的条件下。
@ConditionalOnMissingBean:当容器里不存在指定bean的条件下。
@ConditionalOnClass:当类路径下有指定类的条件下。
@ConditionalOnMissingClass:当类路径下不存在指定类的条件下。
@ConditionalOnProperty:指定的属性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当xxx.xxx为enable时条件的布尔值为true,如果没有设置的情况下也为true。

以HttpEncodingAutoConfiguration为例,只要三个条件注解满足,这个配置类就会生效,然后给容器中添加组件,这些组件的属性值是从ServerProperties类中获得的,而ServerProperties类中的每一个属性又是和配置文件(application.properties)绑定的,我们可以在配置文件中指定ServerProperties类中每一个属性的初始值。

@AutoConfiguration
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {...}

ServerProperties类上通过注解定义了设置它内部属性的前缀为"server",然后我们可以在application.properties配置文件中输入server后面就会提示该类里面所有的属性。

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {...}

总结

  1. Spring Boot在启动时从类路径下的META-INF/spring-factories中获取到所有的自动配置类(xxxAutoConfiguration);
  2. 将这些自动配置类导入容器,条件满足的配置类就会生效,然后给容器中添加各种组件;
  3. 这些组件的属性值是从对应的xxxProperties类中获得的,而xxxProperties类又和配置文件application.properties绑定,我们可以通过配置文件去指定我们想要设置的初始值;
  4. xxxAutoConfiguration:自动配置类,给容器添加组件;
  5. xxxProperties:封装配置文件中相关的属性。
posted @   学海无涯#  阅读(258)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示