引言
在java spring cloud项目中,我们常常会在子模块中创建公共方法,那么在另外一个子模块中,需要加载配置文件的时候,往往Spring Boot 自动扫描包的时候,只会扫描自己模块下的类。这个是springboot约定俗成的内容。
抛出问题
如果想要被Spring容器管理的Bean的路径不再Spring Boot 的包扫描路径下,怎么办呢?也就是如何去加载第三方的Bean 呢?
目前较通用的方式有2种,一是使用注解进行实例化,而是使用spring.factories机制。
这里我们使用Swagger的配置来做实验。
首先一个Swagger的配置类:SwaggerConfig,工程结构如下:

发现我的SwaggerConfig 类和 SpringBoot 的启动类ConfigApplication.java 不在同一级目录下,按照springboot启动机制,以当Spring Boot 自动扫描包的时候,是扫描不到我的SwaggerConfig 的配置的,也就在控制台没有Swagger的打印的信息:

所以这时候我如果想要把SwaggerConfig 加载到Spring容器中的话 要怎么办呢?下面介绍上面提到的2种方式:
方法解决
- 方法一:在Spring Boot Application 主类上使用@Import注解。

- 方法二:创建spring.factories文件
现在我们将其改造一下,采用spring.factories 的方式去加载SwaggerConfig类,在resources目录下新建一个META-INF 的目录,然后在
新建一个spring.factories 的文件,里面的内容为:
1 2 3
| # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.sg.config.Swagger2Config
|
然后在把Spring Boot 启动类上的@Import注释掉,启动发现也可以把SwaggerConfig加载到Spring 容器中。
到这就完成了加载一个Spring 不能扫描到的一个类,他可以是第三方的,也可以是自己写的,只要是Spring Boot 默认扫描路径不能够扫描到,都可以使用这种方式去加载。
内部原理机制
Spring.Factories这种机制实际上是仿照java中的SPI扩展机制实现的。
什么是SPI 机制
SPI 的全名为 Service Provider Interface.这个是是针对厂商或者插件的。在java.util.ServiceLoader 的文档里有比较详细的介绍。
简单总结下Java SPI机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如 日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及了具体的实现类,就违反了可插拔的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
Java SPI 就是提供这样的一种机制:为某个接口寻找服务的实现的机制,有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制很重要。
Spring Boot 中的SPI 机制
在Spring boot 中也有一种类似的加载机制,它在
META-INFO/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。
这种自定义的SPI 机制就是Spring Boot Starter 实现的基础。
Spring Factories实现原理
spring -core 包里定义了SpringFactoriesLoader 类,这个类实现了检索META-INF/spring.factories文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:
loadFactories 根据接口类获取其实现类的实例,这个方法返回的是对象列表
loadFactoryNames 根据接口获取其接口类的名称,这个方法返回的是类名的列表。
上面两个方法的关键都是从指定的ClassLoader中获取spring.factories文件,并解析得到类名列表,具体代码如下:

从代码中可以看到,在这个方法中会遍历整个ClassLoader 中所有Jar包下的spring.factories文件,也就是我们可以在自己jar中配置spring.factories文件,不会影响到其他地方的配置,也不回被别人的配置覆盖。
spring.factories的是通过Properties解析得到的,所以我们在写文件中的内容都是按照下面这种方式配置的。
1 2 3
| # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.sg.config.Swagger2Config
|
如果一个接口希望配置多个实现类,可以用”,”分割。
spring-boot包中的spring.factories文件
spring-boot包中的spring.factories文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.env.PropertiesPropertySourceLoader,\ org.springframework.boot.env.YamlPropertySourceLoader
org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener
org.springframework.boot.SpringBootExceptionReporter=\ org.springframework.boot.diagnostics.FailureAnalyzers
org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
org.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.ConfigFileApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\ org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor
org.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer
org.springframework.boot.diagnostics.FailureAnalysisReporter=\ org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
|
在日常工作中,我们可能需要实现一些SDK 或者Sring boot starter 给别人用的时候,我们就可以使用Factories机制,Factories机制可以让SDK或者Stater的使用只需要很少或者不需要进行配置,只需要在服务中引入我们的Jar包就即可。