SpringBoot笔记:源码剖析—自动配置

SpringBoot文章目录

第一章:简述与应用
第二章:源码剖析—源码环境搭建与依赖管理
第三章:源码剖析—自动配置




前言

这个是学习笔记,仅供参考,每天学习一点点


从问题出发:SpringBoot是如何进行自动配置的,都把哪些组件进行了自动配置

自动配置:根据添加的jar包依赖,会自动将一些配置类的bean注册进ioc容器,可以需要的地方使用@autowired或者@resource等注解来使用它。
Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,
@SpringBootApplication : SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的 main() 方法启动 SpringBoot 应用。

一、@SpringBootApplication

查看@SpringBootApplication内部源码进行分析 ,核心代码具体如下:

@Target({ElementType.TYPE}) //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) //表示注解的生命周期,Runtime运行时
@Documented //表示注解可以记录在javadoc中
@Inherited //表示可以被子类继承该注解
@SpringBootConfiguration // 标明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)})
public @interface SpringBootApplication {
    
    // 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。
    @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {};

    // 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全 类名字符串数组。
    @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {};

    // 指定扫描包,参数是包名的字符串数组。
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {};

    // 扫描特定的包,参数类似是Class类型数组。
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {};
}

@SpringBootApplication注解是一个组合注解,前面 4 个是注解的元数据信息。
主要是后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解。


二、@SpringBootConfiguration

@SpringBootConfiguration : SpringBoot 的配置类,标注在某个类上,表示这是一个 SpringBoot
的配置类。
查看@SpringBootConfiguration注解源码,核心代码具体如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented

@Configuration // 配置类的作用等同于配置文件,配置类也是容器中的一个对象 
public @interface SpringBootConfiguration {
}

@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。
由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已。


三、@EnableAutoConfiguration

package org.springframework.boot.autoconfigure;

// 自动配置包
@AutoConfigurationPackage

// Spring的底层注解@Import,给容器中导入一个组件;
// 导入的组件是AutoConfigurationPackages.Registrar.class
@Import(AutoConfigurationImportSelector.class)

// 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    // 返回不会被导入到 Spring 容器中的类
    Class<?>[] exclude() default {};

    // 返回不会被导入到 Spring 容器中的类名
    String[] excludeName() default {};
}

Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import 来收集并注册特定场景相关的Bean ,并加载到 IOC 容器。
@EnableAutoConfiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。

1. @AutoConfigurationPackage

package org.springframework.boot.autoconfigure; 

@Import(AutoConfigurationPackages.Registrar.class) // 导入Registrar中注册的组件
public @interface AutoConfigurationPackage {
 }

@AutoConfigurationPackage :自动配置包,它也是一个组合注解,其中最重要的注解是@Import(AutoConfigurationPackages.Registrar.class) ,它是 Spring 框架的底层注解,它的作用就是给容器中导入某个组件类。
例如@Import(AutoConfigurationPackages.Registrar.class) ,它就是将Registrar 这个组件类导入到容器中,可查看 Registrar 类中 registerBeanDefinitions 方法:

@Override 
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { 
// 将注解标注的元信息传入,获取到相应的包名 
register(registry, new PackageImport(metadata).getPackageName()); 
}

对 new PackageImport(metadata).getPackageName() 进行检索,查看其结果:
在这里插入图片描述
再看register方法

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        // 这里参数 packageNames 缺省情况下就是一个字符串,是使用了注解
        // @SpringBootApplication 的 Spring Boot 应用程序入口类所在的包
        if (registry.containsBeanDefinition(BEAN)) {
            // 如果该bean已经注册,则将要注册包名称添加进去
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        } else {
            //如果该bean尚未注册,则注册该bean,参数中提供的包名称会被设置到bean定义中去 
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }
    }

AutoConfigurationPackages.Registrar这个类就干一个事,注册一个 Bean。
这个 Bean 就是org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages ,它有一个参数,这个参数是使用了 @AutoConfigurationPackage 这个注解的类所在的包路径,保存自动配置类以供之后的使用,比如给 JPA entity 扫描器用来扫描开发人员通过注解 @Entity 定义的 entity类。

2. @Import(AutoConfigurationImportSelector.class)

@Import({AutoConfigurationImportSelector.class}) :将AutoConfigurationImportSelector 这个类导入到 Spring 容器中,AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的@Configuration配置都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。
在这里插入图片描述
可以看到 AutoConfigurationImportSelector 重点是实现了 DeferredImportSelector 接口和各种Aware 接口,然后 DeferredImportSelector 接口又继承了 ImportSelector 接口。
其不光实现了 ImportSelector 接口,还实现了很多其它的 Aware 接口,分别表示在某个时机会被回调。

2.1 确定自动配置实现逻辑的入口方法

