自定义starter为什么要加上spring.factories
文章目录
- @SpringApplication注解的实现
- 自定义starter里面的类可以由这个注解扫描吗?
- 怎么样可以引入依赖jar包中在项目包外的类?
- 结论
自定义starter为什么要加上spring.factories,这个问题要从@SpringApplication注解的实现开始
@SpringApplication注解的实现
@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 {
排除元注解,先来看看比较熟悉的@ComponentScan注解
@ComponentScan的作用是,扫描指定的basePackages包下的,用@Component及其派生注解标记的类,加入到Spring容器中管理。
注:@Service,@Configuration,@Controller等等都是@Component的派生注解
自定义starter里面的类可以由这个注解扫描吗?
思考一个问题,如果我自定义了一个封装的starter,里面包含一个@Service标记的服务类,它也可以由这个注解进行扫描并注册到Spring容器中吗?
可以做两个测试接口来验证上面的问题
1)自定义一个starter,包名分别是com.zby和com.ttt,下面各有一个Service服务类,并且使用@Service注解标记
service方法一:
package com.zby.service;import org.springframework.stereotype.Service;/*** @author zhengbingyuan* @date 2022/5/25*/
@Service
public class TestImportService {public String test01(){return "TestImportService.test01";}
}
service方法二:
package com.ttt.service;import org.springframework.stereotype.Service;/*** @author zhengbingyuan* @date 2022/5/25*/
@Service
public class TestNotImportService {public String test01(){return "TestNotImportService.test01";}
}
位置:
在另外的工作项目,Application所在包名为com.zby,引入这个自定义的starter,并新建两个测试接口:
注:这里我本身想使用构造器注入的方式的,但是这样启动就会报错,因为构造器注入要求依赖不能为空,所以为了更好的测试,我把注入方式改为了@Autowired注入
/*** 其他测试接口** @author XBird* @date 2022/5/8**/
@Api(tags = "测试接口集合")
@RestController
//@AllArgsConstructor
public class TestController {@Autowiredprivate TestImportService testImportService;@Autowired(required = false)private TestNotImportService testNotImportService;@ApiOperation(value = "测试starter中跟当前项目父包相同的service是否会被加载", notes = "\n开发者:郑炳元")@PostMapping(value = "/testStarterImportService")public JsonResult<String> testStarterImportService() {return JsonResult.success(testImportService.test01());}@ApiOperation(value = "测试starter中跟当前项目父包不同的service是否会被加载", notes = "\n开发者:郑炳元")@PostMapping(value = "/testStarterNotImportService")public JsonResult testStarterNotImportService() {return JsonResult.success(testNotImportService.test01());}
}
结果:
com.zby下的服务类可以引入成功,但是com.ttt下的服务类引入失败
接口一:
接口二:
结论:
如果starter里面的包名是跟工作项目(引入这个starter的项目)的SpringApplication主运行类的所在包是相同的,那么可以成功引入,否则引入失败。
怎么样可以引入依赖jar包中在项目包外的类?
那SpringBoot有办法引入这些不是跟当前工作项目统一包名的封装功能吗?
工作环境下,没办法保证引入的starter的功能和当前工作项目是相同的包,例如我封装了包为com.xxx的starter,其他小组觉得挺好用的,想直接拿过来用。
这就要说到@EnableAutoConfiguration 这个注解了。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
特别注意到两个注解:
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
先来分析@Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector 实现了DeferredImportSelector 接口,DeferredImportSelector 又是继承自ImportSelector接口。
因此,首先知道一点,AutoConfigurationImportSelector 会存在一个selectImports 方法,该方法返回的数组中的类名会注册到Spring容器当中。
@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {//从这句代码可以知道,通过spring.boot.enableautoconfiguration =false//可以禁用自动配置功能if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}
其中,getAutoConfigurationEntry(annotationMetadata) 方法就是获取项目中需要自动配置的配置类
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}AnnotationAttributes attributes = getAttributes(annotationMetadata);//从META-INF/spring.factories获取候选的配置类List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);configurations = removeDuplicates(configurations);Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}
getCandidateConfigurations(annotationMetadata, attributes); 这一步,实际上是获取候选的配置类,这些候选的配置类来源就是从类路径下的META-INF/spring.factories,寻找key=EnableAutoConfiguration.class 对应的value(一个数组),这些数组都是配置类的类名
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;}
loadSpringFactories 方法不仅仅是getCandidateConfigurations方法调用的,其他方法也会调用到,例如springboot启动时加载ApplicationContextInitializer和SpringApplicationRunListener的时候也会访问这个方法,所以这个方法做了缓存
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {//这个应该是第二次执行这个方法的时候,从缓存里面拿的意思//因为spring.factories是静态文件不会改,下面有cache.put(classLoader, result)的操作MultiValueMap<String, String> result = cache.get(classLoader);if (result != null) {return result;}try {//这里FACTORIES_RESOURCE_LOCATION就是META-INF/spring.factoriesEnumeration<URL> urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));result = new LinkedMultiValueMap<>();while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryTypeName = ((String) entry.getKey()).trim();for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {result.add(factoryTypeName, factoryImplementationName.trim());}}}cache.put(classLoader, result);return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}}
spring.factories文件示例:是以K-V形式获取的
结论
回到最初的问题,“为什么要写spring.factories文件?”,
spring.factories是为了能够将SpringBoot工作项目启动类所在包之外的bean(即在pom文件中添加依赖中的bean)注册到spring-boot项目的spring容器中。因为启动类中的@ComponentScan只能扫描启动类所在的包及其子包的bean。因此需要@EnableAutoConfiguration注解来注册项目包外的bean。
但是,如果自定义starter的配置类所在包跟项目包相同,就没必要写spring.factories了
这个,其实也就是回答了@EnableAutoConfiguration注解的原理。
总结一下使用场景:
(1)starter中有配置类
(2)其他项目需要引用到starter,且启动类所在的包级别不同
所以对于上面的问题,com.ttt.TestNotImportService 的情况,可以通过写一个配置类,返回它的bean,然后在META-INF/spring.factories中写一个EnableAutoConfiguration的键值对,就可以成功加载了。
@Configuration
public class TestNotImportConfiguration {@Beanpublic TestNotImportService testNotImportService(){return new TestNotImportService();}
}
然后配置META-INF/spring.factories,注意!注意!注意!千万别漏了META-INF,血的教训(T_T)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ttt.config.TestNotImportConfiguration
放的位置
结果:
可以成功访问了项目包以外的类了
自定义starter为什么要加上spring.factories相关推荐
- 【读官方文档,学原味技术】SpringBoot-Staters和自定义Starter
spring-boot-reference 如果不想阅读英文原文,你可以直接读本文的[TS]标注参考翻译内容.由于本人水平有限,如有理解错误,烦请指正,互相交流. Lire les documents ...
- SpringBoot加载spring.factories的价值
SpringBoot加载spring.factories的价值 在springboot的各个依赖包下,我们经常看到META-INF/spring.factories这个文件.spring.factor ...
- Spring Boot spring.factories 用法及原理
文章目录 1. spring.factories 用法 2. spring.factories 实现原理 3. spring.factories 用于解决什么问题? 3.1 业务场景思考及 start ...
- Spring Boot 如何自定义Starter,你知道吗?
原文:https://shimo.im/docs/ywxu41XEYxYXYy3U/read 如果我们系统中想要拥有自动配置的功能,可以自己编写一个starter (启动器),想想就觉得很酷,因为这意 ...
- spring boot @value取不到值_看看大厂如何自定义starter,还真想不到
我自己整理了一套java架构资料,还有大厂面试题,需要私信回复"资料"就好了,无偿分享,希望对你有帮助. 背景 使用过Spring Boot的小伙伴都应该知道,一个Spring B ...
- Spring Boot 自动配置的原理、核心注解以及利用自动配置实现了自定义 Starter 组件
本章内容 自定义属性快速入门 外化配置 自动配置 自定义创建 Starter 组件 摘录:读书是读完这些文字还要好好用心去想想,写书也一样,做任何事也一样 图 2 第二章目录结构图 第 2 章 Spr ...
- Spring Boot:自定义starter
来源:阿杜的世界 号外:最近整理了一下以前编写的一系列Spring Boot内容,整了个<Spring Boot基础教程>的PDF,关注我,回复:001,快来领取吧-!更多内容持续整理中, ...
- Spring Boot : 自定义 Starter
1.美图 2.概述 因为spring boot 已经自定义了很多starter,但是任然不能涵盖所有的开发场景,所以有时候就需要我们自己自定义starter. 自定义starter需要思考的问题 : ...
- spring.factories加载原理以及自定义EnvironmentPostProcessor
目录 spring.factories加载原理 1. SpringApplication的构造方法 1.1 SpringApplication#getSpringFactoriesInstances ...
最新文章
- Linux中安装配置hadoop集群
- 【[CQOI2018]交错序列】
- python用tsne降维图像_python代码实现TSNE降维数据可视化教程
- android系统镜像:boot.img kernel.img ramdisk.img system.img userdata.img cache.img recovery.img
- bat文件设置dns服务器,.bat文件设置IP、DNS
- 用数组选出最大的数并且排序
- php header apk,php常用的header头
- 面试前准备这些,成功率会大大提升!(Java篇)
- php定时任务sw,[原创]Swoole和Swoft的那些事(Task投递/定时任务篇)
- 按不同vlan下发dhcp_Cisco三层交换上给不同Vlan配置不同的DHCP
- 微信小程序接入腾讯云IM即时通讯(发送消息开发步骤)
- servlet制作过滤器
- 50.纯 CSS 创作一个永动的牛顿摆
- # Vue 配置前端后端路由地址
- 阿里云研究中心主任田丰: 如何从实体经济走向智能产业
- 电脑开远程的几个方法
- tesseract 遇到的一些问题
- 【ArcGIS微课1000例】0055:根据图层创建自定义图例符号案例教程
- 大学网络安全主题班会PPT模板
- ❤️AI从入门到到精通❤️