引入
早期的EJB笨重复杂,使用起来很不方便,很多人对此叫苦不迭。后来轻量级的Spring框架横空出世,通过编写一些简单的XML配置或注解就能快速搭建起项目环境,不用在EJB体系中苦苦挣扎,开发者的生产力得到解放。但单纯使用Spring搭建环境和开发应用仍需开发者编写很多配置,冗长的XML或一堆注解配置类总会分散开发者的一部分精力。后来Spring Boot出现了,它是对Spring的再封装,提供了几个核心特性,让开发者不用在冗长的配置和复杂的环境部署中挣扎和重复。
Spring Boot的核心特性包括约定大于配置,各种场景启动器,自动装配和嵌入式Web容器等:
- 约定大于配置: 对日常开发中的常见场景提供了约定配置,开发者少量配置或不配置。
- 场景启动器starter:对各种场景做了整合,把这些场景需要的依赖都收集一起并通过默认配置,开发者在maven的pom文件中添加一个starter依赖就能获得这个场景需要的所有依赖。
- 自动装配:通过Spring原生装配方式,搭配Spring Boot提供的条件注解,可以在具体场景中自动注册所需组件,也可通过配置文件动态注册一些组件。
- 嵌入式容器:应用开发完成后不用打成传统的war包然后放到Tomcat等Web容器中启动,可以打成单独jar包,然后在内部嵌入的Web容器中运行。
这些核心特性总结一下,就是整合了各种场景,通过提供约定配置、自动装配所需组件和提供嵌入式容器等方式,不用再像单纯使用Spring那样纠结于繁杂的配置,Spring Boot会帮我们做很多工作。 这大大减轻了开发者的工作量,生产力再次得到了解放!
当我们进入项目组,拉下代码,打开项目可能会发现一些模块,每个模块会有一个名称像xxxSpringApllication的主类,见下图。这个类里面有个main方法,方法体内会有run方法。启动main方法后,整个项目就跑起来了。
启动项目真方便,不需要开发做很多额外工作,一个main方法就解决了!但是方便的背后你知道Spring Boot背后为你做了什么吗?
- 你知道为什么run方法执行后项目就启动了呢?
- 你知道@SpringBootApplication背后为我们做了啥吗?
- 我们常说Spring框架的2个特性IOC和AOP,你知道Spring Boot是怎样支持这两者的吗?
- 工作中我们会编写properties或yaml配置文件,然后写注解配置类,你知道文件中的配置是怎样一步一步被Spring Boot获取并注入配置类中的吗?或者你可能知道Spring Boot默认的配置文件格式是properties和yaml,如果我想用json格式的文件,Spring Boot它还能做到吗?
- 你可能为某些场景自定义过启动器starter,但是为什么你那样做产生的jar就能被其他Spring Boot项目用起来呢?
这些背后的故事,我们可以从解析源码中获取。下面我们就开始分析Spring Boot的源码。
1. Spring的@EnableXXX注解
1.1 @EnableXXX的方式
Spring早期引入大量@EnableXXX注解,通过加上这些注解,就可以快速装配相应模块的bean到IOC容器:
-
@EnableWebMvc
-
@EnableScheduling
-
@EnableAsync
......
查看对应的源码:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({DelegatingWebMvcConfiguration.class}) public @interface EnableWebMvc { }@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(AsyncConfigurationSelector.class) public @interface EnableAsync { }
可以发现上面几个@EnableXXX注解就是简单的:自定义注解+@Import导入组件。我们也来学习一下Spring,通过一个场景来验证下这种方式能否获取我们想要的组件。一个公司大致有开发,测试,产品,项目经理这几种岗位,他们分工合作共同推动公司发展。我们用代码构建一个公司,公司里有这四种岗位,不同岗位可看作一个个组件,要将它们注入公司这个容器中。实际上,@Import可以导入普通类、配置类、ImportSelector实现类、ImportBeanDefinitionRegistrar实现类等,我们分别验证。请在本地跟着实现一下!
1.2 导入普通类
1.定义@EnableCompany
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({})
public @interface EnableCompany {
}
2.创建项目经理类,然后加入到@EnableCompany的@Import元注解的属性中
public class Manager {
}
3.创建配置类CompanyConfiguration,加上@EnableCompany
@Configuration
@EnableCompany
public class CompanyConfiguration {
}
4.测试类
public class CompanyApplication {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(CompanyConfiguration.class);
Manager manager = ctx.getBean(Manager.class);
System.out.println(manager);
}
}
运行结果:
org.cosmos.springboot.base.a_demo.component_demo.Manager@6af93788
可以看到普通类Manager已经注入到IOC容器中了。
1.3 导入配置类
1.创建开发者类
public class Developer {
private String qualified;
public Developer(String qualified) {
this.qualified = qualified;
}
//getter and setter ...
}
2.创建配置类并结合@Bean注册组件
@Configuration
public class DeveloperConfiguration {
@Bean
public Developer backer(){
return new Developer("后端");
}
@Bean
public Developer fronter(){
return new Developer("前端");
}
}
3.DeveloperConfiguration类加入到@EnableCompany的@Import的属性中
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({
Manager.class,
DeveloperConfiguration.class
})
public @interface EnableCompany {
}
4.测试类
public class CompanyApplication {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(CompanyConfiguration.class);
Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
System.out.println("Developer beans in the container:");
Map<String, Developer> developers = ctx.getBeansOfType(Developer.class);
developers.forEach((name, developer) -> System.out.println(developer));
}
}
运行结果:
companyConfiguration org.cosmos.springboot.base.a_demo.component_demo.Manager org.cosmos.springboot.base.a_demo.configuration.DeveloperConfiguration backer fronter Developer beans in the container: org.cosmos.springboot.base.a_demo.component_demo.Developer@6dbb137d org.cosmos.springboot.base.a_demo.component_demo.Developer@3c9d0b9d
配置类和2个开发者都注册进IOC容器了。
1.4 导入ImportSelector实现类
1.创建产品经理类ProductConfiguration
@Configuration
public class ProductConfiguration {
@Bean
public Product bbproduct(){
return new Product();
}
}
2.创建ImportSelector实现类,声明它要导入的类(覆盖selectImports方法,返回值中我们设置了产品经理类及其配置类)
public class ProductImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{Product.class.getName(), ProductConfiguration.class.getName()};
}
}
3.ProductImportSelector类加入到@EnableCompany的@Import的属性中
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({
Manager.class,
DeveloperConfiguration.class,
ProductImportSelector.class,
})
public @interface EnableCompany {
}
4.测试类
public class CompanyApplication {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(CompanyConfiguration.class);
Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
}
}
测试类运行结果:
companyConfiguration org.cosmos.springboot.base.a_demo.component_demo.Manager org.cosmos.springboot.base.a_demo.configuration.DeveloperConfiguration backer fronter org.cosmos.springboot.base.a_demo.component_demo.Product org.cosmos.springboot.base.a_demo.configuration.ProductConfiguration bbproduct
ProductImportSelector指定的类都注入进IOC容器了,ProductImportSelector本身不会注入。
1.5 导入ImportBeanDefinitionRegisrtar实现类
1.创建测试者类
public class Tester {
}
2.创建ImportBeanDefinitionRegisrtar实现类, 覆盖registerBeanDefinitions方法。
public class TesterRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){
registry.registerBeanDefinition("Tester", new RootBeanDefinition(Tester.class));
}
}
同样加入到@EnableCompany的@Import的属性中,然后运行配置类:
companyConfiguration org.cosmos.springboot.base.a_demo.component_demo.Manager org.cosmos.springboot.base.a_demo.configuration.DeveloperConfiguration backer fronter org.cosmos.springboot.base.a_demo.component_demo.Product org.cosmos.springboot.base.a_demo.configuration.ProductConfiguration bbproduct Tester
可以看到,ImportBeanDefinitionRegisrtar实现类也成功导入了显式指明的类。
2.@Conditional
上面的案例说明了Spring的一种装配组件到容器的重要方法,Spring Boot作为对Spring的二次封装,自然吸纳了这个优秀方法。另外,Spring Boot还提供了基于Profile的装配方法,可以查找资料看一下。除此之外,Spring Boot还提供了基于@ConditionalOnXXX系列注解,这是基于某些条件的装配。我们在项目代码或Spring Boot的框架源码经常看到很多这样的注解,从这些注解中我们可以有选择性的导入我们所需的bean。
项目经理是项目的灵魂,是吗?:smile:项目经理首先对接市场或客户,提出或传递需求,然后产品经理梳理需求并画原型,后面开发者设计系统编写代码,最后测试者测试开发质量。
开发者依赖项目经理的存在,没有项目经理就不会有开发者后面的事。我们先创建判断开发者是否存在的类ManagerCondition:
public class ManagerCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getBeanFactory().containsBeanDefinition(Manager.class.getName());
}
}
然后在开发者注解配置类中加上@Condition注解,加上这个判断类:
@Configuration
public class DeveloperConfiguration {
@Bean
@Conditional(ManagerCondition.class)
public Developer backer(){
return new Developer("后端");
}
@Bean
public Developer fronter(){
return new Developer("前端");
}
}
然后@EnableCompany的@Import只保留DeveloperConfiguration.class,然后启动测试类
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({
// Manager.class,
DeveloperConfiguration.class,
// ProductImportSelector.class,
// TesterRegistrar.class,
// TesterDeferredImportSelector.class
})
public @interface EnableCompany {
}
运行结果:
companyConfiguration org.cosmos.springboot.base.a_demo.configuration.DeveloperConfiguration fronter
可见IOC容器中没有Manager类,开发者对象"后端"也不会注册到容器中。@EnableCompany中取消 Manager.class的注册重新运行测试类:
companyConfiguration org.cosmos.springboot.base.a_demo.component_demo.Manager org.cosmos.springboot.base.a_demo.configuration.DeveloperConfiguration backer fronter
"后端"重新回到了战场。所以@Conditional注解帮我们决定哪些类可以注入哪些不行。常见的ConditionalOnXXX注解有:
-
@ConditionalOnClass
-
@ConditionOnBean、@ConditionalOnMissingBean
-
@ConditionalOnProperty
......
从名称基本都可以猜测到每个注解的用法。
3. SPI机制
下面学习一下SPI。日常开发中我们应该在代码依赖接口而不是直接依赖接口实现类,因为某些原因实现类可能要修改,但真正起作用的还是实现类,依赖了接口怎么去找到对应的实现类来完成任务呢?SPI机制设计出来就解决了这个问题。
3.1 JDK SPI
SPI可以动态的加载接口/抽象类的实现类,JDK提供了对SPI的支持。先来看看JDK原生SPI有怎样神奇的效果。
1.定义接口和实现类
package org.cosmos.springboot.base.spi.bean;
public interface Cloud {
}
package org.cosmos.springboot.base.spi.bean;
public class AliCloud implements Cloud {
}
package org.cosmos.springboot.base.spi.bean;
public class TencentCloud implements Cloud {
}
2.创建SPI文件。JDK规范了,所有定义的SPI文件要满足:
- 位置:必须放在项目的META-INF/services目录下。
- 文件名:必须命名为接口或抽象类的全限定名。
- 文件内容:接口或抽象类的具体实现类全限定名,如果有多个实现类,则每行声明一个实现类的全限定名,多个实现类之间没有分割符。
项目resource目录下创建META-INF/services目录,在其中创建文件,文件名org.cosmos.springboot.base.spi.bean.Cloud,文件内容:
org.cosmos.springboot.base.spi.bean.AliCloud
org.cosmos.springboot.base.spi.bean.TencentCloud
3.创建测试类
public class JdkSPIApplication {
public static void main(String[] args) {
ServiceLoader<Cloud> serviceLoader = ServiceLoader.load(Cloud.class);
serviceLoader.iterator().forEachRemaining(cloud -> {
System.out.println(cloud);
});
}
}
运行结果:
org.cosmos.springboot.base.spi.bean.AliCloud@816f27d org.cosmos.springboot.base.spi.bean.TencentCloud@87aac27
通过ServiceLoader的load方法成功获得了Cloud接口声明的2个实现类!
3.2 Spring SPI
Spring的SPI相较于JDK原生SPI更强大,不只是接口或实现类,还可以是任何一个类或注解。Spring Boo多处利用了SPI机制加载自动配置类和一些特殊组件。先来验证一下效果。
1.创建SPI文件
- 位置:必须放在项目的META-INF/services目录下。
- 文件名:需要命名为spring.factories。这个文件其实是个properties文件。
- 文件内容:接口或抽象类的具体实现类的全限定名作为properties的key,具体需要获取的类(不一定是全部的实现类)作为value,多个实现类 用英文逗号分割。
org.cosmos.springboot.base.spi.bean.Cloud=org.cosmos.springboot.base.spi.bean.AliCloud, org.cosmos.springboot.base.spi.bean.TencentCloud
2.测试类
public class SpringSpiApplication {
public static void main(String[] args) {
List<Cloud> clouds = SpringFactoriesLoader.loadFactories(Cloud.class, SpringApplication.class.getClassLoader());
clouds.forEach(cloud -> {
System.out.println(cloud);
});
System.out.println("cloudNames:");
List<String> cloudNames = SpringFactoriesLoader.loadFactoryNames(Cloud.class, SpringApplication.class.getClassLoader());
cloudNames.forEach(className -> {
System.out.println(className);
});
}
}
运行结果:
org.cosmos.springboot.base.spi.bean.AliCloud@12bc6874 org.cosmos.springboot.base.spi.bean.TencentCloud@de0a01f cloudNames: org.cosmos.springboot.base.spi.bean.AliCloud org.cosmos.springboot.base.spi.bean.TencentCloud
Spring的SPI机制也生效了。下面进一步看看Spring到底是如何获取spring.factories中声明的类。核心是SpringFactoriesLoader.loadFactoryNames方法:
//用全局Map做缓存
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
loadFactoryNames调用了静态方法loadSpringFactories:
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 {
//缓存找不到,直接从META-INF/spring.factories获取!
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
//检索每一个spring.factories文件
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
//以properties读取
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
//properties的key(接口等的全限定名)
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
//每个spring.factories文件的同名key对应的value,合并放到result,result是MultiValueMap
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
//catch ...
}
}
源码表明项目启动后,先把META-INF/spring.factories中声明的所有类读出来放到缓存区中,后面直接走缓存。
4. @SpringBootApplication
点开@SpringBootApplication,它由3个注解标注:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
4.1 @ComponentScan
@ComponentScan来自Spring,放在这里意图扫描主类所在包及子包中所有组件,这个注解中excludeFilters属性是个数组,先看TypeExcludeFilter。spring文档指出,只需要上下文中注册TypeExcludeFilter子类并覆盖match方法,Spring Boot会自动找到这些子类并调用它们。
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
if (this.beanFactory instanceof ListableBeanFactory && this.getClass() == TypeExcludeFilter.class) {
Iterator var3 = this.getDelegates().iterator();
while(var3.hasNext()) {
TypeExcludeFilter delegate = (TypeExcludeFilter)var3.next();
//遍历所有TypeExcludeFilter并调用它的match方法
if (delegate.match(metadataReader, metadataReaderFactory)) {
return true;
}
}
}
return false;
}
再来看看AutoConfigurationExcludeFilter,见名知义,它是用来排除自动配置类的,它的关键方法match会加上2个判断。
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return this.isConfiguration(metadataReader) && this.isAutoConfiguration(metadataReader);
}
private boolean isConfiguration(MetadataReader metadataReader) {
//1.判断是否是配置类:是否被@Configuration标注
return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
}
private boolean isAutoConfiguration(MetadataReader metadataReader) {
//2.判断是否是自动装配类:用Sping SPI机制加载所有配置类后从中判断
return this.getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
}
protected List<String> getAutoConfigurations() {
if (this.autoConfigurations == null) {
//Spring SPI
this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader);
}
return this.autoConfigurations;
}
4.2 @SpringBootConfiguration
@SpringBootConfiguration,很简单,用来标注这是一个配置类。
@Configuration
public @interface SpringBootConfiguration {}
4.3 @EnableAutoConfiguration
经过前面的准备,就可以探索@EnableAutoConfiguration这个非常重要的注解了。这个核心注解的作用是:启用Spring Boot的自动装配,根据导入的依赖和上下文合理加载默认配置。点开源码可以看到,它有2个注解标注:
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {}
4.3.1 @AutoConfigurationPackage
@EnableAutoConfiguration是个组合注解,先来研究@AutoConfigurationPackage注解。
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
@AutoConfigurationPackage又用@Import导入了一个类,这个类是抽象类AutoConfigurationPackages的静态内部类Registrar。Spring Boot会导入Registrar类,这个类肯定起了重要作用。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
返回来看AutoConfigurationPackages类,他有3个静态内部类Registrar、PackageImports和BasePackages。
Registrar实现了ImportBeanDefinitionRegistrar,说明他有导入组件到IOC容器的作用。重点关注它重写的registerBeanDefinitions方法,它调用了AutoConfigurationPackages的register方法。这个方法的第一个参数AnnotationMetadata,指的是@AutoConfigurationPackage所在根包;第二个参数的获取较长,是调用AutoConfigurationPackages的静态内部类PackageImports的getPackageNames方法的返回值。先看一下PackageImports:
private static final class PackageImports {
private final List<String> packageNames;
PackageImports(AnnotationMetadata metadata) {
//获取@AutoConfigurationPackage的属性
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
List<String> packageNames = new ArrayList();
String[] var4 = attributes.getStringArray("basePackages");
int var5 = var4.length;
int var6;
for(var6 = 0; var6 < var5; ++var6) {
String basePackage = var4[var6];
//获取basePackages属性值
packageNames.add(basePackage);
}
Class[] var8 = attributes.getClassArray("basePackageClasses");
var5 = var8.length;
for(var6 = 0; var6 < var5; ++var6) {
Class<?> basePackageClass = var8[var6];
//获取basePackageClasses属性值
packageNames.add(basePackageClass.getPackage().getName());
}
if (packageNames.isEmpty()) {
//通过上面没获取到,直接取Spring Boot主类所在包
packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
}
this.packageNames = Collections.unmodifiableList(packageNames);
}
List<String> getPackageNames() {
return this.packageNames;
}
}
再来看AutoConfigurationPackages类的register方法:
private static final String BEAN = AutoConfigurationPackages.class.getName();
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
//BeanDefinitionRegistry可以向IOC容器注册BeanDefinition
//容器中有AutoConfigurationPackages,则取出来将上面获取的包名字符串数组加到BasePackages构造器参数中
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
} else {
//容器中没有,就构造BeanDefinition,并将上面得到的包名放到BasePackages构造器参数中,然后注册到容器
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(2);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
static final class BasePackages {
private final List<String> packages;
//BasePackages的构造器参数会放到属性packages
BasePackages(String... names) {
List<String> packages = new ArrayList();
String[] var3 = names;
int var4 = names.length;
for(int var5 = 0; var5 < var4; ++var5) {
String name = var3[var5];
if (StringUtils.hasText(name)) {
packages.add(name);
}
}
this.packages = packages;
}
}
所以register方法把@AutoConfigurationPackage的basePackages属性和basePackageClasses属性指定的包下的类注册到容器了。
4.3.2 AutoConfigurationImportSelector
接下来看看@EnableAutoConfiguration的第二个注解,用@Import导入了AutoConfigurationImportSelector,它实现了DeferredImportSelector,但比ImportSelector执行得晚些,这个特点可以用来在其他类导入后做些补充性的工作。Spring Boot的自动装配也是基于约定大于配置,项目中已有的配置自动装配了,没有的配置需要补充进来,而DeferredImportSelector就适合做补充工作。先来看看DeferredImportSelector:
public interface DeferredImportSelector extends ImportSelector {
@Nullable
default Class<? extends Group> getImportGroup() {
return null;
}
interface Group {
void process(AnnotationMetadata metadata, DeferredImportSelector selector);
Iterable<Entry> selectImports();
class Entry {
private final AnnotationMetadata metadata;
private final String importClassName;
public Entry(AnnotationMetadata metadata, String importClassName) {
this.metadata = metadata;
this.importClassName = importClassName;
}
//...
}
//...
}
}
DeferredImportSelector接口设计了一个内部接口Group,为了把不同的DeferredImportSelector实现类根据一些条件分成不同的组,就抽象出这个接口。Group接口里又加了内部类Entry和一个重要方法process。具体到AutoConfigurationImportSelector来看:
public class AutoConfigurationImportSelector implements DeferredImportSelector{
//1. 重写了DeferredImportSelector的getImportGroup方法
public Class<? extends Group> getImportGroup() {
return AutoConfigurationImportSelector.AutoConfigurationGroup.class;
}
//2.AutoConfigurationGroup
private static class AutoConfigurationGroup implements Group {
//覆盖的process方法是加载所有自动配置类的入口!
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
//内部类的process方法调用了内部类外的方法getAutoConfigurationEntry
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();
while(var4.hasNext()) {
String importClassName = (String)var4.next();
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
}
//3.重写了ImportSelector的selectImports方法,这里会导入组件!
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
}
1. 先看AutoConfigurationGroup
属性autoConfigurationEntries将要封装自动装配的组件:
private static class AutoConfigurationGroup implements Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
private final Map<String, AnnotationMetadata> entries = new LinkedHashMap();
private final List<AutoConfigurationImportSelector.AutoConfigurationEntry> autoConfigurationEntries = new ArrayList();
//...
}
而AutoConfigurationEntry定义了要导入和不导入的组件名集合:
protected static class AutoConfigurationEntry {
private final List<String> configurations;
private final Set<String> exclusions;
//...
}
process方法是加载所有自动配置类的入口!它调用getAutoConfigurationEntry方法,参数annotationMetadata指的是@EnableAutoConfiguration。方法返回的正是上面说的AutoConfigurationEntry对象,它分别定义了属性表示要导入容器中和不要导入的组件集合,所以我猜想getAutoConfigurationEntry方法要做的就是获取所有自动装配类,然后按照某些方法过滤出需要导入的和不要到导入的,最后封装到AutoConfigurationEntry对象返回,源码正是做了这些工作:
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
//获取注解属性
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//获取所有自动配置类!
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//去重
configurations = this.removeDuplicates(configurations);
//获取显式指定要排除的配置类
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
//去除操作
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
getCandidateConfigurations利用Spring SPI加载了所有自动配置类:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//再次看到Spring SPI的身影!
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
return configurations;
}
获取指定的那些要排除的配置类:
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet();
excluded.addAll(this.asList(attributes, "exclude"));
excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
excluded.addAll(this.getExcludeAutoConfigurationsProperty());
return excluded;
}
//获取Spring Boot全局配置spring.autoconfigure.exclude的值
protected List<String> getExcludeAutoConfigurationsProperty() {
Environment environment = this.getEnvironment();
if (environment == null) {
return Collections.emptyList();
} else if (environment instanceof ConfigurableEnvironment) {
Binder binder = Binder.get(environment);
return (List)binder.bind("spring.autoconfigure.exclude", String[].class).map(Arrays::asList).orElse(Collections.emptyList());
} else {
String[] excludes = (String[])environment.getProperty("spring.autoconfigure.exclude", String[].class);
return excludes != null ? Arrays.asList(excludes) : Collections.emptyList();
}
}
process方法最后是遍历那些需要要导入的自动配置类,然后放到AutoConfigurationGroup的Map类型属性entries中。
2. selectImports
AutoConfigurationImportSelector的selectImports方法指明要导入到容器中的bean,方法也是上面分析过的getAutoConfigurationEntry方法。
总结一下,@EnableAutoConfiguration注解通过组合的2个注解完成了自动装配,利用@AutoConfigurationPackage完成指定包路径下(如果没指定,默认主启动类所在包及其子包)所有bean的注册,利用@Import和AutoConfigurationImportSelector配合SPI导入所有需要导入的bean。
5. 总结
至此,Spring Boot自动装配的源码分析了一下。本质就是通过采用Spring的@Import注解方式、@ConditionOnXXX系列注解、Spring SPI机制这三种方法实现了自动装配。源码设计的精巧有趣,跟一遍你会更好的利用Spring Boot这笔资源。