Spring Boot 自动配置的原理及实现

 2022-08-25
原文地址:https://cloud.tencent.com/developer/article/2079140

SpringBoot自动配置的实现原理

SpringBoot的核心就是自动配置,自动配置又是基于条件判断来配置Bean。关于自动配置的源码在spring-boot-autoconfigure-2.0.3.RELEASE.jar

202208252255578781.png

回顾配置属性

在通常需要我们在property中配置信息时,通常使用@ConfigurationProperties(pefix=“前缀”)注解的方式从配置文件中获取配置,如下:

    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    @RestController
    @ConfigurationProperties(prefix = "test")
    //@Component //如果这里添加了注解那么在自动配置类的时候就不用添加@enableConfigurationProperties(HelloProperties.class)注解.
    public class Demo {
    
     
    	
        private String msg="default";//现在我们在配置文件写hello.msg=world,因为简单就不再展示;如果那么默认为default.
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
    	}
    	
    	
    	@RequestMapping("/msg")
    	public Object index(){
    		return this.msg;
    	}
    
    }

application.yml中配置信息

    test:
      msg: bamboo

访问url获取配置信息返回的值 http://localhost:8080/msg

如果把application.yml中的配置信息注释掉则默认使用default值,否则使用配置信息中的值,以上便是普通配置方式

解析

SpringBoot运行原理 先看@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:标记当前类为配置类 @EnableAutoConfiguration:开启自动配置 @ComponentScan:扫描主类所在的同级包以及下级包里的Bean 关键是@EnableAutoConfiguration

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
       String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
       Class<?>[] exclude() default {};
       String[] excludeName() default {};
    }

最关键的要属@Import(EnableAutoConfigurationImportSelector.class),借助 EnableAutoConfigurationImportSelector ,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器:通过@Import(AutoConfigurationImportSelector.class)导入的配置功能, AutoConfigurationImportSelector中的方法getCandidateConfigurations,得到待配置的class的类名集合,这个集合就是所有需要进行自动配置的类,而是是否配置的关键在于META-INF/spring.factories文件中是否存在该配置信息

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

打开,如下图可以看到所有需要配置的类全路径都在文件中,每行一个配置,多个类名逗号分隔,而\表示忽略换行

202208252255597372.png

202208252256018403.png

整个流程如上图所示

样例讲解

以SpringApplicationAdminJmxAutoConfiguration类来看其主要构成部分

    @Configuration
    @AutoConfigureAfter({JmxAutoConfiguration.class}) //配置完JmxAutoConfiguration后再配置当前类型
    // spring.application.admin为前缀,属性为enabled,有值时为true,没有匹配到则为false:以上条件为true则实例化,否则不是实例化
    @ConditionalOnProperty( prefix = "spring.application.admin", value = {"enabled"}, havingValue = "true",  matchIfMissing = false)
    public class SpringApplicationAdminJmxAutoConfiguration

都能看到各种各样的条件判断注解,满足条件时就加载这个Bean并实例化 此类的条件注解是:@ConditionalOnProperty

@ConditionalOnBean:当容器里有指定Bean的条件下 @ConditionalOnClass:当类路径下有指定的类的条件下 @ConditionalOnExpression:基于SpEL表达式为true的时候作为判断条件才去实例化 @ConditionalOnJava:基于JVM版本作为判断条件 @ConditionalOnJndi:在JNDI存在的条件下查找指定的位置 @ConditionalOnMissingBean:当容器里没有指定Bean的情况下 @ConditionalOnMissingClass:当容器里没有指定类的情况下 @ConditionalOnWebApplication:当前项目时Web项目的条件下 @ConditionalOnNotWebApplication:当前项目不是Web项目的条件下 @ConditionalOnProperty:指定的属性是否有指定的值 @ConditionalOnResource:类路径是否有指定的值 @ConditionalOnOnSingleCandidate:当指定Bean在容器中只有一个,或者有多个但是指定首选的Bean 这些注解都组合了@Conditional注解,只是使用了不同的条件组合最后为true时才会去实例化需要实例化的类,否则忽略 这种spring4.X带来的动态组合很容易后期配置,从而避免了硬编码,使配置信息更加灵活多变,同时也避免了不必要的意外异常报错。使用的人只要知道配置的条件即可也不用去阅读源码,方便快捷,这也是sprignboot快捷方式带来的好处

