从@SpringBootApplication注解入手
为了揭开SpringBoot的奥秘,我们直接从Annotation入手,看看@SpringBootApplication里面,做了什么?
打开@SpringBootApplication这个注解,可以看到它实际上是一个复合注解
1 @Target(ElementType.TYPE) 3 @Retention(RetentionPolicy.RUNTIME) 5 @Documented 7 @Inherited 9 @SpringBootConfiguration //实际上是 @Configuration 11 @EnableAutoConfiguration 13 @ComponentScan(excludeFilters = { 15 @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), 17 @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) 19 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方法。
1 public String[] selectImports(AnnotationMetadata annotationMetadata) { 2 if (!this.isEnabled(annotationMetadata)) { 3 return NO_IMPORTS; 4 } else { 5 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); 6 AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); 7 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); 8 } 9 }
定位到 AutoConfigurationMetadataLoader.loadMetadata代码,可以看到加载了"META-INF/spring-autoconfigure-metadata.properties";
1 final class AutoConfigurationMetadataLoader { 2 protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties"; 3 4 private AutoConfigurationMetadataLoader() { 5 } 6 7 public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) { 8 return loadMetadata(classLoader, PATH); 9 }
定位到this.getAutoConfigurationEntry->getCandidateConfigurations,可以看到用到SpringFactoriesLoader;
1 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { 2 List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), 3 getBeanClassLoader()); 4 Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " 5 + "are using a custom packaging, make sure that file is correct."); 6 return configurations; 7 }
本质上来说,其实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"。
1 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { 2 MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); 3 if (result != null) { 4 return result; 5 } else { 6 try { 7 Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); 8 LinkedMultiValueMap result = new LinkedMultiValueMap();
它其实和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 的启动时间。
原文地址:https://www.cnblogs.com/xingxin666/p/11625566.html