跟自动配置逻辑相关的入口方法在 DeferredImportSelectorGrouping 类的 getImports 方法处,因此就从 DeferredImportSelectorGrouping 类的 getImports 方法来开始分析SpringBoot的自动配置源码。
getImports 方法代码:

// ConfigurationClassParser.java
    public Iterable<Group.Entry> getImports() {
        
        // 遍历DeferredImportSelectorHolder对象集合deferredImports,
        // deferredImports集合装了各种ImportSelector,当然这里装的是AutoConfigurationImportSelector
        for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
            // 【1】利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定导入哪些配置类(这个是分析的重点,自动配置的逻辑全在这了)
            this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());
        }

        // 【2】经过上面的处理后,然后再进行选择导入哪些配置类
        return this.group.selectImports();
    }

标【1】处的的代码是分析的重中之重,自动配置的相关的绝大部分逻辑全在这里了。那么this.group.process(deferredImport.getConfigurationClass().getMetadata(),deferredImport.getImportSelector()) ;主要做的事情就是在 this.group 即 AutoConfigurationGroup 对象的 process 方法中,传入的AutoConfigurationImportSelector对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类,就是这么个事情。

注:
AutoConfigurationGroup:是AutoConfigurationImportSelector的内部类,主要用来处理自动配置相关的逻辑,
拥有process和selectImports方法,然后拥有entries和autoConfigurationEntries集合属性,这两个集合分别存储被处理后的符合条件的自动配置类,知道这些就足够了; 

AutoConfigurationImportSelector: 承担自动配置的绝大部分逻辑,负责选择一些符合条件的自动配置类; 

metadata:标注在SpringBoot启动类上的@SpringBootApplication注解元数据 

标【2】的this.group.selectImports:主要是针对前面的process方法处理后的自动配置类再进一步有选择的选择导入

再进入到AutoConfigurationImportSelector$AutoConfigurationGroup的pross方法:在这里插入图片描述
通过图中可以看到,跟自动配置逻辑相关的入口方法在process方法中

2.2 分析自动配置的主要逻辑

// AutoConfigurationImportSelector$AutoConfigurationGroup.java

// 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName()));
    
    // 【1】,调用getAutoConfigurationEntry方法得到自动配置类放入 autoConfigurationEntry对象中
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
    
    // 【2】,又将封装了自动配置类的autoConfigurationEntry对象装进 autoConfigurationEntries集合 
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    
    // 【3】,遍历刚获取的自动配置类 
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        // 这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合 
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}

再来看上面代码中标 【1】 的方法 getAutoConfigurationEntry ,这个方法主要是用来获取自动配置类有关,承担了自动配置的主要逻辑。代码如下:

// AutoConfigurationImportSelector.java

    // 获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        // 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        // 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据,
        // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
        // 将会获取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的注解数据
        AnnotationAttributes attributes = getAttributes(annotationMetadata);

        // 【1】得到spring.factories文件配置的所有自动配置类
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

        // 利用LinkedHashSet移除重复的配置类
        configurations = removeDuplicates(configurations);

        // 得到要排除的自动配置类,比如注解属性exclude的配置类
        // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
        // 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);

        // 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
        checkExcludedClasses(configurations, exclusions);

        // 【2】将要排除的配置类移除
        configurations.removeAll(exclusions);

        // 【3】因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载 进内存,会造成内存浪费,因此这里需要进行过滤
        // 注意这里会调用AutoConfigurationImportFilter的match方法来判断是否符合这些条件注解@ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication
        configurations = filter(configurations, autoConfigurationMetadata);

        // 【4】获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
        // 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
        // 该事件会在刷新容器时调用invokeBeanFactoryPostProcessors后置处理器时触发
        fireAutoConfigurationImportEvents(configurations, exclusions);

        // 【5】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
        return new AutoConfigurationEntry(configurations, exclusions);
    }

2.3 深入 getCandidateConfigurations 方法

这个方法中有一个重要方法 loadFactoryNames ,这个方法是让 SpringFactoryLoader 去加载一些组件的名字。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 这个方法需要传入两个参数getSpringFactoriesLoaderFactoryClass()和 getBeanClassLoader() 
    // getSpringFactoriesLoaderFactoryClass()这个方法返回的是 EnableAutoConfiguration.class 
    // getBeanClassLoader()这个方法返回的是beanClassLoader(类加载器) 
    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;
}

