SpringBoot是目前软件中最主流的框架,其中一个主要优势就是解决了传统spring的重量级xml配置Bean,实现了自动装配。实现自动装配的核心是启动类上的@SpringBootApplication注解,主要由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解组成,由这三个注解共同完成了自动装配。
1、springboot启动类
任何一个Spring Boot项目中都会用到如下的启动类,在启动springboot的时候,会自动将Bean装配到IoC容器中。例如我们在使用Redis数据库的时候,会引入依赖spring-boot-starter-data-redis。在引入这个依赖后,服务初始化的时候,会将操作Redis需要的组件注入到IoC容器中以便进行后续使用。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
自动装配大致过程:
获取到组件(如spring-boot-starter-data-redis)META-INF文件夹下的spring.factories文件(spring.factories文件中列出了需要注入到IoC容器的类) -> 将spring.factories文件中指定的实体类注入到IoC容器中以便后续进行使用。
在SpringbootApplication启动类中,通过 SpringApplication.run(SpringbootApplication.class, args); 启动容器的过程中,由 @SpringBootApplication 注解实现了自动装配。
2、@SpringBootApplication 注解
@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 {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@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 有很多的注解组成,重要的只有三个Annotation:
- @Configuration(@SpringBootConfiguration实质就是一个@Configuration)
- @EnableAutoConfiguration
- @ComponentScan
我们可以尝试将启动类上的 @SpringBootApplication 替换为上面三个注解,是具有同样效果的。
2.1 @Configuration 注解
这个注解实际上就是代表了一个配置类,相当于一个beans.xml文件。SpringBoot社区推荐使用基于JavaConfig的配置形式,启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。
@Configuration 注解介绍详见:SpringBoot配置类的实现 - @Configuration、@Bean注解的使用详解
2.2 @ComponentScan 注解
@ComponentScan注解在Spring中很重要,该注解的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。
我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则Spring框架默认实现会从声明@ComponentScan所在类的package进行扫描。所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。
2.3 @EnableAutoConfiguration 注解
在spring中有关于@Enablexxx的注解是开启某一项功能的注解,比如@EnableScheduling表示开启spring的定时任务(通过@Import将Spring调度框架相关的bean定义都加载到IoC容器)。@EnableMBeanExport是通过@Import将JMX相关的bean定义加载到IoC容器。
@EnableAutoConfiguration的理念和做事方式其实是一脉相承的,简单概括就是,借助@Import的支持,收集和注册特定场景相关的bean定义(将所有符合自动配置条件的bean定义加载到Ioc容器)。
EnableAutoConfiguration代表开启springboot的自动装配。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
//按类型排序不需要自动装配的类
Class<?>[] exclude() default {};
//按名称排除不需要自动装配的类
String[] excludeName() default {};
}
从源码中可以看到,最关键的要属@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。同时借助于Spring框架原有的一个工具类:SpringFactoriesLoader,@EnableAutoConfiguration就可以实现智能的自动配置。
AutoConfigurationImportSelector源码
AutoConfigurationImportSelector 可以将需要装配的类装配到IoC容器中,从源码中可以看到,AutoConfigurationImportSelector 实现了很多的 xxxAware 和 DeferredImportSelector,所有的aware都优先于selectImports方法执行,也就是说selectImports方法最后执行,那么在它执行的时候所有需要的资源就都已经获取到了。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//1加载META-INF/spring-autoconfigure-metadata.properties文件
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
//2获取注解的属性及其值(PS:注解指的是@EnableAutoConfiguration注解)
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//3.在classpath下所有的META-INF/spring.factories文件中查找org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,并将其封装到一个List中返回
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//4.对上一步返回的List中的元素去重、排序
configurations = this.removeDuplicates(configurations);
//5.依据第2步中获取的属性值排除一些特定的类
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
//6对上一步中所得到的List进行过滤,过滤的依据是条件匹配。这里用到的过滤器是
//org.springframework.boot.autoconfigure.condition.OnClassCondition最终返回的是一个ConditionOutcome[]
//数组。(PS:很多类都是依赖于其它的类的,当有某个类时才会装配,所以这次过滤的就是根据是否有某个
//class进而决定是否装配的。这些类所依赖的类都写在META-INF/spring-autoconfigure-metadata.properties文件里)
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
}
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;
}
...
}
执行过程如下:
- 加载META-INF/spring-autoconfigure-metadata.properties文件
- 获取注解的属性及其值(PS:注解指的是@EnableAutoConfiguration注解)
- 获取所有自动装配的配置类,也就是读取spring.factories文件,在classpath下所有的META-INF/spring.factories文件中查找
- 去除重复的配置项,即对上一步返回的List中的元素去重、排序
- 依据第2步中获取的属性值排除一些特定的类
- 对上一步中所得到的List进行过滤,过滤的依据是条件匹配。这里用到的过滤器是org.springframework.boot.autoconfigure.condition.OnClassCondition最终返回的是一个ConditionOutcome[]数组。(PS:很多类都是依赖于其它的类的,当有某个类时才会装配,所以这次过滤的就是根据是否有某个class进而决定是否装配的。这些类所依赖的类都写在META-INF/spring-autoconfigure-metadata.properties文件里)
SpringFactoriesLoader源码
SpringFactoriesLoader作用是加载配置,属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组@Configuration类。
public abstract class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
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()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
...
3、自动装配总结
1)启动类的@SpringBootApplication注解主要由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解组成,这三个注解共同完成了自动装配功能;
2)@SpringBootConfiguration 注解标记启动类为配置类;
3)@ComponentScan 注解实现启动时扫描启动类所在的包以及子包下所有标记为bean的类,并在IOC容器中注册为bean;
4)@EnableAutoConfiguration通过 @Import 注解导入 ImportSelector 的子类 AutoConfigurationImportSelector类,然后通过AutoConfigurationImportSelector 类的 selectImports 方法去读取需要被自动装配的组件依赖下的spring.factories文件配置的组件的类全名,并按照一定的规则过滤掉不符合要求的组件的类全名,将剩余读取到的各个组件的类全名集合返回给IOC容器并将这些组件注册为bean;
- AutoConfigurationImportSelector类中重写了ImportSelector中selectImports方法,批量返回需要装配的配置类。
- AutoConfigurationImportSelector类通过selectImports方法加载读取所有 spring-boot-autoconfigure 依赖下的 spring-autoconfigure-metadata.properties 配置文件和spring.factories 配置文件的内容,并根据 AutoConfigurationImportSelector 类下的 AutoConfigurationImportFilter过滤器的过滤规则和 spring-autoconfigure-metadata.properties 配置文件的内容过滤掉 spring.factories文件中需要被过滤掉的组件元素(当然这之前还有一步根据@EnableAutoConfiguration注解的 exclude 和 excludeName属性过滤 spring.factories 配置文件的内容,由于 @EnableAutoConfiguration注解的这两个属性默认为空,所以这步操作什么都没做),最终返回spring.factories文件中剩余组件的类全名数组,并由IOC容器注册为Bean。
- SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。这些功能配置类要生效的话,会去classpath中找是否有该类的依赖类(也就是pom.xml必须有对应功能的jar包才行)并且配置类里面注入了默认属性值类,功能类可以引用并赋默认值。生成功能类的原则是自定义优先,没有自定义时才会使用自动装配类。
4、SpringApplication执行流程
SpringApplication的run方法的主要流程如下:
1) 如果我们使用的是SpringApplication的静态run方法,那么,这个方法里面首先要创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例方法。在SpringApplication实例初始化的时候,它会提前做几件事情:
- 根据classpath里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用使用的ApplicationContext类型。
- 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。
- 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。
- 推断并设置main方法的定义类。
2) SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑了,方法执行伊始,首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener。调用它们的started()方法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!”。
3) 创建并配置当前Spring Boot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)。
4) 遍历调用所有SpringApplicationRunListener的environmentPrepared()的方法,告诉他们:“当前SpringBoot应用使用的Environment准备好了咯!”。
5) 如果SpringApplication的showBanner属性被设置为true,则打印banner。
6) 根据用户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成,然后根据条件决定是否添加ShutdownHook,决定是否使用自定义的BeanNameGenerator,决定是否使用自定义的ResourceLoader,当然,最重要的,将之前准备好的Environment设置给创建好的ApplicationContext使用。
7) ApplicationContext创建好之后,SpringApplication会再次借助Spring-FactoriesLoader,查找并加载classpath中所有可用的ApplicationContext-Initializer,然后遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理。
8) 遍历调用所有SpringApplicationRunListener的contextPrepared()方法。
9) 最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。
10) 遍历调用所有SpringApplicationRunListener的contextLoaded()方法。
11) 调用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。
12) 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。
13) 正常情况下,遍历执行SpringApplicationRunListener的finished()方法、(如果整个过程出现异常,则依然调用所有SpringApplicationRunListener的finished()方法,只不过这种情况下会将异常信息一并传入处理)
去除事件通知点后,整个流程如下:
评论区