文章目录

  • @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相关推荐

  1. 【读官方文档,学原味技术】SpringBoot-Staters和自定义Starter

    spring-boot-reference 如果不想阅读英文原文,你可以直接读本文的[TS]标注参考翻译内容.由于本人水平有限,如有理解错误,烦请指正,互相交流. Lire les documents ...

  2. SpringBoot加载spring.factories的价值

    SpringBoot加载spring.factories的价值 在springboot的各个依赖包下,我们经常看到META-INF/spring.factories这个文件.spring.factor ...

  3. Spring Boot spring.factories 用法及原理

    文章目录 1. spring.factories 用法 2. spring.factories 实现原理 3. spring.factories 用于解决什么问题? 3.1 业务场景思考及 start ...

  4. Spring Boot 如何自定义Starter,你知道吗?

    原文:https://shimo.im/docs/ywxu41XEYxYXYy3U/read 如果我们系统中想要拥有自动配置的功能,可以自己编写一个starter (启动器),想想就觉得很酷,因为这意 ...

  5. spring boot @value取不到值_看看大厂如何自定义starter,还真想不到

    我自己整理了一套java架构资料,还有大厂面试题,需要私信回复"资料"就好了,无偿分享,希望对你有帮助. 背景 使用过Spring Boot的小伙伴都应该知道,一个Spring B ...

  6. Spring Boot 自动配置的原理、核心注解以及利用自动配置实现了自定义 Starter 组件

    本章内容 自定义属性快速入门 外化配置 自动配置 自定义创建 Starter 组件 摘录:读书是读完这些文字还要好好用心去想想,写书也一样,做任何事也一样 图 2 第二章目录结构图 第 2 章 Spr ...

  7. Spring Boot:自定义starter

    来源:阿杜的世界 号外:最近整理了一下以前编写的一系列Spring Boot内容,整了个<Spring Boot基础教程>的PDF,关注我,回复:001,快来领取吧-!更多内容持续整理中, ...

  8. Spring Boot : 自定义 Starter

    1.美图 2.概述 因为spring boot 已经自定义了很多starter,但是任然不能涵盖所有的开发场景,所以有时候就需要我们自己自定义starter. 自定义starter需要思考的问题 : ...

  9. spring.factories加载原理以及自定义EnvironmentPostProcessor

    目录 spring.factories加载原理 1. SpringApplication的构造方法 1.1 SpringApplication#getSpringFactoriesInstances ...

最新文章

  1. Linux中安装配置hadoop集群
  2. 【[CQOI2018]交错序列】
  3. python用tsne降维图像_python代码实现TSNE降维数据可视化教程
  4. android系统镜像:boot.img kernel.img ramdisk.img system.img userdata.img cache.img recovery.img
  5. bat文件设置dns服务器,.bat文件设置IP、DNS
  6. 用数组选出最大的数并且排序
  7. php header apk,php常用的header头
  8. 面试前准备这些,成功率会大大提升!(Java篇)
  9. php定时任务sw,[原创]Swoole和Swoft的那些事(Task投递/定时任务篇)
  10. 按不同vlan下发dhcp_Cisco三层交换上给不同Vlan配置不同的DHCP
  11. 微信小程序接入腾讯云IM即时通讯(发送消息开发步骤)
  12. servlet制作过滤器
  13. 50.纯 CSS 创作一个永动的牛顿摆
  14. # Vue 配置前端后端路由地址
  15. 阿里云研究中心主任田丰: 如何从实体经济走向智能产业
  16. 电脑开远程的几个方法
  17. tesseract 遇到的一些问题
  18. 【ArcGIS微课1000例】0055:根据图层创建自定义图例符号案例教程
  19. 大学网络安全主题班会PPT模板
  20. ❤️AI从入门到到精通❤️

热门文章

  1. Minio分布式对象存储的部署与使用
  2. wallhaven的热度壁纸爬取(入门级爬虫)——python
  3. Excel被保护了无法复制
  4. 轻量级代码生成器加测试数据生成器
  5. WEB 视频开发系列——千万级流量弹幕
  6. 16进制(00)转字符
  7. 一个会计做账的实际过程
  8. Mac 终端vim进入编辑并退出
  9. 了解数据库的作用、特点及关系型数据库管理系统
  10. 【协议分析】Web QQ登陆验证分析