2024源码开篇--Springboot自动装配原理,看完不会来打我!

前言

在Springboot的自动装配令开发人员减轻了大量的配置负担。

那么什么是自动装配?

自动装配就是在Spring启动过程中,装载了必要的Bean,以方便在开发的时候调用,而不用开发人员自己配置。

下面是一个Spring的启动类:

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

  

自动装配主要是通过注解@SpringBootApplication实现的,具体是怎么实现的,请继续阅读。

源码&&原理

注解分析

SPringBootApplication注解分为下面几个注解,其中和自动装配的核心注解为EnableAutoConfiguration,之后着重分析这个注解。

@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 {
    //省略
}

  

EnableAutoConfiguration

注解分析:

除了元注解,主要的注解为两个:一个是@AutoConfigurationPackage,另一个是@Import(AutoConfigurationImportSelector.class),下面一个一个进行分析。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
​
    /**
     * Environment property that can be used to override when auto-configuration is
     * enabled.
     */
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
​
    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
    Class<?>[] exclude() default {};
​
    /**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */
    String[] excludeName() default {};
​
}

  

AutoConfigurationPackage

相似的,除了元注解,只剩下@Import(AutoConfigurationPackages.Registrar.class)这个注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
​
    /**
     * Base packages that should be registered with {@link AutoConfigurationPackages}.
     * <p>
     * Use {@link #basePackageClasses} for a type-safe alternative to String-based package
     * names.
     * @return the back package names
     * @since 2.3.0
     */
    String[] basePackages() default {};
​
    /**
     * Type-safe alternative to {@link #basePackages} for specifying the packages to be
     * registered with {@link AutoConfigurationPackages}.
     * <p>
     * Consider creating a special no-op marker class or interface in each package that
     * serves no purpose other than being referenced by this attribute.
     * @return the base package classes
     * @since 2.3.0
     */
    Class<?>[] basePackageClasses() default {};
​
}

  

继续分析看到导入了一个类:AutoConfigurationPackages.Registrar.class,这个类中有一个核心方法:registerBeanDefinitions,这个方法主要是加载用户自己包下的Bean。(在启动的时候可以自行Debug)

自动装配的Bean分为两部分:

一个是用户自己定义的Bean,另一个是用户在pom文件中引入的依赖Bean。

    /**
     * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
     * configuration.
     */
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
​
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        //启动的时候这里的metadata就是主启动类的全类名,会扫描与主启动类同级的包、
        //将这些包下的Bean注册到Bean容器中
            register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }
​
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImports(metadata));
        }
​
    }

  

AutoConfigurationImportSelector

这个类中有一个核心方法,getAutoConfigurationEntry,这个方法将依赖的Bean加载到Bean容器中。

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

  

这个方法里面主要逻辑:

通过这个方法去获取所有的候选配置,这个方法通过SpringFactoriesLoader去加载所有的META-INF/spring.factories里面的配置。

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这个方法,这个方法里面的有一个方法loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());而其下面又有一个主要方法classLoader.getResources(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;
    }

  

继续深入,可以看到这个方法是去找到所有的给定名字的资源,而这个地方我们给定的名字是META-INF/spring.factories,所有这个方法就是把你的项目依赖里面所有的资源文件查出来。

    public Enumeration<URL> getResources(String name) throws IOException {
        Objects.requireNonNull(name);
        @SuppressWarnings("unchecked")
        Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
        if (parent != null) {
            tmp[0] = parent.getResources(name);
        } else {
            tmp[0] = BootLoader.findResources(name);
        }
        tmp[1] = findResources(name);
​
        return new CompoundEnumeration<>(tmp);
    }

  

spring-boot-autoconfigure

为什么单独拎出来这个依赖,因为我们平时所使用的大部分依赖都要使用到这个自动配置依赖,为什么这么说呢,我们继续分析。

打开源码结构,可以看到autoconfigure中有想要找的文件,spring.factories

image-20240112174205835

打开这个文件:可以看到这个里面有一堆的自动配置类,可以包含初始化类,监听器,还有接下来的主角:EnableAutoConfiguration等等。那么这么多的类是不是都要加载,答案是不是的,至于为什么,请往下阅读。

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
​
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
​
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.autoconfigure.integration.IntegrationPropertiesEnvironmentPostProcessor
​
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
​
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
​
# 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,\
......太多了,省略了

 

加载EnableAutoConfiguration

在上面提到的方法getCandidateConfigurations中有一个函数,getSpringFactoriesLoaderFactoryClass()这个方法很简单,返回的就是EnableAutoConfiguration.class;这个类(下方第二个函数)。

    /**
     * Return the auto-configuration class names that should be considered. By default
     * this method will load candidates using {@link SpringFactoriesLoader} with
     * {@link #getSpringFactoriesLoaderFactoryClass()}.
     * @param metadata the source metadata
     * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
     * attributes}
     * @return a list of candidate configurations
     */
    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;
    }
