zoukankan      html  css  js  c++  java
  • SpringBoot自动配置原理

    SpringBoot自动配置主要通过@EnableAutoConfiguration, @Conditional, @EnableConfigurationProperties或者@ConfigurationProperties 等几个注解来进行自动配置完成的。

    @EnableAutoConfiguration开启自动配置,主要作用就是调用Spring-Core包里的loadFactoryNames(),将autoconfig 包里的已经写好的自动配置加载进来。

    @Conditional条件注解,通过判断类路径下有没有相应配置的jar包来确定是否加载和自动配置这个类。

    @EnableConfigurationProperties的作用就是,给自动配置提供具体的配置参数,只需要写在 application.properties 中,就可以通过映射写入配置类的POJO属性中。

    @EnableAutoConfiguration

    @Enable*注解并不是SpringBoot新发明的注解,Spring 3框架就引入了这些注释,用这些注释替代XML配置文件。比如:
    @EnableTransactionManagement注解,它能够声明事务管理
    @EnableWebMvc注解,它能启用Spring MVC
    @EnableScheduling注解,它可以初始化一个调度器。

    这些注释事实上都是简单的配置,通过@Import注解导入。
    从启动类的@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里通过@Import导入了AutoConfigurationImportSelector

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

    找到selectImports()方法,他调用了getCandidateConfigurations()方法,在这里,这个方法又调用了Spring Core包中的loadFactoryNames()方法。这个方法的作用是,会查询META-INF/spring.factories文件中包含的JAR文件。

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
                autoConfigurationMetadata, annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    protected AutoConfigurationEntry getAutoConfigurationEntry(
            AutoConfigurationMetadata autoConfigurationMetadata,
            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 = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

    当找到spring.factories文件后,SpringFactoriesLoader将查询配置文件命名的属性。

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

    org.springframework.boot.autoconfigure的spring.factories

    spring.factories文件中,可以看到一系列Spring Boot自动配置的列表

    下面我们来看spring.factories文件自动配置Kafka的细节,KafkaAutoConfiguration:

    @Configuration
    @ConditionalOnClass(KafkaTemplate.class)
    @EnableConfigurationProperties(KafkaProperties.class)
    @Import({ KafkaAnnotationDrivenConfiguration.class,
            KafkaStreamsAnnotationDrivenConfiguration.class })
    public class KafkaAutoConfiguration {
    
        private final KafkaProperties properties;
    
        private final RecordMessageConverter messageConverter;
    
        public KafkaAutoConfiguration(KafkaProperties properties,
                ObjectProvider<RecordMessageConverter> messageConverter) {
            this.properties = properties;
            this.messageConverter = messageConverter.getIfUnique();
        }
    
        @Bean
        @ConditionalOnMissingBean(KafkaTemplate.class)
        public KafkaTemplate<?, ?> kafkaTemplate(
                ProducerFactory<Object, Object> kafkaProducerFactory,
                ProducerListener<Object, Object> kafkaProducerListener) {
            KafkaTemplate<Object, Object> kafkaTemplate = new KafkaTemplate<>(
                    kafkaProducerFactory);
            if (this.messageConverter != null) {
                kafkaTemplate.setMessageConverter(this.messageConverter);
            }
            kafkaTemplate.setProducerListener(kafkaProducerListener);
            kafkaTemplate.setDefaultTopic(this.properties.getTemplate().getDefaultTopic());
            return kafkaTemplate;
        }
    
        @Bean
        @ConditionalOnMissingBean(ProducerListener.class)
        public ProducerListener<Object, Object> kafkaProducerListener() {
            return new LoggingProducerListener<>();
        }
    
        @Bean
        @ConditionalOnMissingBean(ConsumerFactory.class)
        public ConsumerFactory<?, ?> kafkaConsumerFactory() {
            return new DefaultKafkaConsumerFactory<>(
                    this.properties.buildConsumerProperties());
        }
    
        @Bean
        @ConditionalOnMissingBean(ProducerFactory.class)
        public ProducerFactory<?, ?> kafkaProducerFactory() {
            DefaultKafkaProducerFactory<?, ?> factory = new DefaultKafkaProducerFactory<>(
                    this.properties.buildProducerProperties());
            String transactionIdPrefix = this.properties.getProducer()
                    .getTransactionIdPrefix();
            if (transactionIdPrefix != null) {
                factory.setTransactionIdPrefix(transactionIdPrefix);
            }
            return factory;
        }
    
        @Bean
        @ConditionalOnProperty(name = "spring.kafka.producer.transaction-id-prefix")
        @ConditionalOnMissingBean
        public KafkaTransactionManager<?, ?> kafkaTransactionManager(
                ProducerFactory<?, ?> producerFactory) {
            return new KafkaTransactionManager<>(producerFactory);
        }
    
        @Bean
        @ConditionalOnProperty(name = "spring.kafka.jaas.enabled")
        @ConditionalOnMissingBean
        public KafkaJaasLoginModuleInitializer kafkaJaasInitializer() throws IOException {
            KafkaJaasLoginModuleInitializer jaas = new KafkaJaasLoginModuleInitializer();
            Jaas jaasProperties = this.properties.getJaas();
            if (jaasProperties.getControlFlag() != null) {
                jaas.setControlFlag(jaasProperties.getControlFlag());
            }
            if (jaasProperties.getLoginModule() != null) {
                jaas.setLoginModule(jaasProperties.getLoginModule());
            }
            jaas.setOptions(jaasProperties.getOptions());
            return jaas;
        }
    
        @Bean
        @ConditionalOnMissingBean
        public KafkaAdmin kafkaAdmin() {
            KafkaAdmin kafkaAdmin = new KafkaAdmin(this.properties.buildAdminProperties());
            kafkaAdmin.setFatalIfBrokerNotAvailable(this.properties.getAdmin().isFailFast());
            return kafkaAdmin;
        }
    
    }

    这个类进行了简单的Spring配置,声明了Kafka所需典型Bean,和其它很多类一样,重度依赖于Spring Boot注释:
    1)@ConditionOnClass激活一个配置,当类路径中存在这个类时才会配置该类
    2)@EnableConfigurationProperties自动映射一个POJO到Spring Boot配置文件(默认是application.properties文件)的属性集。
    3)@ConditionalOnMissingBean启用一个Bean定义,但必须是这个Bean之前未定义过才有效。
    还可以使用@ AutoConfigureBefore注释、@AutoConfigureAfter注释来定义这些配置类的载入顺序。

    着重了解@Conditional注释,Spring 4框架的新特性
    此注释使得只有在特定条件满足时才启用一些配置。SrpingBoot的AutoConfig大量使用了@Conditional,它会根据运行环境来动态注入Bean。这里介绍一些@Conditional的使用和原理,并自定义@Conditional来自定义功能。

    @Conditional是SpringFramework的功能,SpringBoot在它的基础上定义了
    @ConditionalOnClass,@ConditionalOnProperty等一系列的注解来实现更丰富的内容。
    具体几个@Conditon*注解的含义
    @ConditionalOnBean
    仅仅在当前上下文中存在某个对象时,才会实例化一个Bean

    @ConditionalOnClass
    某个class位于类路径上,才会实例化一个Bean),该注解的参数对应的类必须存在,否则不解析该注解修饰的配置类

    @ConditionalOnExpression
    当表达式为true的时候,才会实例化一个Bean

    @ConditionalOnMissingBean
    仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean,该注解表示,如果存在它修饰的类的bean,则不需要再创建这个bean,可以给该注解传入参数例如@ConditionOnMissingBean(name = "example"),这个表示如果name为“example”的bean存在,这该注解修饰的代码块不执行

    @ConditionalOnMissingClass
    某个class类路径上不存在的时候,才会实例化一个Bean

    @ConditionalOnNotWebApplication
    不是web应用时,才会执行

    Properties系列注释

    @EnableConfigurationProperties
    @ConfigurationProperties(prefix = "may")

    在需要注入配置的类上加上这个注解,prefix的意思是,以该前缀打头的配置

    如果不用系统初始的application.properties配置类,而是使用自己的如winner.properties,可以如下配置

    /**
     * @PropertySource 只能加载.properties文件
     * @author winner_0715
     */
    @Configuration
    @PropertySource("classpath:winner.properties")
    @ConfigurationProperties(prefix = "winner")
    public class WinnerConfig {
        private String name;
        private String email;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    }

    最后注意在spring Boot入口类加上@EnableConfigurationProperties

    @ConfigurationProperties(prefix = "spring.kafka")
    public class KafkaProperties {
    
        /**
         * Comma-delimited list of host:port pairs to use for establishing the initial
         * connections to the Kafka cluster. Applies to all components unless overridden.
         */
        private List<String> bootstrapServers = new ArrayList<>(
                Collections.singletonList("localhost:9092"));
    
        /**
         * ID to pass to the server when making requests. Used for server-side logging.
         */
        private String clientId;
    
        /**
         * Additional properties, common to producers and consumers, used to configure the
         * client.
         */
        private final Map<String, String> properties = new HashMap<>();
    
        private final Consumer consumer = new Consumer();

    Ref:

    https://www.cnblogs.com/leihuazhe/p/7743479.html

  • 相关阅读:
    http
    python的列表生成式
    flask的登陆验证
    脚本更新流程
    k8s中job和pod的区别
    k8s中一些常见概念
    supervisord部署和使用
    flask中config
    python类的继承super()的使用
    python中类的继承
  • 原文地址:https://www.cnblogs.com/winner-0715/p/10327811.html
Copyright © 2011-2022 走看看