【SpringBoot学习笔记 四】SpringBoot自动配置原理(下)

简介: 【SpringBoot学习笔记 四】SpringBoot自动配置原理(下)

3 selectImports将 process 方法处理后得到的自动配置类,进行过滤、排除,最后将所有自动配置类添加到容器中

public Iterable<DeferredImportSelector.Group.Entry> selectImports() {
    if (this.autoConfigurationEntries.isEmpty()) {
        return Collections.emptyList();
    } else {
        //获取所有需要排除的配置类
        Set<String> allExclusions = (Set)this.autoConfigurationEntries.stream().
                map(AutoConfigurationImportSelector.AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
        //获取所有经过自动化配置过滤器的配置类
        Set<String> processedConfigurations = (Set)this.autoConfigurationEntries.stream().map(AutoConfigurationImportSelector.
                AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));
        //排除过滤后配置类中需要排除的类
        processedConfigurations.removeAll(allExclusions);
        return (Iterable)this.sortAutoConfigurations(processedConfigurations,
                this.getAutoConfigurationMetadata()).stream().map((importClassName) -> {
            return new DeferredImportSelector.Group.Entry((AnnotationMetadata)this.entries.get(importClassName), importClassName);
        }).collect(Collectors.toList());
    }
}

SpringBoot自动配置绑定和修改

spring.factories 文件中的所有自动配置类(xxxAutoConfiguration),都是必须在一定的条件下才会作为组件添加到容器中,配置的内容才会生效。这些限制条件在 Spring Boot 中以 @Conditional 派生注解的形式体现

下面我们以 ServletWebServerFactoryAutoConfiguration 为例,介绍 Spring Boot 自动配置是如何生效的

ServletWebServerFactoryAutoConfiguration自动配置类

ServletWebServerFactoryAutoConfiguration配置类的代码如下:

@Configuration(   //表示这是一个配置类,与 xml 配置文件等价,也可以给容器中添加组件
        proxyBeanMethods = false
)
@AutoConfigureOrder(-2147483648)
@ConditionalOnClass({ServletRequest.class})//判断当前项目有没有 ServletRequest 这个类
@ConditionalOnWebApplication(// 判断当前应用是否是 web 应用,如果是,当前配置类生效 
type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
//启动指定类的属性配置(ConfigurationProperties)功能;将配置文件中对应的值和 ServerProperties 绑定起来;并把 ServerProperties 加入到ioc容器中
@Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
    public ServletWebServerFactoryAutoConfiguration() {
    }
    @Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties, ObjectProvider<WebListenerRegistrar> webListenerRegistrars) {
        return new ServletWebServerFactoryCustomizer(serverProperties, (List) webListenerRegistrars.orderedStream().collect(Collectors.toList()));
    }
    @Bean
    @ConditionalOnClass(
            name = {"org.apache.catalina.startup.Tomcat"}
    )
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {
        return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }
    @Bean
    @ConditionalOnMissingFilterBean({ForwardedHeaderFilter.class})
    @ConditionalOnProperty(
            value = {"server.forward-headers-strategy"},
            havingValue = "framework"
    )
    public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
        ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
        FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean(filter, new ServletRegistrationBean[0]);
        registration.setDispatcherTypes(DispatcherType.REQUEST, new DispatcherType[]{DispatcherType.ASYNC, DispatcherType.ERROR});
        registration.setOrder(-2147483648);
        return registration;
    }
    public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
        private ConfigurableListableBeanFactory beanFactory;
        public BeanPostProcessorsRegistrar() {
        }
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            if (beanFactory instanceof ConfigurableListableBeanFactory) {
                this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
            }
        }
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            if (this.beanFactory != null) {
                this.registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class, WebServerFactoryCustomizerBeanPostProcessor::new);
                this.registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new);
            }
        }
        private <T> void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<T> beanClass, Supplier<T> instanceSupplier) {
            if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
                RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass, instanceSupplier);
                beanDefinition.setSynthetic(true);
                registry.registerBeanDefinition(name, beanDefinition);
            }
        }
    }
}

