SpringBoot自动配置原理

springboot的自动配置流程:

主程序启动会扫描@SpringBootApplication
@SpringBootApplication
public class HelloWorldMainApplication {

    public static void main(String[] args) {

        SpringApplication.run(HelloWorldMainApplication.class,args);
    }
}
@SpringBootApplication元信息

起到主要作用的是@SpringBootConfiguration和@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 {

@SpringBootConfiguration:
其实就是@Configuration,标注当前类为主配置类(主容器),当前类指的HelloWorldMainApplication

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

@EnableAutoConfiguration:
包含两个重要注解,@AutoConfigurationPackage和@Import({EnableAutoConfigurationImportSelector.class})

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

@AutoConfigurationPackage的作用:相当于包扫描器,扫描主配置类所在包下的所有自定义的bean,例如@Controller、@Service等注解标识的bean。
它的内部是通过@Import({Registrar.class}),利用ImportBeanDefinitionRegistrar的registerBeanDefinitions方法,向BeanDefinitionRegistry注册主配置类所在包下自定义的逻辑组件

@Import({EnableAutoConfigurationImportSelector.class})的作用:通过ImportSelector选择器执行selectImports方法获得最终启用(符合当前场景)的自动配置类的全类名的String数组,主配置类就会把这些自动配置类的bean加载进spring容器,@Configuration其实也是spring容器的组件,内部@Component修饰

selectImports方法内部:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
       if (!this.isEnabled(annotationMetadata)) {
           return NO_IMPORTS;
       } else {
           try {
               AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
               AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
               List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
               configurations = this.removeDuplicates(configurations);
               configurations = this.sort(configurations, autoConfigurationMetadata);
               Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
               this.checkExcludedClasses(configurations, exclusions);
               configurations.removeAll(exclusions);
               configurations = this.filter(configurations, autoConfigurationMetadata);
               this.fireAutoConfigurationImportEvents(configurations, exclusions);
               return (String[])configurations.toArray(new String[configurations.size()]);
           } catch (IOException var6) {
               throw new IllegalStateException(var6);
           }
       }
   } 

自动配置的核心就在于selectImports方法,下面一步步的剖析这个方法

AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
    public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
        return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
    }
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration.ConditionalOnClass=org.apache.solr.client.solrj.SolrClient,org.springframework.data.solr.repository.SolrRepository
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration.AutoConfigureBefore=org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.jms.artemis.ArtemisXAConnectionFactoryConfiguration=
    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
        try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
            Properties properties = new Properties();

            while(urls.hasMoreElements()) {
                properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource((URL)urls.nextElement())));
            }

            return loadMetadata(properties);
        } catch (IOException var4) {
            throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", var4);
        }
    }

loadMetadata方法内部遍历应用下的所有jar,加载了spring-boot-autoconfigure-1.5.9.RELEASE.jar下的META-INF/spring-autoconfigure-metadata.properties,配置文件里面的信息为自动配置类xxxAutoConfiguration的@ConditionalOnClass、@AutoConfigureAfter、@AutoConfigureOrder等注解参数的key-value信息,因为自动配置类是否启用要看符不符合这些条件,如果启用,还要对这些自动配置类的加载顺序进行排序,返回的autoConfigurationMetadata 封装了这些信息的Properties,Properties把spring-autoconfigure-metadata.properties封装了

AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

参数annotationMetadata为主启动类的信息(以及它的注解及元注解信息),返回的attributes为@EnableAutoConfiguration注解的exclude和excludeName的值

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, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();

        try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            ArrayList result = new ArrayList();

            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }

            return result;
        } catch (IOException var8) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
        }
    }

只截取一部分,org.springframework.boot.autoconfigure.EnableAutoConfiguration为key

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\

getCandidateConfigurations方法返回所有自动配置类的全类名集合(从META-INF/spring.factories原封不动地拿),参数getSpringFactoriesLoaderFactoryClass()其实就EnableAutoConfiguration.class类对象,loadFactoryNames方法内部会读取该应用所有jar包下的META-INF/spring.factories,(其实只有spring-boot-autoconfigure-1.5.9.RELEASE.jar包下有)取出factories文件中key为EnableAutoConfiguration全类名的值的集合,装进集合configurations 返回

