SpringBoot自动配置原理

SpringBoot自动配置原理

一、前言

二、原理

我们需要从Spring Boot项目的启动类开始跟踪,在启动类上我们一般会加入SpringBootApplication注解,

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

① @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 :作用就相当于**@Configuration**注解,被注解的类将成为一个bean配置类
@EnableAutoConfiguration :这个注解很重要,借助@Import的支持,收集和注册依赖包中相关的bean定义
@ComponentScan :作用就是自动扫描并加载符合条件的组件,最终将这些bean加载到spring容器中

②继续跟踪**@EnableAutoConfiguration**注解源码:

@SuppressWarnings("deprecation") 
@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME)
@Documented 
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)  //EnableAutoConfigurationImportSelector批量导入bean
public @interface EnableAutoConfiguration { // ... } 
    

@EnableAutoConfiguration注解引入了@Import这个注解。

Import:导入需要自动配置的组件,此处为EnableAutoConfigurationImportSelector这个类(这块需要了解@Import的相关知识)

​ 重写了selectImports方法,将返回的String[]导入到spring容器中

EnableAutoConfigurationImportSelector类源码如下:

/**
 * 最主要的方法
 * annotationMetadata
 * [@org.springframework.boot.autoconfigure.SpringBootApplication
 * (scanBasePackageClasses=[], excludeName=[], exclude=[], scanBasePackages=[])]
 * @param annotationMetadata
 * @return
 */
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    /**
    * 加载META-INF/spring-autoconfigure-metadata.properties,获取所有支持自动配置的信息
    */
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        .loadMetadata(this.beanClassLoader);
    /**
     * 得到注解中的所有属性信息{excludeName=[], exclude=[]}
     */
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    /**
     * 【重点】【重点】【重点】
     * 获取所有支持EnableAutoConfiguration的组件信息,这部分信息配置在spring-boot-autoconfig包下的spring.factories下
     *
     *  使用了内部工具使用SpringFactoriesLoader,查找classpath上所有jar包中的
     *  META-INF\spring.factories,找出其中key为
     *  org.springframework.boot.autoconfigure.EnableAutoConfiguration
     *  的属性定义的工厂类名称。
     */
    List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);
    configurations = removeDuplicates(configurations);
    /**
     * 去除不需要的
     * @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, RedisAutoConfiguration.class,
                     DataSourceTransactionManagerAutoConfiguration.class, })
      */
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    /**
     * 然后使用AutoConfigurationImportFilter进行过滤,过滤的方式基本上是判断现有系统是否引入了某个组件,(系统是否使用哪个组件是在pom定义的时候就确定了的)
               * ,如果有的话则进行相关配置。比如ServletWebServerFactoryAutoConfiguration
               * ,会在ServletRequest.class等条件存在的情况下进行配置,
               * 而EmbeddedTomcat会在Servlet.class, Tomcat.class存在的情况下创建TomcatServletWebServerFactory
               *
               * org.springframework.boot.autoconfigure.condition.OnClassCondition
               * 总而言之,此过滤器会检查候选配置类的注解@ConditionalOnClass,如果要求的类在classpath 中不存在,则这个候选配置类会被排除掉
               */
    configurations = filter(configurations, autoConfigurationMetadata);
    /**
                * 现在已经找到所有需要被应用的候选配置类
                * 广播事件AutoConfigurationImportEvent
                */
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return StringUtils.toStringArray(configurations);
}


private void fireAutoConfigurationImportEvents(List<String> configurations,
                                               Set<String> exclusions) {
    List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
    if (!listeners.isEmpty()) {
        AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
                                                                              configurations, exclusions);
        for (AutoConfigurationImportListener listener : listeners) {
            invokeAwareMethods(listener);
            listener.onAutoConfigurationImportEvent(event);
        }
    }
 }

继续看**getCandidateConfigurations(annotationMetadata,attributes)**方法

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

Spring工厂加载器加载工厂名称

扫描具有META-INF/spring.factories文件的jar包

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

spring.factories文件是Key=Value形式,多个Value时使用,隔开,该文件中定义了关于初始化,监听器等信息,而真正使自动配置生效的key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,如下所示:

 # Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
//省略

上面的EnableAutoConfiguration配置了多个类,这些都是Spring Boot中的自动配置相关类;在启动过程中会解析对应类配置信息。每个Configuation类都定义了相关bean的实例化配置。都说明了哪些bean可以被自动配置,什么条件下可以自动配置,并把这些bean实例化出来,下面的初始化数据源的部分源码:

@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration {

    private static final Log logger = LogFactory
            .getLog(DataSourceAutoConfiguration.class);

    @Bean
    @ConditionalOnMissingBean
    public DataSourceInitializer dataSourceInitializer(DataSourceProperties properties,
            ApplicationContext applicationContext) {
        return new DataSourceInitializer(properties, applicationContext);
    }
    //略
}

会用到很多常用的注解

三、相关注解

@Configuration:这个配置就不用多做解释了,我们一直在使用
@EnableConfigurationProperties:这是一个开启使用配置参数的注解,value值就是我们配置实体参数映射的ClassType,将配置实体作为配置来源。
以下为SpringBoot内置条件注解:
@ConditionalOnBean:当SpringIoc容器内存在指定Bean的条件
@ConditionalOnClass:当SpringIoc容器内存在指定Class的条件
@ConditionalOnExpression:基于SpEL表达式作为判断条件
@ConditionalOnJava:基于JVM版本作为判断条件
@ConditionalOnJndi:在JNDI存在时查找指定的位置
@ConditionalOnMissingBean:当SpringIoc容器内不存在指定Bean的条件
@ConditionalOnMissingClass:当SpringIoc容器内不存在指定Class的条件
@ConditionalOnNotWebApplication:当前项目不是Web项目的条件
@ConditionalOnProperty:指定的属性是否有指定的值
@ConditionalOnResource:类路径是否有指定的值
@ConditionalOnSingleCandidate:当指定BeanSpringIoc容器内只有一个,或者虽然有多个但是指定首选的Bean
@ConditionalOnWebApplication:当前项目是Web项目的条件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值