​
    /**
     * Return the class used by {@link SpringFactoriesLoader} to load configuration
     * candidates.
     * @return the factory class
     */
    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }

  

然后我们在继续看loadFactoryNames方法,这里为了好理解,我做了一个Debug,可以看到默认会拿到key为EnableAutoConfiguration的配置类,这里有133个,这133个就是spring.factories里面org.springframework.boot.autoconfigure.EnableAutoConfiguratio下面的配置类。到此。我们知道了在哪找,以及找哪些类了,下面我们分析这133个类是不是都注入了?答案是:并没有,而是按需注入。那么怎么才叫按需注入呢?我们接下来分析。

image-20240112215951129

按需注入

这里我们在这133个待注入Bean中,随便分析几个,其他的都是一样的分析方法。

1.org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration

这里分析这个配置类,可以看到是RestTemplate的自动配置类,首先关注这个类上的注解:

@Configuration(proxyBeanMethods = false)//告诉Spring这是一个配置类
@AutoConfigureAfter(HttpMessageConvertersAutoConfiguration.class)//在HttpMessageConvertersAutoConfiguration之后装配
@ConditionalOnClass(RestTemplate.class)//项目红是否有RestTemplate.class这个类,如果有才装配
@Conditional(NotReactiveWebApplicationCondition.class)//是非响应式Web应用
public class RestTemplateAutoConfiguration {
    //.....
}

  

通过这几个注解我们看到这几个条件都是满足的,然后看这个配置类往容器中注入了哪些Bean,首先是restTemplateBuilderConfigurerrestTemplateBuilder,注解的含义我写在代码后面的注释上,可以看到这个类应该是被注入到容器中了,下面我们来验证一下。

public class RestTemplateAutoConfiguration {
​
    @Bean
    @Lazy//懒加载
    @ConditionalOnMissingBean//表示没有这个Bean才注入
    public RestTemplateBuilderConfigurer restTemplateBuilderConfigurer(
            ObjectProvider<HttpMessageConverters> messageConverters,
            ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers,
            ObjectProvider<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
        RestTemplateBuilderConfigurer configurer = new RestTemplateBuilderConfigurer();
        configurer.setHttpMessageConverters(messageConverters.getIfUnique());
        configurer.setRestTemplateCustomizers(restTemplateCustomizers.orderedStream().collect(Collectors.toList()));
        configurer.setRestTemplateRequestCustomizers(
                restTemplateRequestCustomizers.orderedStream().collect(Collectors.toList()));
        return configurer;
    }
​
    @Bean
    @Lazy
    @ConditionalOnMissingBean
    public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
        RestTemplateBuilder builder = new RestTemplateBuilder();
        return restTemplateBuilderConfigurer.configure(builder);
    }
​
    static class NotReactiveWebApplicationCondition extends NoneNestedConditions {
​
        NotReactiveWebApplicationCondition() {
            super(ConfigurationPhase.PARSE_CONFIGURATION);
        }
​
        @ConditionalOnWebApplication(type = Type.REACTIVE)
        private static class ReactiveWebApplication {
​
        }
​
    }
​
}

  

在Bean容器中获取这两个Bean,可以看到确实存在。

image-20240112225222489

2.org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

在分析一个自动配置类,可以看出这是Redis的自动配置类,当满足条件时,这个类会把redisTemplate和stringRedisTemplate放入到容器中,显然他是不满足这个条件的,因为目前的项目中并没有RedisOperations这个类,所以下面的两个bean也就没必要看了,肯定不会注入的,下面再验证一下。

import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
​
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
​
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }
​
}

  

验证程序:

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(InitProjectTemplateApplication.class, args);
        boolean beanDefinition = run.containsBeanDefinition("redisTemplate");
        System.out.println("redisTemplate是否存在-->"+beanDefinition);
        boolean builder = run.containsBeanDefinition("stringRedisTemplate");
        System.out.println("stringRedisTemplate是否存在-->"+builder);
    }
    输出:
redisTemplate是否存在-->false
stringRedisTemplate是否存在-->false

  

什么时候这两个bean才会被放进去,答案是必须要有这个类import org.springframework.data.redis.core.RedisOperations,这个类就是redis的starter里面的,所以我们平时开发的时候,引入一个spring-boot-redis-starter就可以用redisTemplate或者stringRedisTemplate直接操作redis了,一切都是spring帮我们配置好了。

 

好了,本片文章就到这里了,有问题欢迎交流。

 

 

 
posted @ 2024-01-12 23:11  要变光头强  阅读(13)  评论(0编辑  收藏  举报