configurations = this.removeDuplicates(configurations);

返回原集合,不必理会

configurations = this.sort(configurations, autoConfigurationMetadata);

对集合排序,根据autoConfigurationMetadata里面的AutoConfigureOrder、AutoConfigureAfter、AutoConfigureBefore等信息对自动配置类集合configurations排序,自动配置类加载是有顺序的,
例如WebMvcAutoConfiguration,就必须等到DispatcherServletAutoConfiguration加载成功之后再加载,而且还要根据@Order的派生注解@AutoConfigureOrder决定加载优先级

@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);

处理需要排除的自动配置类,不必理会

configurations = this.filter(configurations, autoConfigurationMetadata);

根据@ConditionalOnClass(类.Class)、@ConditionalOnMissingBean(类.Class)等注解,判断注解中的类.Class是否存在或不存在,从而筛选出符合启用的自动配置类。
例如我们的应用没有导入redis场景的starter,而RedisAutoConfiguration里

@Configuration
@ConditionalOnClass({JedisConnection.class, RedisOperations.class, Jedis.class})
@EnableConfigurationProperties({RedisProperties.class})
public class RedisAutoConfiguration {

如果我们应用里没有这三个JedisConnection.class, RedisOperations.class, Jedis.class,那么RedisAutoConfiguration就不会生效,filter方法会把它过滤掉,最终返回的configurations就是经过过滤后的、符合我们当前应用场景的自动配置类的String数组。

这些自动配置类在被@Import进主容器之后,spring创建这些自动配置类的bean,这些bean各自完成初始化自己内部组件的工作
至此,springboot自动配置类的默认初始化完成

springboot修改默认配置:

springboot为我们自动配置了各个场景下的所有默认配置,那它是如何设置的默认配置呢,再者如果我们需要修改默认配置,应该从哪入手?

首先要明确一点,每个自动配置类的主要功能就是给容器中加入所需的组件

以HttpEncodingAutoConfiguration为例

例如HttpEncodingAutoConfiguration类给容器中加入一个解决编码的CharacterEncodingFilter组件
HttpEncodingAutoConfiguration类信息如下:

@Configuration //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties(HttpEncodingProperties.class) //启动指定类的 ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把 HttpEncodingProperties加入到ioc容器中,以便自动配置类以有参构造器的方式注入Properties
@ConditionalOnWebApplication //Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果 满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnClass(CharacterEncodingFilter.class) //判断当前项目有没有这个类,自动配置类是否启动要看这个注解; CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的 //即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
public class HttpEncodingAutoConfiguration {

private final HttpEncodingProperties properties;

public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
    this.properties = properties;
    }
@Bean //给容器中添加一个组件,这个组件的某些属性值需要从properties中获取
@ConditionalOnMissingBean(CharacterEncodingFilter.class) //判断容器有没有这个组件? 没有才会生效
public CharacterEncodingFilter characterEncodingFilter() {
    CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
         filter.setEncoding(this.properties.getCharset().name());
         filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
         filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
         return filter;
    }
}

我们只需要指定HttpEncodingProperties类中的charset属性的值即可,而HttpEncodingProperties通过@ConfigurationProperties(prefix = "")又和配置文件绑定了,
这里说的配置文件是我们应用的配置文件,因为spring-boot-autoconfigure-1.5.9.RELEASE.jar没有配置文件,会扫描我们应用的配置文件,
只要对应上prefix前缀和HttpEncodingProperties类的属性名就行了

HttpEncodingProperties类如下:
可以看出charset的默认值是UTF-8

@ConfigurationProperties(prefix = "spring.http.encoding") //从配置文件(我们自己应用的配置文件)中获取指定的值和bean的属性进行绑定
public class HttpEncodingProperties {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    private Charset charset;
    private Boolean force;
    private Boolean forceRequest;
    private Boolean forceResponse;
    private Map<Locale, Charset> mapping;
    public HttpEncodingProperties() {
            this.charset = DEFAULT_CHARSET;
        }
    set/get方法......
}

我们自己在application.properties中自定义配置

spring.http.encoding.enabled=true
spring.http.encoding.charset=gbk
spring.http.encoding.force=true
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容