该类使用了以下注解:

  • @Configuration:用于定义一个配置类,可用于替换 Spring 中的 xml 配置文件;
  • @Bean:被 @Configuration 注解的类内部,可以包含有一个或多个被 @Bean 注解的方法,用于构建一个 Bean,并添加到 Spring 容器中;该注解与 spring 配置文件中 等价,方法名与 的 id 或 name 属性等价,方法返回值与 class 属性等价;
  • 除了 @Configuration 和 @Bean 注解外,该类还使用 5 个 @Conditional 衍生注解:
  • @ConditionalOnClass({ServletRequest.class}):判断当前项目是否存在 ServletRequest 这个类,若存在,则该配置类生效。
  • @ConditionalOnWebApplication(type = Type.SERVLET):判断当前应用是否是 Web 应用,如果是的话,当前配置类生效。
  • @ConditionalOnClass(name = {"org.apache.catalina.startup.Tomcat"}):判断是否存在 Tomcat 类,若存在则该方法生效。
  • @ConditionalOnMissingFilterBean({ForwardedHeaderFilter.class}):判断容器中是否有 ForwardedHeaderFilter 这个过滤器,若不存在则该方法生效。
  • @ConditionalOnProperty(value = {"server.forward-headers-strategy"},havingValue = "framework"):判断配置文件中是否存在 server.forward-headers-strategy = framework,若不存在则该方法生效。

那么我们想要给自动配置类中属性什么样的值又是怎么实现的呢?

ServerProperties 类

ServletWebServerFactoryAutoConfiguration 类还使用了一个 @EnableConfigurationProperties 注解,通过该注解导入了一个 ServerProperties 类,其部分源码如下

@ConfigurationProperties(
    prefix = "server",
    ignoreUnknownFields = true
)
public class ServerProperties {
    private Integer port;
    private InetAddress address;
    @NestedConfigurationProperty
    private final ErrorProperties error = new ErrorProperties();
    private ServerProperties.ForwardHeadersStrategy forwardHeadersStrategy;
    private String serverHeader;
    private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8L);
    private Shutdown shutdown;
    @NestedConfigurationProperty
    private Ssl ssl;
    @NestedConfigurationProperty
    private final Compression compression;
    @NestedConfigurationProperty
    private final Http2 http2;
    private final ServerProperties.Servlet servlet;
    private final ServerProperties.Tomcat tomcat;
    private final ServerProperties.Jetty jetty;
    private final ServerProperties.Netty netty;
    private final ServerProperties.Undertow undertow;
    public ServerProperties() {
        this.shutdown = Shutdown.IMMEDIATE;
        this.compression = new Compression();
        this.http2 = new Http2();
        this.servlet = new ServerProperties.Servlet();
        this.tomcat = new ServerProperties.Tomcat();
        this.jetty = new ServerProperties.Jetty();
        this.netty = new ServerProperties.Netty();
        this.undertow = new ServerProperties.Undertow();
    }
    ....
}

我们看到,ServletWebServerFactoryAutoConfiguration 使用了一个 @EnableConfigurationProperties 注解,通过该注解导入了一个 ServerProperties 类,而 ServerProperties 类上则使用了一个 @ConfigurationProperties 注解。这其实是 Spring Boot 自动配置机制中的通用用法。

自动配置实现步骤

