SpringBoot中的starter是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进starter,应用者只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。starter让我们摆脱了各种依赖库的处理,需要配置各
1 Starter
在开发 SpringBoot 项目的时候,我们常常通过 Maven 导入自动各种依赖,其中很多依赖都是以 xxx-starter 命名的。
像这种 starter 依赖是怎么工作的呢?
2 了解 spring.factories机制
导入一个依赖,我们就可以调用包内的公共类,这是因为公共类可以被异包调用。很多时候我们添加依赖它会自动往我们的主程序注入一些对象或者监听器,这个是怎么做到的?
2.1 不同包路径下的依赖注入
SpringBoot
默认只扫描启动类所在目录里面的对象
而我们导入的依赖是在另外一个包里,SpringBoot
是扫描不到的!
如何让主项目注入(加载)异包对象呢?通常有两种方法:
- 在启动类上加上
@SpringBootApplication
注解,配置scanBasePackages
属性,指定扫描路径。 - 在
resources/META-INF
目录下创建spring.factories
配置文件,在里面配置需要加载的类
2.2 spring.factories 机制
spring.factories
机制是springboot
的核心基础之一,这可以描述为一种 可插拔结构,模仿自java
中的SPI
扩展机制。
spring.factories 实现例子
1.在任意一个项目中新建一个starter
模块(springboot
项目)
导入 springboot 的自动配置依赖,这里我们主要用到它的@Configuration
、@Bean
注解和ApplicationListener
监听器接口
<!-- SpringBoot 自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
2.随便创建一个bean
类
public class User {
private String name;
public void setName(String name) { this.name = name; }
public String getName() { return name; }
}
3.创建一个初始化监听器
一般的starter
会在容器启动时做一些初始化的操作,这里作为演示只打印一句话。
public class ApplicationInitialize implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
System.out.println("应用初始化");
}
}
4.创建配置类
这个配置类就是主项目注入starter
模块依赖的入口,当它扫描到这个配置类的时候就会加载里面的Bean
对象
@Configuration
public class StarterGenericConfig {
@Bean
public User getUser() {
User user = new User();
user.setName("我来自starter模块");
return user;
}
@Bean
public ApplicationInitialize getAppli() {
return new ApplicationInitialize();
}
}
5.创建spring.factories
配置文件
配置类有了,但是因为和主项目不同包启动类它扫描不到,这时我们就要通过spring.factories
机制让它能扫描到这个配置类,完成依赖注入。
先在resource
资源目录下创建META-INF
文件夹,然后创建一个名为spring.factories
的文件
内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.echoo.cloud.nacos.starter.config.StarterGenericConfig
这里需要配置StarterGenericConfig
配置类的全限定名。
把这个项目导入主项目(添加到主项目的pom
依赖中),运行主项目,看看是否注入成功
这就是starter
依赖注入的基本思路,实际可能复杂得多,需要继续摸索。
3 spring.factories 机制的实现源码分析
在 springframework
框架中有这样一个类
package org.springframework.core.io.support;
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
...
/** SpringFactories
* 静态方法, 加载spring.factories文件
* */
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
...
try {
// 通过类加载器加载资源目录下的"META-INF/spring.factories"文件
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 factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
...
}
SpringFactoriesLoader
是Spring
容器初始化时会加载的一个类,而它的静态方法loadSpringFactories()
里会用类加载器去加载资源文件resourece/META-INF/spring.factories
,然后读取里面的配置参数(应该都是待加载Bean
类的映射数据),用集合封装返回Spring
容器,后面应该就是Spring
容器加载对应的Bean
类。
4 程序运行入口run()
前面知道了Spring
容器会加载加载资源文件resourece/META-INF/spring.factories
然后加载里面对应的类,那为什么对应的key
是org.springframework.boot.autoconfigure.EnableAutoConfiguration
?
先说结论:Spring
容器初始化会加载org.springframework.boot.autoconfigure.EnableAutoConfiguration
这个类,完了还会去扫描resourece/META-INF/spring.factories
加载里面的Bean
类,配置文件是键值对形式的,那key
用org.springframework.boot.autoconfigure.EnableAutoConfiguration
是因为这个是一个注解,本身就是为了注入拓展类用的,它会在容器初始化或刷新的适当时机注入对应的类。因为扫描不到异包配置类上的@Configuration
注解,所以创建了一个@EnableAutoConfiguration
注解配合spring.factories
配置文件的形式来注入配置类
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); // 创建stopWatch对象
stopWatch.start(); // 开始计算时间
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty(); // 是否使用模拟输入输出设备(默认是,因为服务器不一定有鼠标键盘显示器)
SpringApplicationRunListeners listeners = this.getRunListeners(args); // 获取并启动监听器
listeners.starting(); // 获取的监听器为 Event PublishingRunListener,监听并发布启动事件
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备应用环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment); // 打印 banner
context = this.createApplicationContext(); 创建容器
// 加载 SpringFactories 实例(返回的是实例加载的记录、报告)
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
// 准备上下文环境
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context); // 刷新容器
this.afterRefresh(context, applicationArguments); // 容器刷新后的动作,这里默认没有做任何实现
stopWatch.stop(); // 停止计算时间
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
...
}
...
}
里面有个创建应用容器的方法createApplicationContext()
,深入进去发现他是根据webApplicationType
类型去决定创建那种容器,而webApplicationType
类型在SpringApplication
初始化的时候指定。
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch(this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
// 反射创建容器实例
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
这里一共有三种类型的容器
SERVLET
类型创建AnnotationConfigServletWebServerApplicationContext
(Servlet
容器)REACTIVE
类型创建AnnotationConfigReactiveWebServerApplicationContext
(Reactive
容器)- 默认创建
AnnotationConfigApplicationContext
(Application
容器)
SpringApplication
的构造器中对webApplicationType
类型进行了初始化,默认返回SERVLET
类型。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
...
this.webApplicationType = WebApplicationType.deduceFromClasspath();
...
}
也就是说默认创建AnnotationConfigServletWebServerApplicationContext
(Servlet
容器)
在上面入口方法run()
方法中,有一个refreshContext()
方式,这个刷新容器的方法里面
跟踪这个refreshContext()
底层是一个refresh()
方法,三种容器都分别实现了这个方法
这里着重看ServletWebServerApplicationContext.refresh()
public final void refresh() throws BeansException, IllegalStateException {
try { super.refresh(); } catch (RuntimeException var3) { ... }
}
发现它调用的是父类AbstractApplicationContext
的refresh()
函数
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) { // 上锁,防止并发
this.prepareRefresh(); // 刷新准备工作,记录开始时间,校验配置文件
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); // 获取Bean工厂
this.prepareBeanFactory(beanFactory); // Bean工厂准备工作,不详谈
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory); // Spring拓展点之一
...
} catch (BeansException var9) {
...
} finally {
this.resetCommonCaches();
}
}
}
重点在invokeBeanFactoryPostProcessors(beanFactory)
方法上,这是SpringBoot
实现Spring
拓展的关键节点,这个方法执行时会调用实现了BeanFactoryPostProcessors
接口的实现类的postProcessBeanFactory(factory)
方法
(也会调用BeanDefinitionRegistryPostProcessor
接口的各个实现类的postProcessBeanDefinitionRegistry(registry)
方法)
进入invokeBeanFactoryPostProcessors(beanFactory)
方法
public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
...
currentRegistryProcessors = new ArrayList();
// 获取所有 BeanDefinitionRegistryPostProcessor 接口实现类的全限定名集合
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
String[] var16 = postProcessorNames;
var9 = postProcessorNames.length;
int var10;
String ppName;
for(var10 = 0; var10 < var9; ++var10) {
ppName = var16[var10];
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
// 根据 Bean 名获取 Bean 对象放入 currentRegistryProcessors
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory); // 排序,暂时没看,不知道排啥
registryProcessors.addAll(currentRegistryProcessors);
// 调用 Bean 定义注册后处理器
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
...
}
每一个Bean
类的定义注册是在Spring
容器中完成的,在上面invokeBeanFactoryPostProcessors()
方法中,通过 Bean 工厂获取 了所有BeanDefinitionRegistryPostProcessor
接口的实现类名,然后再通过 invokeBeanDefinitionRegistryPostProcessors()
方法调用所有实现类的postProcessBeanDefinitionRegistry()
方法去做 Bean 类注册后的相关处理动作。
BeanDefinitionRegistryPostProcessor
接口:
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}
知道这个有什么用呢?前面我们知道了Springboot
容器中的AnnotationConfigServletWebServerApplicationContext
(Servlet
容器)是通过反射获取AnnotationConfigServletWebServerApplicationContext
的构造器创建实例的,所以我们看看AnnotationConfigServletWebServerApplicationContext
的构造器长什么样儿。
public AnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
super(beanFactory);
this.annotatedClasses = new LinkedHashSet();
this.reader = new AnnotatedBeanDefinitionReader(this); // 注解 Bean 定义读取器
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
进入AnnotatedBeanDefinitionReader
看它的构造器
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
...
// 通过 AnnotationConfigUtils 工具注册 注解配置处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
再进入AnnotationConfigUtils
工具的registerAnnotationConfigProcessors()
看看它是如何注册的
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {
...
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet(8); // 存储 BeanDefinitionHolder 对象的集合
RootBeanDefinition def;
if (!registry.containsBeanDefinition("org.springframework.context.annotation.internalConfigurationAnnotationProcessor")) {
// 创建一个 ConfigurationClassPostProcessor 的 RootBeanDefinition 对象
def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
// 把 ConfigurationClassPostProcessor 的 RootBeanDefinition 对象装入一个 BeanDefinitionHolder (容器)
// 并映射名字为 org.springframework.context.annotation.internalConfigurationAnnotationProcessor
beanDefs.add(registerPostProcessor(registry, def, "org.springframework.context.annotation.internalConfigurationAnnotationProcessor"));
}
...
}
在它的registerAnnotationConfigProcessors()
方法中看到了它用注册后处理器registerPostProcessor
去注册org.springframework.context.annotation.internalConfigurationAnnotationProcessor
的BeanDefinition
(Bean定义描述对象)和BeanDefinitionHolder
(Bean定义描述对象容器),然后返回这个BeanDefinitionHolder
(Bean定义描述对象容器)存储到beanDefs
(Bean定义描述对象容器集合)里面。
到这里就是说明在初始化AnnotationConfigServletWebServerApplicationContext
(Servlet
容器)时,会用org.springframework.context.annotation.internalConfigurationAnnotationProcessor
这个名字注册ConfigurationClassPostProcessor
这个 Bean
对象,然后就能根据它的BeanDefinitionHolder
(Bean定义描述对象容器)去创建ConfigurationClassPostProcessor
对象。
现在问题就来到了ConfigurationAnnotationProcessor
对象身上了,为啥要创建它?因为它就是加载spring.factories
配置文件的关键。
进入它的postProcessBeanDefinitionRegistry()
方法
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) { // 判断是否有对应的注册记录
throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
} else if (this.factoriesPostProcessed.contains(registryId)) { // 是否有对应的 Bean 工厂
throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + registry);
} else { // 都没有,说明这个 BeanDefinition 没有注册加载过
this.registriesPostProcessed.add(registryId); // 添加注册记录
this.processConfigBeanDefinitions(registry); // 处理这个 BeanDefinition 的配置
}
}
深入processConfigBeanDefinitions()
看它怎么处理这个 BeanDefinition
的配置
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList(); // 待处理配置集合
String[] candidateNames = registry.getBeanDefinitionNames();
String[] var4 = candidateNames;
int var5 = candidateNames.length;
for(int var6 = 0; var6 < var5; ++var6) {
String beanName = var4[var6];
BeanDefinition beanDef = registry.getBeanDefinition(beanName); // 根据名称获取 BeanDefinition
// 判断这个 BeanDefinition 的配置属性是不是空
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (this.logger.isDebugEnabled()) {
// 如果不是空就说明这个 BeanDefinition 已经被当作配置类处理过了
this.logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
} else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
// 如果为空,放入待处理配置集合里等待后续处理
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 指令排序相关,不深究
if (!configCandidates.isEmpty()) {
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
...
// 根据环境创建了一个配置类解析器
ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet(configCandidates);
HashSet alreadyParsed = new HashSet(configCandidates.size());
do {
parser.parse(candidates); // 解析配置类
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
}
...
} while(!candidates.isEmpty())
...
}
}
进入ConfigurationClassParser.parse()
方法,看看它怎么解析配置类
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try { // 对 BeanDefinition 做解析操作
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
// 延迟导入选择器
this.deferredImportSelectorHandler.process();
}
解析发现不了什么线索,进入this.deferredImportSelectorHandler.process()
延迟导入选择处理器看看
层层追踪:
this.deferredImportSelectorHandler.process()
↓
DeferredImportSelectorGroupingHandler.processGroupImports()
↓
grouping.getImports()
↓
grouping.getImports()
↓
this.group.selectImports()
追踪到public interface ImportSelector {...}
接口
在找到它的实现类AutoConfigurationImportSelector
在实现类AutoConfigurationImportSelector
里层层追踪
selectImports()
→
getAutoConfigurationEntry()
→
getCandidateConfigurations()
→
getSpringFactoriesLoaderFactoryClass()
最后追踪到 getSpringFactoriesLoaderFactoryClass()
方法
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
里面返回的是一个EnableAutoConfiguration.class
类,这个类就是我们在spring.factories
配置文件里面配置的org.springframework.boot.autoconfigure.EnableAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.echoo.cloud.nacos.starter.config.StarterGenericConfig
到此这篇关于SpringBoot Starter依赖原理与实例详解的文章就介绍到这了,更多相关SpringBoot Starter依赖内容请搜索编程学习网以前的文章希望大家以后多多支持编程学习网!
本文标题为:SpringBoot Starter依赖原理与实例详解
基础教程推荐
- Java实现查找文件和替换文件内容 2023-04-06
- ConditionalOnProperty配置swagger不生效问题及解决 2023-01-02
- Java数据结构之对象比较详解 2023-03-07
- JDK数组阻塞队列源码深入分析总结 2023-04-18
- Java文件管理操作的知识点整理 2023-05-19
- Java实现线程插队的示例代码 2022-09-03
- Java并发编程进阶之线程控制篇 2023-03-07
- java基础知识之FileInputStream流的使用 2023-08-11
- java实现多人聊天系统 2023-05-19
- springboot自定义starter方法及注解实例 2023-03-31