什么是 springboot
对于spring框架,我们接触得比较多的应该是spring mvc和 spring。而 spring 的核心在于 IOC(控制反转)和 DI (依赖注入)。而这些框架在使用的过程中会需要配置大量 的xml,或者需要做很多繁琐的配置。 springboot 框架是为了能够帮助使用 spring 框架的开发者快速高效的构建一个基于 spirng 框架以及 spring 生态 体系的应用解决方案。它是对约定优于配置
这个理念下 的一个最佳实践。因此它是一个服务于框架的框架,服务 的范围是简化配置文件。
约定优于配置的体现
- maven的目录结构
- 默认有resources文件夹存放配置文件
- 默认打包方式为jar
- spring-boot-starter-web 中默认包含 spring mvc 相关依赖以及内置的tomcat容器,使得构建一个web应用 更加简单
- 默认提供application.properties/yml文件
- 默认通过 spring.profiles.active 属性来决定运行环境时读取的配置文件
- EnableAutoConfiguration 默认对于依赖的starter进行自动装载
启动原理
我们开发任何一个Spring Boot项目,都会用到如下的启动类
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
为了揭开 springboot 的奥秘,我们直接从 Annotation 入 手,看看@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 {
.....
}
SpringBootApplication本质上是由3个注解组成,分别是
- @Configuration (@SpringBootConfiguration里面也用的@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
Enable 并不是新鲜玩意
在 spring3.1 版本中,提供了一系列的@Enable 开 头的注解,Enable主机应该是在JavaConfig框架上更进一步的完善,是的用户在使用spring相关的框架是,避免配置大量的代码从而降低使用的难度 。
比如@EnableScheduling、@EnableCaching、@EnableWebMvc等,@EnableAutoConfiguration的理念和做事方式其实一脉相承,简单概括一下就是,借助@Import的支持,收集和注册特定场景相关的bean定义。
- @EnableWebMvc,这个注解引入了MVC 框架在Spring 应用中需要用到的所有 bean
- @EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IoC容器,开启计划任务的支持。
而@EnableAutoConfiguration也是借助@Import的帮助,将所有符合条件的@Configuration 配置都加载到当前SpringBoot创建并使用的IoC容器中。仅此而已!
@EnableAutoConfiguration会根据类路径中的jar依赖为项目进行自动配置,如:添加了spring-boot-starter-web依赖,会自动添加Tomcat和Spring MVC的依赖,Spring Boot会对Tomcat和Spring MVC进行自动配置。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
@Import(AutoConfigurationImportSelector.class)从名字来看,可以猜到它是基于ImportSelector来实现基于动态bean的加载功能。要知道Springboot @Enable*注解的工作原理ImportSelector接口selectImports返回的数组(类的全类名)都会被纳入到 spring容器中。 那么可以猜想到这里的实现原理也一定是一样的,定位到 AutoConfigurationImportSelector这个类中的 selectImports方法 。
本质上来说,其实EnableAutoConfiguration会帮助 springboot应用把所有符合@Configuration配置都加载到当前SpringBoot创建的IoC容器,而这里面借助了Spring框架提供的一个工具类SpringFactoriesLoader的支持。以及用到了Spring提供的条件注解 @Conditional,选择性的针对需要加载的bean进行条件过滤
AutoConfigurationImportSelector
我们来看下AutoConfigurationImportSelector源码下的selectImports方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//加载spring-autoconfigure-metadata.properties配置文件中的数据
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
//通过getAutoConfigurationEntry获取需要动态加载的class
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
//返回需要交给spring进行注入的类
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
AutoConfigurationMetadataLoader 的源码:
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);
}
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())));
}
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
上述selectImports方法就返回了需要springboot自动装配的一些bean,通过String[]的形式返回需要装配的bean的name,但是这个方法的在真正返回需要装配的bean的name之前,还做了很多操作。做了些动态过滤的操作。
第一步是通过loadMetadata加载当前classpath下的spring-autoconfigure-metadata.properties文件,这个文件里面配置了所有动态加载的条件。第二步是通过getAutoConfigurationEntry获取需要动态加载的class,这一步具体源码如下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//下面这一步是去加载classpath下的spring.factories文件中的实例
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
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;
}
这里SpringFactoriesLoader 的作用就是从classpath/META-INF/spring.factories文件中,根据key来 加载对应的类到spring IoC容器中。
可以看出这里就是加载当前classpath下的所有的spring.factories文件中的内容。下面就是spring.factories中EnableAutoConfiguration的配置。这些如果没有AutoConfigurationImportSelector的过滤操作,这里所有配置的值,都会在getCandidateConfigurations方法中会返回给IOC容器,springboot会自动装载这些类。
@EnableAutoConfiguration作用就是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。这些功能配置类要生效的话,会去classpath中找是否有该类的依赖类(也就是pom.xml必须有对应功能的jar包才行)并且配置类里面注入了默认属性值类,功能类可以引用并赋默认值。生成功能类的原则是自定义优先,没有自定义时才会使用自动装配类。