@ConfigurationProperties 注解的作用,是将这个类的所有属性与配置文件中相关的配置进行绑定,以便于获取或修改配置,但是 @ConfigurationProperties 功能是由容器提供的,被它注解的类必须是容器中的一个组件,否则该功能就无法使用。而 @EnableConfigurationProperties 注解的作用正是将指定的类以组件的形式注入到 IOC 容器中,并开启其 @ConfigurationProperties 功能。因此,@ConfigurationProperties + @EnableConfigurationProperties 组合使用,便可以为 XxxProperties 类实现配置绑定功能。整体回顾下:

  1. SpringBoot启动会加载大量的自动配置类
  2. 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中
  3. 我们再来看这个自动配置类中到底配置了哪些组件,(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可

而这一切机制都是通过SpringBoot主启动类启动时的自动发现机制实现的。

其实SpringBoot的IOC和DI管理就是通过@Configuration纯注解方式实现的,提前编写好自动配置类,通过Spring Factories 机制发现导入,然后再读取配置文件中的值并绑定就可以直接使用或修改了。

自动配置类 XxxAutoConfiguration 负责使用 XxxProperties 中属性进行自动配置,而 XxxProperties 则负责将自动配置属性与配置文件的相关配置进行绑定,以便于用户通过配置文件修改默认的自动配置。也就是说,真正限制我们可以在配置文件中配置哪些属性的类就是这些 XxxxProperties 类,它与配置文件中定义的 prefix 关键字开头的一组属性是唯一对应的,这也是为什么我们在yaml这篇Blog【SpringBoot学习笔记 三】Profile多环境配置及配置优先级中提到的servler配置会自动生效。

Spring Boot 中为我们提供了大量的自动配置类 XxxAutoConfiguration 以及 XxxProperties,每个自动配置类 XxxAutoConfiguration 都使用了 @EnableConfigurationProperties 注解,而每个 XxxProperties 上都使用 @ConfigurationProperties 注解,再回顾下我在这篇Blog中【SpringBoot学习笔记 二】YAML格式文件配置方式详解提到的Person配置为什么能被读取,就是因为这其实就是一个手动的配置,我们自己写配置,自己绑定配置与组件

手动写配置

person:
  name: tml
  age: 30
  pets:
    -dog
    -cat
    -pig
  car:
    name: 蔚来es6
url:
  domain: www.baidu.com
  location: china

手动注册组件,手动将配置与组件绑定

package com.example.springboot.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.util.List;
/**
 * * @Name Person
 * * @Description
 * * @author tianmaolin
 * * @Data 2021/9/27
 */
@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
@ConfigurationProperties(prefix = "person")
@PropertySource(value = "classpath:application.properties")
public class Person {
    //@ConfigurationProperties配置yaml自动JavaBean注入
    private String name;
    private Integer age;
    private List<String> pets;
    private Car car;
    //@Value配置yaml属性引入,SPEL表达式获取值
    @Value("${url.domain}")
    private String url;
    @Value("${url.location}")
    private String location;
    //@Value配置外部配置文件属性绑定
    @Value("${spring.application.name}")
    private String appName;
}

总结一下

话不多说,通过学习理解整理了如下的流程图,也就是为什么SpringBoot可以基本实现零配置启动,其实并不是零配置,而是Spring提前给我们做好了一切而已。

目录
打赏
0
0
0
0
32
分享
相关文章
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
45 0
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
72 0
SpringBoot自动配置及自定义Starter
Java程序员依赖Spring框架简化开发,但复杂的配置文件增加了负担。SpringBoot以“约定大于配置”理念简化了这一过程,通过引入各种Starter并加载默认配置,几乎做到开箱即用。
66 10
SpringBoot自动配置及自定义Starter
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
SpringBoot配置跨模块扫描问题解决方案
在分布式项目中,使用Maven进行多模块开发时,某些模块(如xxx-common)没有启动类。如何将这些模块中的类注册为Spring管理的Bean对象?本文通过案例分析,介绍了两种解决方案:常规方案是通过`@SpringBootApplication(scanBasePackages)`指定扫描路径;推荐方案是保持各模块包结构一致(如com.xxx),利用SpringBoot默认扫描规则自动识别其他模块中的组件,简化配置。
SpringBoot配置跨模块扫描问题解决方案
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
100 14
|
2月前
|
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
88 1
SpringBoot入门(7)- 配置热部署devtools工具
springboot自动配置原理
启动类@SpringbootApplication注解下,有三个关键注解 (1)@springbootConfiguration:表示启动类是一个自动配置类 (2)@CompontScan:扫描启动类所在包外的组件到容器中 (3)@EnableConfigutarion:最关键的一个注解,他拥有两个子注解,其中@AutoConfigurationpackageu会将启动类所在包下的所有组件到容器中,@Import会导入一个自动配置文件选择器,他会去加载META_INF目录下的spring.factories文件,这个文件中存放很大自动配置类的全类名,这些类会根据元注解的装配条件生效,生效
springboot中路径默认配置与重定向/转发所存在的域对象
Spring Boot 提供了简便的路径默认配置和强大的重定向/转发机制,通过合理使用这些功能,可以实现灵活的请求处理和数据传递。理解并掌握不同域对象的生命周期和使用场景,是构建高效、健壮 Web 应用的关键。通过上述详细介绍和示例,相信读者能够更好地应用这些知识,优化自己的 Spring Boot 应用。
57 3
springboot启动配置文件-bootstrap.yml常用基本配置
以上是一些常用的基本配置项,在实际应用中可能会根据需求有所变化。通过合理配置 `bootstrap.yml`文件,可以确保应用程序在启动阶段加载正确的配置,并顺利启动运行。
393 2