SpringBoot自动装配原理

本文介绍了SpringBoot自动装配的原理,从元注解和组合注解开始,详细阐述了@SpringbootApplication注解的组成部分,如@ComponentScan、@SpringBootConfiguration和@EnableAutoConfiguration。重点解析了@EnableAutoConfiguration中的@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class),说明了如何通过自动配置类和条件判断来实现Bean的自动装配。
摘要由CSDN通过智能技术生成

SpringBoot 自动装配原理

1. 元注解和组合注解

元注解: 主要作用就是为其他注解提供解释说明

@Target(ElementType.TYPE) 表明该注解用于类、接口、注解、枚举上
@Retention(RetentionPolicy.RUNTIME) 表明该注解将会被保留在Class文件上,且在JVM中也保留(运行时可获得)
@Documented 将注解包含在Javadoc中(javadoc中默认不保留注解)
@Inherited 表示父类注解将会被子类继承得到

组合注解: 字面意思是多个注解组合形成,被注解的注解就是组合注解

2. 自动装配原理

2.1 Springboot的主启动类
@SpringBootApplication
public class Demo1Application {
    public static void main(String[] args) {
        SpringApplication.run(Demo1Application.class, args);
    }
}

只要标注了@SpringbootApplication的类就是启动类,启动类通过SpringApplication.run方法找到主启动类中的 @SpringbootApplication注解即可实现自动装配,因此@SpringbootApplication即为启动类的核心

2.2 @SpringbootApplication注解

按住Ctrl点进@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) })
public @interface SpringBootApplication {

可以看到, @SpringbootApplication是一个组合注解,除了元注解外,由三个注解组成

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
2.2.1 @ComponentScan

这个注解就是扫描包,并放进spring容器中。

2.2.2 @SpringBootConfiguration

该注解也是一个组合注解,且组成十分简单,就是对@Configuration注解的一个封装,表示被注解类是一个 配置类,该注解表明主启动类其实也是一个配置类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
2.2.3 @EnableAutoConfiguration

这个注解是实现自动装配的核心注解,也是一个组合注解

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

@AutoConfigurationPackage里面就是一个@Import注解。@Import的作用就是给容器中注册一个组件,所以它其实就是注册了Registrar这个组件,

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImports(metadata));
        }
    }

其中: AnnotationMetadata metadata 即注解元信息,通过 metadata 拿到了主启动类的信息,打个断点看下

在这里插入图片描述

在这里插入图片描述

其实就是将 启动类所在包及子包里面所有组件扫描加载到Spring容器。

2.3.3.2 @Import(AutoConfigurationImportSelector.class)

这个注解导入了一个 AutoConfigurationImportSelector组件,这个组件继承了 DeferredImportSelector延迟导入选择器,并重写了其 selectImports方法

@Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // 首先判断是否禁用了自动装配的功能
        if (!isEnabled(annotationMetadata)) {
            // 若开启了,则返回一个空数组
            return NO_IMPORTS;
        }
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

通过断点看到这个方法将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。

这些自动配置类都是 xxAutoConfiguration命名方式。

自动装配组件

具体如何自动导入的: 可以看到,这个方法首先判断 @EnableAutoConfiguration这个注解有没有开启,

isEnabled

protected boolean isEnabled(AnnotationMetadata metadata) {
    if (getClass() == AutoConfigurationImportSelector.class) {
        return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
    }
    return true;
}

然后通过 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);
    }

官方注释

Return the AutoConfigurationImportSelector.AutoConfigurationEntry based on the AnnotationMetadata of the importing @Configuration class.
Params:
annotationMetadata – the annotation metadata of the configuration class
Returns:
the auto-configurations that should be imported

可以看到主要是通过 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;
    }

getCandidateConfigurations()通过SpringFactoriesLoaderloadFactoryNames()方法获取到一个所有自动配置类类名的List集合,

loadFactoryNames()调用 loadSpringFactories方法从 FACTORIES_RESOURCE_LOCATION META-INF/spring.factories这个目录中获取配置信息,封装为Properties对象,但是,springboot并不会直接加载所有配置,而是需要满足一定条件才会加载,@ConditionOnxxx 注解判断是否开启自动装配

注意:loadSpringFactories并不是直接去加载资源的,它首先从缓存 cache中读取,若没有,再去加载

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
...
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

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

    result = new HashMap<>();
    try {
        Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        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();
                String[] factoryImplementationNames =
                    StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                for (String factoryImplementationName : factoryImplementationNames) {
                    result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                        .add(factoryImplementationName.trim());
                }
            }
        }

        // Replace all lists with unmodifiable lists containing unique elements
        result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                          .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
        cache.put(classLoader, result);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                           FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result;
}

找一个 spring.factories看下,在META-INF目录下

spring.factories

RedisAutoConfiguration为例

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

@EnableConfigurationProperties注解中引入的类就封装了对应的自动装配类的可配置属性定义了我们可以再配置文件中的定义的属性

RedisProperties部分源码

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {

    /**
     * Database index used by the connection factory.
     */
    private int database = 0;

    /**
     * Connection URL. Overrides host, port, and password. User is ignored. Example:
     * redis://user:password@example.com:6379
     */
    private String url;

    /**
     * Redis server host.
     */
    private String host = "localhost";

    /**
     * Login username of the redis server.
     */
    private String username;

    /**
     * Login password of the redis server.
     */
    private String password;

    /**
     * Redis server port.
     */
    private int port = 6379;

    /**
     * Whether to enable SSL support.
     */
    private boolean ssl;
2.3 自动装配原理总结

Springboot自动装配流程图:

自动装配流程图

springboot是通过启动类的SpringBootApplication注解进行开始解析的,他会根据EnableAutoConfiguration开启自动化配置,里面有个核心方法ImportSelect选择性的导入,通过loadFanctoryNames方法根据classpash路径以MATA-INF/spring.factorces下面以什么什么EnableAutoConfiguration开头的key去加载里面所有对应的自动化配置,他并不是直接将所有自动化配置全部导入,在他每个自动化配置里面都有条件判断注解,先判断是否引入相互的jar包,再判断容器是否有bean再进行注入到bean容器

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值