梦里寻他千百度 --Springboot 自动配置
还记得曾经为了引入一个框架, 而在 spring 的 xml 文件里面写一大堆的配置或者以注解的形式, 定义一大堆的配置类, 简直不要太繁琐, 稍不注意还很容易出错. 终于有一天, springboot 出现了, 简直就是天使般的存在, 自从用上 springboot, 腰也不疼了, 头发掉的也少了. 这一切都源于 Springboot 自动配置的特性.
Springboot 遵循 "约定优于配置" 的原则, 使用注解对一些常规的配置项做默认配置, 减少或不使用 xml 配置, 让你的项目快速运行起来. Springboot 还为大量的开发常用框架封装了 starter, 如今引入框架只要引入一个 starter, 你就可以使用这个框架, 只需少量的配置甚至是不需要任何配置.
找了两篇文章, 对比在 spring 应用中引入 mybatis 的代码量和复杂度.
Spring 集成 MyBatis 完整示例 https://www.cnblogs.com/best/p/5648740.html
springboot(六): 如何优雅的使用 mybatis
Springboot 自动配置原理
举个可能不是很恰当的例子, SpringBoot 的自动配置原理, 跟餐厅的机制很类似. 以我最近很喜欢的探鱼来说, 如果将 SpringBoot 比喻成探鱼, 把吃饭比做我们的应用, 我们来到探鱼吃饭的时候 (相当于在应用中加入了 @SpringBootApplication), 服务员会引导我们开始在菜单点餐纸上点餐 (菜单点餐纸是预先定义好的, 就相当于 spring.factories 文件, 预先定义了我们可以使用的自动配置信息), 探鱼既可以自行搭配烤鱼口味, 也可以直接点店家为我们搭配好的口味 (springboot 也是如此, 比如消息中间件, 就有好多种口味可以选, 比如 rabbitmq,kafka, 根据业务场景而定), 我们在喜欢的菜上进行勾选 (相当于在 pom 文件中引入所需框架的 starter), 然后确定下单 (启动 springboot 应用). 我很喜欢吃花菜, 可惜探鱼没有这道辅菜, 但是我们可以自己准备然后带过去啊, 烤鱼上了就加进去煮, 真是骚操作 (这就是加入自定义的自动配置了, 这一步比较麻烦, 需要自行封装 starter).
这么好用的东西, 到底 springboot 是怎么实现的, 一直十分好奇, 今天就跟着源码一步一步的来了解一下.
- @SpringBootApplication
- public class MyApplication {
- public static void main(String[] args) {SpringApplication.run(MyApplication .class, args);}
- }
我们看到, MyApplication 作为入口类, 入口类中有一个 main 方法, 这个方法其实就是一个标准的 Java 应用的入口方法, 一般在 main 方法中使用 SpringApplication.run() 来启动整个应用. 值得注意的是, 这个入口类要使用 @SpringBootApplication 注解声明, 它是 SpringBoot 的核心注解.
- @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 {
- // 略
- }
这个注解里面, 最主要的就是 @EnableAutoConfiguration, 这么直白的名字, 一看就知道它要开启自动配置, SpringBoot 要开始骚了, 于是默默进去 @EnableAutoConfiguration 的源码.
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Inherited
- @AutoConfigurationPackage
- @Import(EnableAutoConfigurationImportSelector.class)
- public @interface EnableAutoConfiguration {
- // 略
- }
可以看到, 在 @EnableAutoConfiguration 注解内使用到了 @import 注解来完成导入配置的功能, 而 EnableAutoConfigurationImportSelector 内部则是使用了 SpringFactoriesLoader.loadFactoryNames 方法进行扫描具有 META-INF/spring.factories 文件的 jar 包. 下面是 1.5.8.RELEASE 实现源码:
- @Override
- public String[] selectImports(AnnotationMetadata annotationMetadata) {
- if (!isEnabled(annotationMetadata)) {
- return NO_IMPORTS;
- }
- try {
- AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
- .loadMetadata(this.beanClassLoader);
- AnnotationAttributes attributes = getAttributes(annotationMetadata);
- // 扫描具有 META-INF/spring.factories 文件的 jar 包
- List<String> configurations = getCandidateConfigurations(annotationMetadata,
- attributes);
- // 去重
- configurations = removeDuplicates(configurations);
- // 排序
- configurations = sort(configurations, autoConfigurationMetadata);
- // 删除需要排除的类
- Set<String> exclusions = getExclusions(annotationMetadata, attributes);
- checkExcludedClasses(configurations, exclusions);
- configurations.removeAll(exclusions);
- configurations = filter(configurations, autoConfigurationMetadata);
- fireAutoConfigurationImportEvents(configurations, exclusions);
- return configurations.toArray(new String[configurations.size()]);
- }
- catch (IOException ex) {
- throw new IllegalStateException(ex);
- }
- }
- // 主要实现
- 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;
- }
任何一个 springboot 应用, 都会引入 spring-boot-autoconfigure, 而 spring.factories 文件就在该包下面. spring.factories 文件是 Key=Value 形式, 多个 Value 时使用, 隔开, 该文件中定义了关于初始化, 监听器等信息, 而真正使自动配置生效的 key 是 org.springframework.boot.autoconfigure.EnableAutoConfiguration, 如下所示:
- # Auto Configure
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
- org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
- org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
- org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
- org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
- org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
- org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
- .......
- org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
上面的 EnableAutoConfiguration 配置了多个类, 这些都是 Spring Boot 中的自动配置相关类; 在启动过程中会解析对应类配置信息. 每个 Configuation 类都定义了相关 bean 的实例化配置. 都说明了哪些 bean 可以被自动配置, 什么条件下可以自动配置, 并把这些 bean 实例化出来. 如果我们自定义了一个 starter 的话, 也要在该 starter 的 jar 包中提供 spring.factories 文件, 并且为其配置 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的配置类. 所有框架的自动配置流程基本都是一样的, 判断是否引入框架, 获取配置参数, 根据配置参数初始化框架相应组件. 下面的初始化数据源的部分源码:
- @Configuration
- @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
- @EnableConfigurationProperties(DataSourceProperties.class)
- @Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
- public class DataSourceAutoConfiguration {
- private static final Log logger = LogFactory
- .getLog(DataSourceAutoConfiguration.class);
- @Bean
- @ConditionalOnMissingBean
- public DataSourceInitializer dataSourceInitializer(DataSourceProperties properties,
- ApplicationContext applicationContext) {
- return new DataSourceInitializer(properties, applicationContext);
- }
- // 略
- }
我们可以看到自动化配置代码中有很多我们之前没有用到的注解配置, 下面一一介绍.
@Configuration: 这个配置就不用多做解释了, 我们一直在使用
@EnableConfigurationProperties: 这是一个开启使用配置参数的注解, value 值就是我们配置实体参数映射的 ClassType, 将配置实体作为配置来源.
以下为 SpringBoot 内置条件注解:
@ConditionalOnBean: 当 SpringIoc 容器内存在指定 Bean 的条件
@ConditionalOnClass: 当 SpringIoc 容器内存在指定 Class 的条件
@ConditionalOnExpression: 基于 SpEL 表达式作为判断条件
@ConditionalOnJava: 基于 JVM 版本作为判断条件
@ConditionalOnJndi: 在 JNDI 存在时查找指定的位置
@ConditionalOnMissingBean: 当 SpringIoc 容器内不存在指定 Bean 的条件
@ConditionalOnMissingClass: 当 SpringIoc 容器内不存在指定 Class 的条件
@ConditionalOnNotWebApplication: 当前项目不是 Web 项目的条件
@ConditionalOnProperty: 指定的属性是否有指定的值
@ConditionalOnResource: 类路径是否有指定的值
@ConditionalOnSingleCandidate: 当指定 Bean 在 SpringIoc 容器内只有一个, 或者虽然有多个但是指定首选的 Bean
@ConditionalOnWebApplication: 当前项目是 Web 项目的条件
以上注解都是元注解 @Conditional 演变而来的, 根据不用的条件对应创建以上的具体条件注解.
总结
Springboot 的设计思想以及理念都非常前卫, 多理解多学习, 完全可以在工作中运用起来, Springboot 真是个好东西.
来源: http://www.jianshu.com/p/5901da52ca09