继续点开 loadFactory 方法,在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件,spring.factories里面保存着springboot的默认提供的自动配置类。

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 {
            //如果类加载器不为null,则加载类路径下spring.factories文件,将其中设置的 配置类的全路径信息封装 为Enumeration类对象
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();
            //循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入 的键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中 
            while (urls.hasMoreElements()) {
                URL url = (URL) urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();
                while (var6.hasNext()) {
                    Entry<?, ?> entry = (Entry) var6.next();
                    String factoryClassName = ((String) entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                    int var10 = var9.length;
                    for (int var11 = 0; var11 < var10; ++var11) {
                        String factoryName = var9[var11];
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
    }
}

META-INF/spring.factories:
在这里插入图片描述

2.4 总结下 AutoConfigurationEntry 方法主要做的事情

AutoConfigurationEntry 方法主要做的事情就是获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费.

  1. 从 spring.factories 配置文件中加载 EnableAutoConfiguration 自动配置类),获取的自动配置类如图所示。
  2. 若 @EnableAutoConfiguration 等注解标有要 exclude 的自动配置类,那么再将这个自动配置类排除掉;
  3. 排除掉要 exclude 的自动配置类后,然后再调用 filter 方法进行进一步的过滤,再次排除一些不符合条件的自动配置类;
  4. 经过重重过滤后,此时再触发 AutoConfigurationImportEvent 事件,告诉ConditionEvaluationReport 条件评估报告器对象来记录符合条件的自动配置类;
  5. 最后再将符合条件的自动配置类返回。

2.5 再来细看一下AutoConfigurationImportSelector 的 filter 方法

// AutoConfigurationImportSelector.java

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    // 将从spring.factories中获取的自动配置类转出字符串数组
    String[] candidates = StringUtils.toStringArray(configurations);
    // 定义skip数组,是否需要跳过。注意skip数组与candidates数组顺序一一对应
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    // getAutoConfigurationImportFilters方法:拿到OnBeanCondition, OnClassCondition和OnWebApplicationCondition
    // 然后遍历这三个条件类去过滤从spring.factories加载的大量配置类
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        // 调用各种aware方法,将beanClassLoader,beanFactory等注入到filter对象中,
        // 这里的filter对象即OnBeanCondition,OnClassCondition或 OnWebApplicationCondition
        invokeAwareMethods(filter);
        // 判断各种filter来判断每个candidate(这里实质要通过candidate(自动配置类)拿到其标 注的
        // @ConditionalOnClass,@ConditionalOnBean和@ConditionalOnWebApplication里 面的注解值)是否匹配,
        // 注意candidates数组与match数组一一对应
        // /**********************【主线,重点关注】********************************/
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        // 遍历match数组,注意match顺序跟candidates的自动配置类一一对应
        for (int i = 0; i < match.length; i++) {
            // 若有不匹配的话
            if (!match[i]) {
                // 不匹配的将记录在skip数组,标志skip[i]为true,也与candidates数组一一 对应
                skip[i] = true;
                // 因为不匹配,将相应的自动配置类置空
                candidates[i] = null;
                // 标注skipped为true
                skipped = true;
            }
        }
    }
    // 这里表示若所有自动配置类经过OnBeanCondition,OnClassCondition和 OnWebApplicationCondition过滤后,全部都匹配的话,则全部原样返回
    if (!skipped) {
        return configurations;
    }
    // 建立result集合来装匹配的自动配置类
    List<String> result = new ArrayList<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        // 若skip[i]为false,则说明是符合条件的自动配置类,此时添加到result集合中
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }
    // 打印日志
    if (logger.isTraceEnabled()) {
        int numberFiltered = configurations.size() - result.size();
        logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
    }
    // 最后返回符合条件的自动配置类 
    return new ArrayList<>(result);
}

AutoConfigurationImportSelector 的 filter 方法主要做的事情就是调用AutoConfigurationImportFilter 接口的 match 方法来判断每一个自动配置类上的条件注解(若有的话) @ConditionalOnClass , @ConditionalOnBean 或 @ConditionalOnWebApplication 是否满足条件,若满足,则返回true,说明匹配,若不满足,则返回false说明不匹配。

3. 关于条件注解的讲解

@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。

  • @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
  • @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
  • @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式的条件判断。
  • @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
  • @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
  • @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
  • @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
  • @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
  • @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
  • @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
  • @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
  • @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
  • @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化

有选择的导入自动配置类
this.group.selectImports 方法是如何进一步有选择的导入自动配置类的。代码:

// AutoConfigurationImportSelector$AutoConfigurationGroup.java

public Iterable<Entry> selectImports() {
    if (this.autoConfigurationEntries.isEmpty()) {
        return Collections.emptyList();
    }
    // 这里得到所有要排除的自动配置类的set集合
    Set<String> allExclusions = this.autoConfigurationEntries.stream().map(AutoConfigurationEntry::getExclusions)
            .flatMap(Collection::stream).collect(Collectors.toSet());
    // 这里得到经过滤后所有符合条件的自动配置类的set集合 
    Set<String> processedConfigurations = this.autoConfigurationEntries.stream().map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));
    // 移除掉要排除的自动配置类 
    processedConfigurations.removeAll(allExclusions);
    // 对标注有@Order注解的自动配置类进行排序, 
    return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream().map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)).collect(Collectors.toList());
}

