【Spring】Spring Boot 自动配置原理分析
目录
一、前言
二、自动配置3个问题的解决方案
2.1 问题一:满足了什么条件,才去自动配置呢?
2.2 问题二:应该创建哪些bean呢?
2.3 问题三:bean的属性值来源在哪里?
三、自动配置类
四、条件注解
五、配置属性
六、内置starter
6.1 Starter机制
七、自动配置相关的重要注解以及自动配置源码分析
7.1 SpringBootApplication
7.2 Inherited
7.3 SpringBootConfiguration
7.4 ComponentScan
7.5 EnableAutoConfiguration
7.5.1 @AutoConfigurationPackage
7.5.2 @Import(AutoConfigurationImportSelector.class)
7.6 AutoConfigurationImportSelector
7.6.1:getAutoConfigurationMetadata
7.6.2:getAutoConfigurationEntry
一、前言
在Java中各种框架,中间件,非常非常多,在我们的项目中不可能默认配置所有的,那么对于自动配置而言的第一个问题就是满足了什么条件,才去自动配置呢?,先不管这个条件是怎么样的,假设这个条件已经得到了满足,接下来就有第二个问题应该创建哪些bean呢?,继续,假设创建哪些bean也确定了,那么,必定某些bean有一些属性值是需要动态设置的,因此,第三个问题就是bean的属性值来源在哪里?。总结如下:
- 满足了什么条件,才去自动配置呢?
- 应该创建哪些bean呢?
- bean的属性值来源在哪里?
我们就从这三个问题作为入口来进行分析。
二、自动配置3个问题的解决方案
我们直接来看一个Spring Boot中的自动配置类,然后再就着这个自动配置类来分析这三个问题的解决方案,如下是Spring Boot关于web服务器的自动配置类源码:
@Configuration
@ConditionalOnWebApplication // <2.1>
@EnableConfigurationProperties(ServerProperties.class) // <4.1>
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {// 下面都是内部类,这些内部类都是一个JavaConfig类,里面就配置了相关的Bean@Configuration@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class }) // <2.2>public static class TomcatWebServerFactoryCustomizerConfiguration {@Bean // <3.1>public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {return new TomcatWebServerFactoryCustomizer(environment, serverProperties);}}@Configuration@ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class }) // <2.3>public static class JettyWebServerFactoryCustomizerConfiguration {@Bean // <3.2>public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) { return new JettyWebServerFactoryCustomizer(environment, serverProperties);}}
}
这是一个Java Config类,Java config是一种允许我们通过写Java代码创建java bean的机制,@Configuration注解说明类是一个Java Config的类,@Bean来定义一个Java bean,@Bean注解功能同xml配置方式的<bean>标签。
2.1 问题一:满足了什么条件,才去自动配置呢?
在上述源码中,我们看<2.1>、<2.2>、<2.3>处的形如@ConditionalOnxxx的注解,这种注解我们叫做条件注解,就是用来设置需要满足条件的,这种注解在Spring Boot中非常非常的多,我们在自定义Spring Boot的starter的时候也可能需要自定义这种注解,如下图是Spring Boot中提供的条件注解:
所以这个问题的结论就是条件注解。
2.2 问题二:应该创建哪些bean呢?
在上述源码中<3.1>、<3.2>处标记有@Bean注解的方法,其返回值就是需要创建的bean。
2.3 问题三:bean的属性值来源在哪里?
属性值的来源其实就是在application.yml或application.properties的配置文件中,我们会将Bean的属性值写在这个配置文件里,然后Spring Boot启动的时候就会读取配置文件中的key-value赋给对应Bean的属性。
接下来看底层究竟是如何接收这些配置的,在上述源码中<4.1>处@EnableConfigurationProperties中设置了ServerProperties.class,那么就会自动创建类型为ServerProperties的bean,ServerProperties类的定义如下:
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {private Integer port;private InetAddress address;...snip...
}
其中@ConfigurationProperties注解中的prefix设置要读取的配置项的前缀,然后和该类的属性名称组合就是配置文件的key了(就是application.yml或application.properties的配置文件中那些配置项的Key),最终就读取到了配置文件中对应配置项的值,并将其设置到对应的Bean属性中。如配置文件中设置server.port=9999,则ServerProperties这个bean的port的属性值是9999。
到这里,我们知道了在Spring Boot中自动配置类对这3个问题提供的解决方案,我们就可以给自动配置类来一个定义:解决我们提出的三个问题的类,可以称之为自动配置类,当然这是我自己定义的,非官方,仅用于理解使用,通过自动配置类来实现在满足一定条件时,通过读取配置文件信息,来创建bean。
但是Spring Boot怎么知道需要加载哪些自动配置类呢?我们继续来看。
三、自动配置类
在Spring中提供了一个工具类org.springframework.core.io.support.SpringFactoriesLoader,用于从META-INF/spring.factories文件中根据类名称来查询配置的具体类。Spring Boot通过这个机制来读取自动配置类,在spring-boot-autoconfigure项目中提供了大量的自动配置类,就是如下图配置的。当然我们自己编写starter定义的自动配置类,也需要按照这种方式来定义,如下图红框中就是具体的配置:
在spring.factories文件中就写了要加载的自动配置类的全限定名。
如下图是kafka和mongo的自动配置类:
|
四、条件注解
通过前面的分析,我们知道,条件注解是用来判断是否需要加载为bean。接下来我们看下条件注解的由来。为了解决在不同的环境中注册不同的bean的需求,spring3.1版本,提供了@Profile注解,如下在不同环境注册不同bean数据源:
@Configuration
public class MyDatasourceConfiguration {// 开发环境的数据源bean@Bean@Profile("dev")public DataSource getDevDatasoucce() {DataSource devDatasource = ...;// 具体逻辑return devDatasource;}// 正式环境使用的数据源bean@Bean@Profile("production")public DataSouce getProductionDatasouce() {DataSource productionDatasouce = ...;// 具体逻辑return productionDatasource;}
}
但是只能提供基于变量spring.profiles.active判断来创建bean,因此,在spring4版本中,使用了更加通用的@Conditional注解,源码如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {Class<? extends Condition>[] value();
}
从源码中可以看到其value方法需要实现了org.springframework.context.annotation.Condition接口的Class类数组作为参数,该接口的作用是用来进行条件满足是和否的判断的,需要我们自己根据情况来提供具体实现,Condition接口的源码如下:
@FunctionalInterface
public interface Condition {boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
这样创建bean的各种条件就全部支持了,Spring Boot基于此,提供了很多常用的条件注解,以及配套的条件判断类,主要如下:
- @ConditionalOnBean:当IOC容器中有某个bean的时候,满足条件
- @ConditionOnMissingBean:当IOC容器中没有某个bean的时候,满足条件
- @ConditionalOnSingleCandidate:当IOC容器中只有一个候选bean,或者是有多个候选bean但是指定了首选bean,满足条件
- @ConditionalOnClass:当类路径下有某个类时,满足条件
- @ConditionalOnMissingClass:当类路径下缺失某个类时,满足条件
- @ConditionalOnProperty:当指定的属性有指定的值时,满足条件
- @ConditionalOnExpression:当SpEL表达式为true时,满足条件
- @ConditionalOnJava:当前项目java版本满足指定的java版本时,满足条件
- @ConditionalOnWebApplication:当前项目是web项目时,满足条件
- @ConditionalOnNotWebApplication:当前项目不是web项目时,满足条件
上述@ConditionalOnSingleCandidate中涉及到了首选bean(存在多个满足要求的bean时,通过primary=true设置最高优先级,即首选bean),关于这个不清楚的可以参考这里。
@ConditionalOnExpression涉及到了SpEL,关于SpEL不清楚的可以参考这里。
五、配置属性
Spring Boot默认从application.yaml,application.properties文件中读取属性,用来填充bean的属性,也可以配合@ConditionalOnProperty条件注解来使用。
六、内置starter
什么是starter,封装某个框架所需要依赖的GAV我们就可以称之为是starter,又叫做是起步依赖,通过起步依赖就可以引入框架需要的jar,此时再配合自动配置类中的@ConditionalOnClass条件注解,就可以实现框架的自动配置,完成框架所依赖相关bean的创建工作,因此在实际使用中都是通过引入起步依赖来完成工作的,这里的起步依赖可以是Spring Boot已经提供的常用框架,也可以是我们在工作中根据业务自定义的,如下图就是Spring Boot提供的起步依赖(部分):
当然我们也可以自定义starter,可以参考这里,这里。
6.1 Starter机制
那SpringBoot中的Starter和自动配置又有什么关系呢?
其实首先要明白一个Starter,就是一个Maven依赖,当我们在项目的pom.xml文件中添加某个Starter依赖时,其实就是简单的添加了很多其他的依赖,比如:
- spring-boot-starter-web:引入了spring-boot-starter、spring-boot-starter-json、spring-boot-starter-tomcat等和Web开发相关的依赖包
- spring-boot-starter-tomcat:引入了tomcat-embed-core、tomcat-embed-el、tomcat-embed-websocket等和Tomcat相关的依赖包
- ...
如果硬要把Starter机制和自动配置联系起来,那就是通过@ConditionalOnClass这个条件注解,因为这个条件注解的作用就是用来判断当前应用的依赖中是否存在某个类或某些类,比如:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {@BeanTomcatServletWebServerFactory tomcatServletWebServerFactory(ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,ObjectProvider<TomcatContextCustomizer> contextCustomizers,ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();// orderedStream()调用时会去Spring容器中找到TomcatConnectorCustomizer类型的Bean,默认是没有的,程序员可以自己定义factory.getTomcatConnectorCustomizers().addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));factory.getTomcatContextCustomizers().addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));factory.getTomcatProtocolHandlerCustomizers().addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));return factory;}}
上面代码中就用到了@ConditionalOnClass,用来判断项目中是否存在Servlet.class、Tomcat.class、UpgradeProtocol.class这三个类,如果存在就满足当前条件,如果项目中引入了spring-boot-starter-tomcat,那就有这三个类,如果没有spring-boot-starter-tomcat那就可能没有这三个类(除非你自己单独引入了Tomcat相关的依赖)。
所以这就做到了,如果我们在项目中要用Tomcat,那就依赖spring-boot-starter-web就够了,因为它默认依赖了spring-boot-starter-tomcat,从而依赖了Tomcat,从而Tomcat相关的Bean能生效。
而如果不想用Tomcat,那就得这么写:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><!-- 排除掉Tomcat依赖 --><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions>
</dependency>
<!-- 添加jetty依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
得把spring-boot-starter-tomcat给排除掉,再添加上spring-boot-starter-jetty的依赖,这样Tomcat的Bean就不会生效,Jetty的Bean就能生效,从而项目中用的就是Jetty。
七、自动配置相关的重要注解以及自动配置源码分析
7.1 SpringBootApplication
在编写Spring Boot的main程序时,想要成为Spring Boot程序,必定会使用该注解,用来写在main方法所在的类上,可能如下:
@SpringBootApplication
public class SpringbootHelloWorldApplication {public static void main(String[] args) {SpringApplication.run(SpringbootHelloWorldApplication.class, args);}
}
该注解最重要的一个作用就是开启自动配置,在META-INF/spring.factories文件中提供的自动配置类,不管是Spring Boot已经提供的还是自定义的都需要使用该注解修饰的class名称作为key进行配置(比如上面示例代码的SpringbootHelloWorldApplication),如下是在这篇文章自定义的:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
dongshi.daddy.beanconfig.MyFirstBeanConfig
有一点不太好,就是自动配置类,没有使用AutoConfiguration结尾,大家看的时候可自行按照规范修改类名称。当然该注解还组合的其它的功能,下面看下注解的源码:
// 通过@SpringBootConfiguration,代表是一个java config类
// 通过@EnableAutoConfiguration,开启自动配置
// 通过@ComponentScan,设置扫描包路径
@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 {// 设置不会被自动配置的类的class数组,如果是不希望某个自动配置// 生效,可以设置到这里@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};// 设置不会被自动配的类的bean名称数组,如果不希望某个自动配置类// 生效,可以将其bean名称设置到这里@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")String[] scanBasePackages() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};
}
由源码我们就能看出,其实这个注解什么也没做,活都交给属下做了。比如其中做了比较重要工作的三个注解:
- @SpringBootConfiguration:其实就是@Configuration,因此主启动类可以当做配置类使用,比如注入Bean等。
- @EnableAutoConfiguration:这个注解牛批了,名字就不一样,开启自动配置,自动配置的关键都在这了。
- @ComponentScan:包扫描注解
下面我们分别来看下@SpringBootApplication中重要的注解。
7.2 Inherited
含义是:如果某个自定义的注解,使用了Inherited注解进行声明,则标注了该自定义注解类的子类,将会继承该自定义注解,但是需要注意仅仅对类上的声明有效,对于方法和属性上的声明无效。不了解的朋友可以参考这篇文章。
7.3 SpringBootConfiguration
该注解是java config中@Configuration注解的子注解,只是为了在Spring Boot环境下更加见名知意,本质上和@Configuration注解没有区别,然后在类里边的方法就可以使用@Bean注解来定义spring bean了,源码如下:
// 同@Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
不了解的朋友可以参考这篇文章。
7.4 ComponentScan
用于指定扫描使用了@Configuration、@Component、@Controller、@Service、@Repository注解的类的包路径。关于此注解不清楚的可以参考这篇文章。
7.5 EnableAutoConfiguration
这是Spring Boot中开启自动配置的注解,为自定义注解,完整路径是org.springframework.boot.autoconfigure.EnableAutoConfiguration。源码如下:
// 开启spring应用环境的自动配置,尝试猜测并且自动配置可能需要的spring beans。自动配置类一般都是
// 基于classpath或者是你已经定义的bean来应用的。比如,如果在classpath下有tomcat-embedded.jar
// 则说明你可能需要自动创建一个TomcatServletWebServerFactory(如果没有定义ServletWebServerFactory的话)。
// 使用了该注解,自动配置将在没有任何副作用的情况下生效。可以通过exclude(),excludeNames()来排除不想要自动配置的类,也可以使用spring.autoconfigure.exclude属性来排除。
// 自动配置永远在用户定义的bean完成注册之后生效。该注解最好使用在根目录,这样其子包也能自动被扫描到。
// 通过SpringFactoriesLoader机制来自动定位需要自动配置的bean,一般配合条件注解@ConditionalOnClass、@ConditionalOnMissingBean等一起使用
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";// 设置要排除的自动配置类的class类型数组,之后永远也不会被应用自动配置Class<?>[] exclude() default {};// 设置要排除的自动配置类对应的bean名称数组,之后永远也不会被应用自动配置String[] excludeName() default {};
}
这里会多次间接或者是直接的使用到@Import注解,该注解的作用是引入java config配置类,本质上就是引入对象到IoC容器中,成为spring bean,不了解的朋友可以参考这篇文章。
其中比较关键的注解是@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class):
- @AutoConfigurationPackage:自动配置包注解,默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到IOC容器中。
- @Import:该注解不必多说了,前面文章说过很多次了,这里是导入了AutoConfigurationImportSelector,用来注入自动配置类。
以上只是简单的分析了两个注解,下面将会从源码详细的介绍一下。我们先来看第一个@AutoConfigurationPackage。
7.5.1 @AutoConfigurationPackage
该注解源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
其中@Import(AutoConfigurationPackages.Registrar.class)是通过引入bean definition的方式来引入对象到IoC容器中,相比普通方式省略了解析为bean definiton的步骤。
那么AutoConfigurationPackages.Registrar,这个类是干什么的?其源码如下:
org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// new PackageImport(metadata).getPackageName():获取扫描的包register(registry, new PackageImport(metadata).getPackageName());}...snip...
}
其实这个类就两个方法,但是的最重要的就是registerBeanDefinitions方法,这个方法很明显是用来注入Bean的,这里的重点是注入哪些Bean,要想知道这个就需要找到导入Bean的包路径,也就是重点看这段代码:
//获取扫描的包
new PackageImports(metadata).getPackageNames()
跟进代码,主要逻辑都在#PackageImports.PackageImports()这个构造方法中,源码解析如下图:
从上面源码分析可以知道,这里扫描的包名是由两部分组成,分别如下:
- 从@AutoConfigurationPackage注解中的两个属性解析得来的包名。
- 注解AutoConfigurationPackage所在的包名,即是@SpringBootApplication所在的包名。
- @AutoConfigurationPackage默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到IoC容器中。
我们再回到register()方法:
org.springframework.boot.autoconfigure.AutoConfigurationPackages#register
public static void register(BeanDefinitionRegistry registry, String... packageNames) {if (registry.containsBeanDefinition(BEAN)) {...snip...}else {GenericBeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(BasePackages.class);// 设置启动类所在的包路径beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);registry.registerBeanDefinition(BEAN, beanDefinition);}
}
其实就是定义了用于封装启动类所在包路径的BasePackages对象的BeanDefinition对象,bean名称是private static final String BEAN = AutoConfigurationPackages.class.getName();,后续如果是需要用到启动类所在包路径的话,就可以通过该spring bean来获取了。
7.5.2 @Import(AutoConfigurationImportSelector.class)
注解@Import(AutoConfigurationImportSelector.class)是重点,用于导入自动配置类。
这个注解本身就不用多说了,最重要的就是该注解导入的AutoConfigurationImportSelector类,我们单独在下一节讲解该类。
7.6 AutoConfigurationImportSelector
我们先来看看它的继承关系,如下图:
这个类的继承关系还是挺简单的,实现了Spring中的xxAware注入一些必要的组件,但是最值得关心的是实现了一个DeferredImportSelector这个接口,这个接口扩展了ImportSelector,也改变了其运行的方式。
「注意」:这个类会导致一个误区,平时看到xxxSelector已经有了反射弧了,肯定会在selectImports()方法上DEBUG,但是这个类压根就没执行该方法,我第一次看也有点怀疑人生了,原来它走的是DeferredImportSelector的接口方法。
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector实现了DeferredImportSelector接口实现自动配置类的引入。
|
其实该类真正实现逻辑的方法是process()方法,但是主要加载自动配置类的任务交给了getAutoConfigurationEntry()方法,具体的逻辑如下图:
上图的逻辑很简单,过程如下:
- 判断是否开启了自动配置
- 获取注解中设置的属性
- 从spring.factories文件中获取所有自动配置类,这里获取到的是一个List
- 将所有自动配置类的List转换为Set,这样就将重复的类去除掉了
- 再过滤掉@SpringBootApplication中定义排除的自动配置类
上图中的第③步getCandidateConfigurations()方法就是从META-INF/spring.factories中加载自动配置类,代码很简单,在上一篇分析启动流程的时候也有很多组件是从spring.facotries文件中加载的,代码都类似。那么下面我们就从getCandidateConfigurations()方法为入口开始分析源码。
|
这些自动配置类是通过方法org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations获取的,先放在这里,后续会分析执行过程是如何的,方法源码如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {// 从META-INF/spring.factories文件中获取自动配置类// getSpringFactoriesLoaderFactoryClass():interface org.springframework.boot.autoconfigure.EnableAutoConfiguration// <202105251818>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;
}
<202105251818>处结果如下图:
下图是如何调用到这里的,发起点还是容器刷新refresh(spring的启动过程讲过这个方法),感兴趣的可参考该图进行debug调试:
其中红框的getImports方法就是我们需要进一步分析的方法,源码如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports
// <202105281804>
public Iterable<Entry> selectImports() {if (this.autoConfigurationEntries.isEmpty()) {return Collections.emptyList();}// map(AutoConfigurationEntry::getExclusions):获取exclusions的set集合private final Set<String> exclusions;// flatMap(Collection::stream):展开exclusions集合,并合并// collect(Collectors.toSet()):转换成set集合并返回,这样可以将重复的类去重// 总体是获取autoConfigurationEntries中所有的exlusions集合,合并到一个set集合中,这样我们就拿到了所有需要剔除的自动配置类Set<String> allExclusions = this.autoConfigurationEntries.stream().map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());// 获取autoConfigurationEntries中每个元素的configurations集合,并合并到一个set集合中返回,这个操作可以将重复的类去重Set<String> processedConfigurations = this.autoConfigurationEntries.stream().map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));// 从可能需要处理的的集合中删除需要排除的,这一步就相当于完成了要加载的自动配置类的过滤操作,将不需要加载的自动配置类剔除// 这里其实在前面逻辑已经排除过了,为什么要重复排除???不影响结果,忽略!!!processedConfigurations.removeAll(allExclusions);// 生成Entry的List集合返回,后续使用return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream().map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)).collect(Collectors.toList());
}
<202105281804>处方法使用到了java8 Stream API,不清楚的可以参考这篇文章。
我们接着来看在getImports方法之前被调用的方法process,源码如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process
// annotationMetadata:是启动类上的注解元信息对象,一般只有一个@SpringBootApplication注解
// deferredImportSelector:通过@Import注解配置的AutoConfigurationImportSelector,是@SpringBootApplication注解的组合注解,用于完成导入自动配置类的工作
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()));// <202105271135> AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);// 添加到autoConfigurationEntries集合中// private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();this.autoConfigurationEntries.add(autoConfigurationEntry);for (String importClassName : autoConfigurationEntry.getConfigurations()) {// private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();// key是要导入的自动配置类的全限定类名称,value是启动类的注解元信息对象this.entries.putIfAbsent(importClassName, annotationMetadata);}
}
<202105271135>处主要是两个方法调用,一个是org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#getAutoConfigurationMetadata,另一个是org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry,第一个方法是用来获取自动配置类和条件注解的组合信息的,第二个方法是用来获取需要自动配置的类和不需要自动配置的类,然后封装在AutoConfigurationEntry对象中返回,分别来看下。第一个方法参考7.6.1:getAutoConfigurationMetadata,第二个方法参考7.6.2:getAutoConfigurationEntry。
7.6.1:getAutoConfigurationMetadata
源码如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#getAutoConfigurationMetadata
private AutoConfigurationMetadata getAutoConfigurationMetadata() {// 为null才重新获取if (this.autoConfigurationMetadata == null) {// <202105271315>this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);}return this.autoConfigurationMetadata;
}
<202105271315>处是从spring-boot-autoconfigure-2.1.14.RELEASE.jar!\META-INF\spring-autoconfigure-metadata.properties文件读取配置的信息,配置到配置文件中的原因是,在不加载自动配置类信息的前提下获取自动配置和其条件注解信息,省去了加载自动配置类信息的过程,提高程序启动的效率。
关于加载过程详细分析,参考这篇文章。
源码如下:
org.springframework.boot.autoconfigure.AutoConfigurationMetadataLoader#loadMetadata(java.lang.ClassLoader)
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {// protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";// <202105271319>return loadMetadata(classLoader, PATH);
}
<202105271319>处源码如下:
org.springframework.boot.autoconfigure.AutoConfigurationMetadataLoader#loadMetadata(java.lang.ClassLoader, java.lang.String)
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(urls.nextElement())));}// 该处源码如下,直接封装为AutoConfigurationMetadata对象,并返回/*staticAutoConfigurationMetadata loadMetadata(Properties properties) {return new PropertiesAutoConfigurationMetadata(properties);}*/// <202105271328>return loadMetadata(properties);}catch (IOException ex) {throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);}
}
<202105271328>处最终结果,如下图:
这些信息在后续过滤自动配置类的时候会使用到。再多说一点,这里的key是自动配置类的全限定名称+条件注解名称组合的字符串,value是条件注解的值。
7.6.2:getAutoConfigurationEntry
该方法用于获取需要自动配置的类和不需要自动配置的类,封装在AutoConfigurationEntry对象中,调用的方法是org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry,源码如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry
// 根据启动类上的@SpringBootAppplication注解组合的@Import(AutoConfigurationImportSelector.class)注解来完成引入自动配置类的工作
// 参数autoConfigurationMetadata:封装自动配置类和其条件注解元信息组合信息的对象
// 参数annotationMetadata:启动类的注解元信息对象
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {// <202105271344>// 判断是否启用了自动配置,如果没有则返回空if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}// 获取启动类注解元信息的属性信息AnnotationAttributes attributes = getAttributes(annotationMetadata);// <202105271434>List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);// 删除重复的,源码如下:/*org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#removeDuplicatesprotected final <T> List<T> removeDuplicates(List<T> list) {return new ArrayList<>(new LinkedHashSet<>(list));}*/// 简单粗暴configurations = removeDuplicates(configurations);// 获取需要排除的自动配置类,即在注解中显式设置的需要排除的自动配置类// <202105271525>Set<String> exclusions = getExclusions(annotationMetadata, attributes);// <202105271600>// 检测排除的自动配置类是否合法checkExcludedClasses(configurations, exclusions);// 从自动配置类中移除需要排除的自动配置类configurations.removeAll(exclusions);// <202105271618> 对自动配置类进行过滤操作,将不需要的类剔除configurations = filter(configurations, autoConfigurationMetadata);// 触发自动配置类导入事件// <202105271822>fireAutoConfigurationImportEvents(configurations, exclusions);// 需要加载的自动配置类configurations和需要排除的自动配置类exclusions作为参数// 构造AutoConfigurationEntry对象return new AutoConfigurationEntry(configurations, exclusions);
}
<202105271344>处是获取是否启用自动配置功能,源码如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#isEnabled
protected boolean isEnabled(AnnotationMetadata metadata) {if (getClass() == AutoConfigurationImportSelector.class) {// 从Environment中获取String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";的值,true则正常执行自动配置,false,则不执行自动配置return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);}return true;
}
<202105271434>处是获取候选的待自动配置的类,其实前面已经大概分析过,这里再贴下源码:
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
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;
}
<202105271525>处是获取需要排除的自动配置类,源码如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getExclusions
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {Set<String> excluded = new LinkedHashSet<>();// 获取exclude配置的值excluded.addAll(asList(attributes, "exclude"));// 获取excludeName配置的值excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));// 获取环境变量spring.autoconfigure.exclude配置的排除信息// String[] excludes = getEnvironment().getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);excluded.addAll(getExcludeAutoConfigurationsProperty());return excluded;
}
如下图是启动类配置和attribute,以及excluded集合的对比注意:仅仅是例子,因为配置的实际上不是自动配置类,所以后续在checkExcludedClasses方法会抛出异常:
<202105271600>处是检测排除的自动配置类是否合法,源码如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#checkExcludedClasses
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {List<String> invalidExcludes = new ArrayList<>(exclusions.size());for (String exclusion : exclusions) {// 如果是classpath下有该类,并且不是自动配置类,则添加到无效排除// invalidExcludes集合中if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {invalidExcludes.add(exclusion);}}// 如果是无效排除集合invalidExcludes集合不为空if (!invalidExcludes.isEmpty()) {// 抛出异常并给出不合法的自动配置类信息列表,源码如下:/*protected void handleInvalidExcludes(List<String> invalidExcludes) {StringBuilder message = new StringBuilder();for (String exclude : invalidExcludes) {message.append("\t- ").append(exclude).append(String.format("%n"));}throw new IllegalStateException(String.format("The following classes could not be excluded because they are" + " not auto-configuration classes:%n%s",message));}*/handleInvalidExcludes(invalidExcludes);}
}
<202105271618>处是根据自动配置类的条件注解信息进行过滤操作,因为不满足条件注解的肯定是不需要自动配置的,具体过程参考:Spring Boot过滤不需要的自动配置类过程分析。
<202105271822>处是在获取了要引入的自动配置类后触发事件,源码如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#fireAutoConfigurationImportEvents
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {// 从META-INF/spring.factories中获取key为AutoConfigurationImportListener的AutoConfigurationImportListener的实现类们,源码如下:/*protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);}*/List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();// 调用监听器,执行监听器逻辑if (!listeners.isEmpty()) {AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);for (AutoConfigurationImportListener listener : listeners) {invokeAwareMethods(listener);listener.onAutoConfigurationImportEvent(event);}}
}
相关文章:【Spring】Spring Boot 自动配置-CSDN博客
【Spring】Spring Boot启动过程源码解析_springboot 启动过程中的源码解析-CSDN博客