从@SpringBootApplication注解入手
为了揭开SpringBoot的奥秘,我们直接从Annotation入手,看看@SpringBootApplication里面,做了什么?
打开@SpringBootApplication这个注解,可以看到它实际上是一个复合注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration //实际上是 @Configuration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
SpringBootApplication 本质上是由 3 个注解组成,分别是
- @Configuration
- @EnableAutoConfiguration
- @ComponentScan
我们直接用这三个注解也可以启动 springboot 应用,只是每次配置三个注解比较繁琐,所以直接用一个复合注解更方便些。
然后仔细观察这三个注解,除了 @EnableAutoConfiguration 可能稍微陌生一点,其他两个注解使用得都很多。
简单分析@Configuration
@Configuration这个注解大家应该有用过,它是JavaConfig形式的基于Spring IOC容器的配置类使用的一种注解。
因 为 SpringBoot 本质上就是一个 spring 应用,所以通过这 个注解来加载 IOC 容器的配置是很正常的。所以在启动类里面标注了@Configuration,意味着它其实也是一个 IoC 容器的
配置类
。
传统意义上的 spring 应用都是基于xml 形式来配置 bean 的依赖关系,然后在spring 容器启动的时候,把 bean 进行初始化,如果 bean 之间存在依赖关系,则分析这些已经在 IoC 容器中的 bean 根据依赖关系进行组装。
直到 Java5 中,引入了 Annotations 这个特性,Spring 框架也紧随大流并且推出了基于 Java 代码和 Annotation 元 信息的依赖关系绑定描述的方式,也就是 JavaConfig。
从 spring3 开始,spring 就支持了两种 bean 的配置方式,一种是基于 xml 文件方式、另一种就是 JavaConfig。 任何一个标注了@Configuration 的 Java 类定义都是一个 JavaConfig 配置类。
在这个配置类中,任何标注了
@Bean
的方法,它的返回值都会作为 Bean 定义注册到 Spring 的 IOC 容器,方法名默认成为这个 bean 的 id。
简单分析@ComponentScan
@ComponentScan 这个注解是大家接触得最多的了,相当于xml 配置文件中的
<context:component-scan>
。
它的主要作用就是扫描指定路径下的标识了需要装配的类,自动装配到 spring 的 Ioc 容器中。 标识需要装配的类的形式主要是:@Component、 @Repository、@Service、@Controller这类的注解标识的类。
ComponentScan默认会扫描当前 package 下的的所有加了相关注解标识的类到 IoC 容器中;
深入分析@EnableAutoConfiguration
我们把@EnableAutoConfiguration 放在最后讲的目的并不是说它是一个新的东西,只是他对于springboot来说意义重大。
EnableAutoConfiguration的主要作用其实就是帮助springboot应用把所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器中。
Enable并不是新鲜玩意
在spring3.1版本中,提供了一系列@Enable 开头的注解,Enable注解应该是在 JavaConfig 框架上更进一步的完善,用户在使用 spring 相关的框架时,避免配置大量的代码从而降低使用难度。
比如常见的一些Enable注解:@EnableWebMvc,(这个注解引入了MVC框架在Spring应用中需要用到的所有bean); 比如说@EnableScheduling,开启计划任务的支持。
找到 EnableAutoConfiguration,我们可以看到每一个涉及到Enable开头的注解,都会带有一个@Import 的注解。
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
@Import注解
import注解是什么意思呢? 联想到 xml 形式下有一个 形式的注解,就明白它的作用了。 import 就是把多个分来的容器配置合并在一个配置中。在 JavaConfig 中所表达的意义是一样的。
@Import 注解可以配置三种不同的class
- 基于普通bean或者带有@Configuration的bean进行注入;
- 实现 ImportSelector 接口进行动态注入;
- 实现 ImportBeanDefinitionRegistrar 接口进行动态注入;
分析@Import(AutoConfigurationImportSelector.class)
了解ImportSelector和ImportBeanDefinitionRegistrar 后,对于EnableAutoConfiguration的理解就容易一些了,它会通过 import 导入第三方提供的 bean 的配置类: @Import(AutoConfigurationImportSelector.class)
从名字来看,可以猜到它是基于 ImportSelector 来实现基于动态 bean 的加载功能。
之前我们讲过 Springboot @Enable*注解的工作原理 ImportSelector接口selectImports 返回的数组(类的全类名)都会被纳入到spring容器中。 那么可以猜想到这里的实现原理也一定是一样的,定位到
AutoConfigurationImportSelector
这个类中的
selectImports
方法。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
定位到
AutoConfigurationMetadataLoader
.
loadMetadata
代码,可以看到加载了”META-INF/
spring-autoconfigure-metadata.properties
“;
final class AutoConfigurationMetadataLoader {
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";
private AutoConfigurationMetadataLoader() {
}
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}
定位到this.getAutoConfigurationEntry->
getCandidateConfigurations
,可以看到用到
SpringFactoriesLoader
;
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;
}
本质上来说,其实EnableAutoConfiguration会帮助springboot应用把所有符合@Configuration配置都加载到当前SpringBoot创建的IoC容器,而这里面借助了Spring框架提供的一个工具类
SpringFactoriesLoader
的支持,以及用到了Spring提供的条件注解
@Conditional
,选择性的针对需要加载的bean进行条件过滤
SpringFactoriesLoader
SpringFactoriesLoader的作用是从 classpath/META-INF/spring.factories 文件中,根据 key 来加载对应的类到spring IoC容器中。
定位到
SpringFactoriesLoader.loadSpringFactories
源码,可以看到加载了”META-INF/
spring.factories
“。
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 {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
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;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
它其实和java中的SPI机制的原理是一样的,不过它比SPI更好的点在于不会一次性加载所有的类,而是根据 key进行加载。
例如:dubbo-spring-boot-starter-2.0.0.jar!/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.dubbo.spring.boot.DubboAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboProviderAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboConsumerAutoConfiguration
条件过滤Conditional
在分析AutoConfigurationImportSelector的源码时看到,会先扫描 spring-autoconfiguration-metadata.properties 文件,最后再扫描spring.factories对应的类时,会结合前面的元数据进行过滤,为什么要过滤呢? 原因是很多的@Configuration 其实是依托于其他的框架来加载的,如果当前的classpath环境下没有相关联的依赖,则意味着这些类没必要进行加载,所以,通过这种条件过滤可以有效的减少@configuration类的数量从而降低 SpringBoot 的启动时间。
Conditional的注解
Conditions | 描述 |
---|---|
@ConditionalOnBean | 在存在某个 bean 的时候 |
@ConditionalOnMissingBean | 不存在某个 bean 的时候 |
@ConditionalOnClass | 当前 classpath 可以找到某个类型的类时 |
@ConditionalOnMissingClass | 当前 classpath 不可以找到某个类型的类时 |
@ConditionalOnResource | 当前 classpath 是否存在某个资源文件 |
@ConditionalOnProperty | 当前 jvm 是否包含某个系统属性为某个值 |
@ConditionalOnWebApplication | 当前 spring context 是否是 web 应用程序 |
注:本文整理自咕泡学院Mic老师的课堂笔记。