曾经有一个 Starter 放在你的面前,你没有好好珍惜。今天照常写一篇干货教程。
第一,要有如下的几个问题 :
- 什么是SpringBoot自动装配 ?
- SpringBoot 是如何实现自动装配的 ? 并且按需加载
- 如何自己写一个Starter ?
前言 : 自动装配并不是SpringBoot兴起的,实际上 Spring FrameWork很早就实现了这个功能。SpringBoot在此基础上,通过SPI(Service Provider Interface)的方式做了进一步的优化.
注: SPI的全名为Service Provider Interface.类似 里式替换原则,依赖倒置原则
系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制
什么是SpringBoot自动装配
SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。
没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Spring Boot 中,我们直接引入一个 starter 即可。比如你想要在项目中使用 redis 的话,直接在项目中引入对应的 starter 即可。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
引入了 stater 之后,我们通过少量注解和一些简单的配置就能使用第三方组件提供的功能了.
同时SpringBoot的自动装配也可以理解为 : 通过注解或者一些简单的配置就能在Sprig Boot的帮助下实现某块功能 。
SpringBoot 是如何实现自动装配的?
我们先看一下 SpringBoot 的核心注解 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 可以看做是 @Configuration , @EnableAutoConfiguration, @ComponentScan 注解的集合。
graph TD
SpringBootApplication --> Configuration
SpringBootApplication --> EnableAutoConfiguration
SpringBootApplication --> ComponentScan
- @EnableAutoConfiguration 启用 SpringBoot 的自动配置机制
- @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类
- @ComponentScan:扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。
@EnableAutoConfiguration 是实现自动装配的重要注解,以这个注解往下走
@EnableAutoConfiguration:实现自动装配的核心注解
@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 {};
}
EnableAutoConfiguration 中,重要的核心配置内容在 AutoConfigurationImportSelector中,往下看
AutoConfigurationImportSelector:加载自动装配类
AutoConfigurationImportSelector 类的继承体系,关系图
class_AutoConfigurationImportSelector 实现了 interface_ImportSelector接口,等于实现了 interface_ImportSelector中的 method_selectImports方法,该方法主要用于获取符合条件的类的全限定类名,这些类需要被加载到IOC容器中。
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// <1> 判断自动装配开关是否打开
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// <2> 获取所有需要装配的Bean
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 加载自动配置类
AutoConfigurationEntry autoConfigurationEntry =
getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
需要重点关注一下getAutoConfigurationEntry()方法,这个方法主要负责加载自动配置类的。主要的调用链如下 :
graph TD
AutoConfigurationImportSelector.getAutoConfigurationEntry_获取自动装配数目 --> AutoConfigurationImportSelector.getCandidateConfigurations_获取自动配置类名称 -->
SpringFactoriesLoader.loadFactoryNames_使用给定的类加载器从spring.factories加载给定类型的工厂实现的标准类名 -->
SpringFactoriesLoader.loadFactories_使用给定的类加载器从spring.factories加载并实例化给定类型的工厂实现
注意 :spring.factories中这么多配置,并不是会全部都加载,只有加了@interface ConditionalOnClass,才可以
有兴趣的童鞋可以详细了解下 Spring Boot 提供的条件注解
- @ConditionalOnBean:当容器里有指定 Bean 的条件下
- @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
- @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
- @ConditionalOnClass:当类路径下有指定类的条件下
- @ConditionalOnMissingClass:当类路径下没有指定类的条件下
- @ConditionalOnProperty:指定的属性是否有指定的值
- @ConditionalOnResource:类路径是否有指定的值
- @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
- @ConditionalOnJava:基于 Java 版本作为判断条件
- @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
- @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
- @ConditionalOnWebApplication:当前项目是 Web 项 目的条件下
如何实现一个 Starter ?
通过实现一个线程池的代码,搞起来。
第一步,创建threadpool-spring-boot-starter工程
第二步,引入 Spring Boot 相关依赖
第三步,创建ThreadPoolAutoConfiguration
第四步,在threadpool-spring-boot-starter工程的 resources 包下创建META-INF/spring.factories文件
最后新建工程引入threadpool-spring-boot-starter
测试通过!!!