selectImports 方法主要是针对经过排除掉 exclude 的和被AutoConfigurationImportFilter 接口过滤后的满足条件的自动配置类再进一步排除 exclude 的自动配置类,然后再排序。

4. 以 HttpEncodingAutoConfiguration ( Http 编码自动配置)为例解释自动配置原理

// 表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件
@Configuration

// 启动指定类的ConfigurationProperties功能;将配置文件中对应的值和 HttpEncodingProperties绑定起来;
@EnableConfigurationProperties({HttpEncodingProperties.class})

// Spring底层@Conditional注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会 生效。
// 判断当前应用是否是web应用,如果是,当前配置类生效。并把HttpEncodingProperties加入到 ioc 容器中
@ConditionalOnWebApplication

// 判断当前项目有没有这个CharacterEncodingFilter : SpringMVC中进行乱码解决的过滤器
@ConditionalOnClass({CharacterEncodingFilter.class})

// 判断配置文件中是否存在某个配置 spring.http.encoding.enabled 如果不存在,判断也是成立的
// matchIfMissing = true 表示即使我们配置文件中不配置 spring.http.encoding.enabled=true,也是默认生效的
@ConditionalOnProperty(prefix = "spring.http.encoding", value = {"enabled"}, matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

    // 它已经和SpringBoot配置文件中的值进行映射了
    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;
    }
}

根据当前不同的条件判断,决定这个配置类是否生效。

一旦这个配置类生效,这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties 类中获取的,这些类里面的每一个属性又是和配置文件绑定的。

# 能配置的属性都是来源于这个功能的properties类 
spring.http.encoding.enabled=true 
spring.http.encoding.charset=utf-8 
spring.http.encoding.force=true

所有在配置文件中能配置的属性都是在 xxxProperties 类中封装着,配置文件能配置什么就可以参照某个功能对应的这个属性类。

// 从配置文件中获取指定的值和bean的属性进行绑定 
@ConfigurationProperties(prefix = "spring.http.encoding") 
public class HttpEncodingProperties { 
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

5. 总结精髓

  1. SpringBoot 启动会加载大量的自动配置类
  2. 看需要实现的功能有没有 SpringBoot 默认写好的自动配置类
  3. 再来看这个自动配置类中到底配置了哪些组件;(只要有要用的组件,就不需要再来配置了)
  4. 给容器中自动配置类添加组件的时候,会从 properties 类中获取某些属性,就可以在配置文件中指定这些属性的值。
  5. xxxAutoConfiguration:自动配置类,用于给容器中添加组件从而代替之前我们手动完成大量繁琐的配置。
  6. xxxProperties: 封装了对应自动配置类的默认属性值,如果需要自定义属性值,只需要根据xxxProperties 寻找相关属性在配置文件设值即可.

SpringBoot自动配置的原理,主要做了以下事情:
5. 从spring.factories配置文件中加载自动配置类;
6. 加载的自动配置类中排除掉 @EnableAutoConfiguration 注解的 exclude 属性指定的自动配置类;
7. 然后再用 AutoConfigurationImportFilter 接口去过滤自动配置类是否符合其标注注解(若有标注的话) @ConditionalOnClass , @ConditionalOnBean 和 @ConditionalOnWebApplication 的条件,若都符合的话则返回匹配结果;
8. 然后触发 AutoConfigurationImportEvent 事件,告诉 ConditionEvaluationReport 条件评估报告器对象来分别记录符合条件和 exclude 的自动配置类。
9. 最后spring再将最后筛选后的自动配置类导入IOC容器中
在这里插入图片描述


四、@ComponentScan

主要是从定义的扫描路径中,找出标识了需要装配的类自动装配到spring 的bean容器中。
常用属性如下:

  • basePackages、value:指定扫描路径,如果为空则以@ComponentScan注解的类所在的包为基本的扫描路径
  • basePackageClasses:指定具体扫描的类
  • includeFilters:指定满足Filter条件的类
  • excludeFilters:指定排除Filter条件的类

includeFilters和excludeFilters 的FilterType可选:ANNOTATION=注解类型 默认、ASSIGNABLE_TYPE(指定固定类)、ASPECTJ(ASPECTJ类型)、REGEX(正则表达式)、CUSTOM(自定义类型),自定义的Filter需要实现TypeFilter接口

@ComponentScan的配置如下:

@ComponentScan(excludeFilters = { 
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), 
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) 
})

借助excludeFilters将TypeExcludeFillter及FilterType这两个类进行排除

当前@ComponentScan注解没有标注basePackages及value,所以扫描路径默认为@ComponentScan注解的类所在的包为基本的扫描路径(也就是标注了@SpringBootApplication注解的项目启动类所在的路径)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值