参考HttpEncodingAutoConfiguration配置信息如下

    @Configuration
    @EnableConfigurationProperties(HttpEncodingProperties.class)
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    @ConditionalOnClass(CharacterEncodingFilter.class)
    @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
    public class HttpEncodingAutoConfiguration {

@Configuration:标明为配置类 @EnableConfigurationProperties(HttpEncodingProperties.class)声明开启属性注入 @ConditionalOnClass(CharacterEncodingFilter.class)当CharacterEncodingFilter在类路径的条件下 @ConditionalOnProperty(prefix = “spring.http.encoding”, value = “enabled”, matchIfMissing = true)当spring.http.encoding=enabled的情况下,如果没有设置则默认为true,即条件符合 @ConditionalOnMissingBean当容器中没有这个Bean时新建Bean

案例扩展

    /**
     * @author wuweifeng wrote on 2017/11/25.
     * 根据部署环境动态决定是否启用eureka
     线上的环境开启eureka,就在application-prod.yml里配上open.eureka=true,
     其他的yml什么也不写就行了。这样本地启动时就相当于没有开启EnableDiscoveryClient
     */
    @Component
    @ConditionalOnProperty(value = "open.eureka")
    @EnableDiscoveryClient
    public class JudgeEnableDiscoveryClient

自己实现一个自己的自动配置

项目

    xm-common:普通jar项目
    - src/main
        java
            BambooServer.java  需要被实例化的服务类
            BambooServerProperties.java 配置信息属性类
            BmbooServiceAutoConfiguration.java 自动配置类
        resources
            META-INF/spring.factories 配置自动配置的属性文件
    demo:普通springboot-web项目

需要实例化的服务类

    public class BambooServer {
        private String name;
    
        public String sayServerName(){
            return "I'm " + name + "! ";
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }

配置信息对应的属性映射类,需要pom中加入spring-boot-starter依赖

    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    @ConfigurationProperties(prefix = "bamboo")
    public class BambooServerProperties {
    
        private static final String NAME = "bamboo_server0";
    
        private String name = NAME;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }

自动配置文件

    /**
     * Author: bamboo
     * Time: 2018/11/25/025
     * Describe: 自动配置类
     * 根据条件判断是否要自动配置,创建Bean
     */
    @Configuration
    @EnableConfigurationProperties(BambooServerProperties.class)//声明开启属性注入BambooServerProperties
    @ConditionalOnClass(BambooServer.class)//判断BambooServer这个类在类路径中是否存在,只有存在时才符合条件
    @ConditionalOnProperty(prefix = "bamboo",value = "enabled",matchIfMissing = true)
    public class BmbooServiceAutoConfiguration {
    
        @Autowired
        private BambooServerProperties mistraServiceProperties;
    
        @Bean(name = "bambooServer")
        @ConditionalOnMissingBean(BambooServer.class)//当容器中没有这个Bean时(BambooServer)就自动配置这个Bean,Bean的参数来自于BambooServerProperties
        public BambooServer mistraService(){
            BambooServer mistraService = new BambooServer();
            mistraService.setName(mistraServiceProperties.getName());
            return mistraService;
        }
    }

在创建如下路径文件src/main/resources/META-INF/spring.factories

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.bamboo.common.autoconfigure.bamboo.BmbooServiceAutoConfiguration

必须是自动配置类的全路径

mvn install 该项目

创建一个springboot-mvc项目pom依赖上面的jar

    @SpringBootApplication
    @RestController
    //@Import(value = {CorsConfig.class, LogFilter.class}) //跨域,接口访问请求日志
    public class DemoApplication {
    	@Autowired
    	private BambooServer bmbooService;
    
    	public static void main(String[] args) {
    		SpringApplication.run(DemoApplication.class, args);
    	}
    
    	@RequestMapping("/")
    	public Object index(){
    		return "helll demo"+bmbooService.getName()+DateUtils.getDate();
    	}
    }

http://localhost:8080/则返回当前服务的默认值

在applicaton.yml中加,重启刷新则会更新为如下信息

    bamboo:
      name: 测试服务

总结图

202208252256034654.png

SpringBoot自动化配置关键组件关系图 mybatis-spring-boot-starter、spring-boot-starter-web等组件的META-INF文件下均含有spring.factories文件,自动配置模块中,SpringFactoriesLoader收集到文件中的类全名并返回一个类全名的数组,返回的类全名通过反射被实例化,就形成了具体的工厂实例,工厂实例来生成组件具体需要的bean。

参考blog

https://www.cnblogs.com/xiaoxi/p/7999885.html

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/137626.html原文链接:https://javaforall.cn