springboot自動配置原理:@SpringBootApplication註解分析、全局配置加載流程

springboot的自動配置是使用springboot的一大原因,那自動配置具體是如何加載運行的呢?主要是依靠@SpringBootApplication註解。

@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 {
}

@SpringBootConfiguration : Spring Boot的配置類,標註在某個類上,表示這是一個Spring Boot的配置類;

@EnableAutoConfiguration:支持自動配置的註解;

@ComponentScan組件包掃描;

@EnableAutoConfiguration:

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

它的2個核心註解是@AutoConfigurationPackage、@Import({AutoConfigurationImportSelector.class})

@AutoConfigurationPackage:

@AutoConfigurationPackage註解的主要作用是定義自動配置的包,供容器啓動時加載。

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

很明顯它的功能就是@Import({Registrar.class})的Registrar類實現的:

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }
        //容器啓動時執行
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            //註冊一個自動配置包的bean
            AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
        }
        //容器啓動時不會執行
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
        }
    }

AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());註冊了一個bean到spring容器中,(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()返回的是啓動類的包名,容器掃描加載bean的時候只會加載這個包及其子包下的bean。啓動類放在頂級目錄的原因。

@Import({AutoConfigurationImportSelector.class}):

用於自動配置導入的選擇。spring-boot-starter中引入了spring-boot-autoconfigure,spring-boot-autoconfigure中定義了衆多自動配置加載執行的配置類。pom.xml中引入springboot的starter就是用來指定:加載spring-boot-autoconfigure中哪些配置類,AutoConfigurationImportSelector是上述過程的實現類。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}
public interface DeferredImportSelector extends ImportSelector {}
public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
}

再來看看AutoConfigurationImportSelector是如何實現selectImports()方法的:

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            //獲取自動配置元數據
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            //根據元數據獲取自動配置Entry對象
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //獲取spring-boot-autoconfigure中定義的所有用於支持自動配置的配置類
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            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);
        }
    }

看一下如何獲取所有配置類的 :

    //獲取spring-boot-autoconfigure中定義的所有用於支持自動配置的配置類
    List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        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;
    }

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

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
...
}

進入loadFactoryNames()方法中可以看到是從META-INF/spring.factories配置文件中獲取的,參考下方spring.factories文件截圖,可發現spring.factories文件中聲明瞭spring-boot-autoconfigure爲每一個starter定義的配置類的全限類名,springboot就是在SpringApplication.run(...)的內部執行selectImports()方法,找到自動配置類的全限類名對應的class,然後將自動配置類加載到Spring容器中,通過加載注入全限類名對應的類來完成容器啓動時配置的自動裝載。

 XxxTemplate模板類的默認屬性修改:

springboot爲starter生成了xxTemplate的工具類,例如:RedisTemplate、MongoTemplate、KafkaTemplate等,如果想要更改默認注入的Template屬性。

方式一:

修改RedisTemplate的序列化方式、開啓事務,向容器中注入一個新的RedisTemplate即可,它會覆蓋默認的RedisTemplate:

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

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}
    @Bean("redisTemplate")
    public RedisTemplate<Object, Object> myRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        // 修改序列化爲Jackson
        template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
        // 開啓事務
        template.setEnableTransactionSupport(true);
        return template;
    }

方式二:

kafka消費接收的數據默認是字符串,如何改成一個對象?

1.首先去KafkaAutoConfiguration(XxxAutoConfiguration)中查看是否有Serializer屬性的配置

@Configuration
@ConditionalOnClass({KafkaTemplate.class})
@EnableConfigurationProperties({KafkaProperties.class})
@Import({KafkaAnnotationDrivenConfiguration.class, KafkaStreamsAnnotationDrivenConfiguration.class})
public class KafkaAutoConfiguration {
    private final KafkaProperties properties;
    private final RecordMessageConverter messageConverter;
}

2.發現沒有,再去KafkaProperties中查看:

然後直接在application.properties修改默認序列化就好:

1 spring.kafka.producer.key-serializer=org.springframework.kafka.support.serializer.JsonSerializer
2 spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
3 spring.kafka.consumer.key-deserializer=com.example.common.MyJson
4 spring.kafka.consumer.value-deserializer=com.example.common.MyJson

application.properties配置是如何生效:

每一個XxxAutoConfiguration自動配置類都是在某些條件下才會生效的,這些條件的限制在Spring Boot中以註解的形式體現,常見的條件註解有如下幾項:

@ConditionalOnBean:當容器裏有指定的bean的條件下。

@ConditionalOnMissingBean:當容器裏不存在指定bean的條件下。

@ConditionalOnClass:當類路徑下有指定類的條件下。

@ConditionalOnMissingClass:當類路徑下不存在指定類的條件下。

@ConditionalOnProperty:指定的屬性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表當xxx.xxx爲enable時條件的布爾值爲true,如果沒有設置的情況下也爲true。

以spring.redis.host=192.168.56.10爲例,觀察application.properties中的全局配置是如何生效的。

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

觀察RedisAutoConfiguration自動配置類(XxxAutoConfiguration),有一個@EnableConfigurationProperties註解:支持配置屬性,而它後面的參數是一個RedisProperties類,這就是“約定大於俗成”生效的地方。

@ConfigurationProperties(
    prefix = "spring.redis"
)
public class RedisProperties {
    private int database = 0;
    private String url;
    private String host = "localhost";
    private String password;
    private int port = 6379;
    private boolean ssl;
    private Duration timeout;
    private RedisProperties.Sentinel sentinel;
    private RedisProperties.Cluster cluster;
    private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
    private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();

    public RedisProperties() {
    }
}

RedisProperties類上的@ConfigurationProperties註解作用就是從配置文件中綁定屬性到對應的bean上,而@EnableConfigurationProperties({RedisProperties.class})負責導入這個已經綁定了屬性的bean到spring容器中。那麼所有其他的和這個類相關的屬性都可以在全局配置文件中定義,也就是說,真正“限制”我們可以在全局配置文件中配置哪些屬性的類就是這些XxxProperties類,它與配置文件中prefix關鍵字開頭的一組屬性是唯一對應的。

總結:Spring Boot啓動的時候會通過@EnableAutoConfiguration註解找到META-INF/spring.factories配置文件中的所有自動配置類,過濾後對其進行加載,而這些自動配置類都是以AutoConfiguration結尾來命名的(類中方法都是@Bean、@ConditionalOnMissingBean等注入對象到spring容器中),它實際上就是一個JavaConfig形式的Spring容器配置類,它通過以Properties結尾命名的類中取得在全局配置文件中配置的屬性如:spring.redis.host,而XxxProperties類是通過@ConfigurationProperties註解與全局配置文件中對應的屬性進行綁定的。即AutoConfiguration是一個配置類,爲了添加組件的,Properties是一個屬性類,爲了封裝配置文件中的屬性的。

如果我們自定義了一個starter的話,也要在該starter的jar包中提供 spring.factories文件,並且爲其配置org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.test.XxAutoConfiguration對應的配置類。所有框架的自動配置流程基本都是一樣的,判斷是否引入框架,獲取配置參數,根據配置參數初始化框架相應組件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章