springboot⾃动配置原理以及spring.factories⽂件的作⽤详解⽬录
⼀、springboot ⾃动配置原理
⼆、spring.factories⽂件的作⽤
spring.factories 的妙⽤
什么是 SPI 机制?
Spring Boot 中的 SPI 机制
Spring Factories 实现原理是什么?
Spring Factories 在 Spring Boot 中的应⽤
⼀、springboot ⾃动配置原理
先说说我们⾃⼰的应⽤程序中Bean加⼊容器的办法:
unicom.dc.dingdingcontractapp;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author jinye.Bai
*/
@SpringBootApplication(
scanBasePackages ={"unicom.dc.dingdingcontractapp"}
)
public class DingdingContractAppApplication {
public static void main(String[] args) {
SpringApplication.run(DingdingContractAppApplication.class, args);
}
}
我们在应⽤程序的⼊⼝设置了 @SpringBootApplication标签,默认情况下他会扫描所有次级⽬录。
如果增加了 scanBasePackages属性,就会扫描所有被指定的路径及其次级⽬录。
那么它在扫描的是什么东西呢?
是这个:@Component
所有被扫描到的 @Component,都会成为⼀个默认的singleton(单例,即⼀个容器⾥只有⼀个对象实体)加⼊到容器中。
认识到以上这点,便于我们理解springboot⾃动配置的机制。
接下来让我们看看在⾃⼰的应⽤程序中实现配置的⽅法。
如图:
unicom.figuration;
import t.annotation.Bean;
import t.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author: jinye.Bai
* @date: 2020/5/22 15:51
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
这⾥我们设置了⼀个配置,往容器中加⼊了⼀个RestTemplate。
⾸先说 @Configuration,这个标签继承了 @Component标签,我们可以在标签内容看到:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package t.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import annotation.AliasFor;
import org.springframework.stereotype.Component;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
可以看到其中是有 @Component标签的,所以,@Configuration会被 @SpringBootApplication扫描到,进⽽把它和它下⾯的 @Bean加⼊容器,于是我们 RestTemplate的内容就配置完成了,在后续的使⽤中,我们就可以直接从容器中拿出RestTemplate使⽤它。
对于在maven中引⽤的其他外部包加⼊容器的过程,需要⽤到spring.factories。
⼆、spring.factories⽂件的作⽤
在springboot运⾏时,SpringFactoriesLoader 类会去寻
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
我们以mybatis-plus为例。
⾸先我们引⼊:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
然后去maven的依赖⾥看它的⾃动配置类MybatisPlusAutoConfiguration
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisPlusProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {
可以看到有上⽂提到的 @Configuration,还有从l载⼊⾃动配置的 @EnableConfigurationProperties({MybatisPlusProperties.class})
这个注解的具体内容请查看我另⼀篇博⽂,对其进⾏了解释:
也就是说,springboot只要能扫描到MybatisPlusAutoConfiguration类的 @Configuration注解,其中的所有配置就能⾃动加⼊到容器中,这⼀过程由上⾯提到的SpringFactoriesLoader 起作⽤,它会去寻 “META-INF/spring.factories” ⽂件,我们可以在 mybatis-plus的依赖中到它:
SpringFactoriesLoader为什么要读取它呢?因为它内部是这样的
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
batisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
batisplus.autoconfigure.MybatisPlusAutoConfiguration
spring.factories⽤键值对的⽅式记录了所有需要加⼊容器的类,EnableAutoConfigurationImportSelector的selectImports⽅法返回的类名,来⾃spring.factories⽂件内的配置信息,这些配置信息的key等于EnableAutoConfiguration,因为spring boot应⽤启动时使⽤了EnableAutoConfiguration注解,所以EnableAutoConfiguration注解通过import注解将EnableAutoConfigurationImportSelector类实例化,并且将其selectImports⽅法返回的类名实例化后注册到spring容器。
以上内容是springboot获得这些类的⽅式,如果你想要实现⾃⼰的⾃动配置,就将你的类通过键值对的⽅式写在你的spring.factories即可,注意,值是你的⾃动配置类,键必须是org.springframework.boot.autoconfigure.EnableAutoConfiguration
spring.factories 的妙⽤
现象
在阅读 Spring-Boot 相关源码时,常常见到 spring.factories ⽂件,⾥⾯写了⾃动配置(AutoConfiguration)相关的类名,因此产⽣了⼀个疑问:“明明⾃动配置的类已经打上了 @Configuration 的注解,为什么还要写 spring.factories ⽂件?
⽤过 Spring Boot 的都知道
@ComponentScan 注解的作⽤是扫描 @SpringBootApplication 所在的 Application 类所在的包(basepackage)下所有的 @component 注解(或拓展了 @component 的注解)标记的 bean,并注册到 spring 容器中。
那么问题来了
在 Spring Boot 项⽬中,如果你想要被 Spring 容器管理的 bean 不在 Spring Boot 包扫描路径下,怎么办?
解决 Spring Boot 中不能被默认路径扫描的配置类的⽅式,有 2 种:
(1)在 Spring Boot 主类上使⽤ @Import 注解
(2)使⽤ spring.factories ⽂件
以下是对使⽤ spring.factories ⽂件的简单理解
Spring Boot 的扩展机制之 Spring Factories
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 中也有⼀种类似与 Java SPI 的加载机制。它在 resources/META-INF/spring.factories ⽂件中配置接⼝的实现类名称,然后在程序中读取这些配置⽂件并实例化。
在 Spring 中也有⼀种类似与 Java SPI 的加载机制。它在 resources/META-INF/spring.factories ⽂件
中配置接⼝的实现类名称,然后在程序中读取这些配置⽂件并实例化。
这种⾃定义的SPI机制是 Spring Boot Starter 实现的基础。
Spring Factories 实现原理是什么?
spring-core 包⾥定义了 SpringFactoriesLoader 类,这个类实现了检索 META-INF/spring.factories ⽂件,并获取指定接⼝的配置的功能。在这个类中定义了两个对外的⽅法:
loadFactories 根据接⼝类获取其实现类的实例,这个⽅法返回的是对象列表。 loadFactoryNames 根据接⼝获取其接⼝类的名称,这个⽅法返回的是类名的列表。
上⾯的两个⽅法的关键都是从指定的 ClassLoader 中获取 spring.factories ⽂件,并解析得到类名列表,具体代码如下
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
spring ioc注解private static final Log logger = Log(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
private SpringFactoriesLoader() {}
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = ClassLoader();
}
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
}
List<T> result = new ArrayList(factoryNames.size());
Iterator var5 = factoryNames.iterator();
while(var5.hasNext()) {
String factoryName = (();
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = Name();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, ptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = ((classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? Resources("META-INF/spring.factories") : SystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (Element();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = Set().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (();
String factoryClassName = ((Key()).trim();
String[] var9 = StringUtilsmaDelimitedListToStringArray((Value());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, im());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
try {
Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
if (!factoryClass.isAssignableFrom(instanceClass)) {
throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + Name() + "]");
} else {
return ReflectionUtils.accessibleConstructor(instanceClass, new Class[0]).newInstance();
}
} catch (Throwable var4) {
throw new IllegalArgumentException("Unable to instantiate factory class: " + Name(), var4);
}
}
}
从代码中我们可以知道,在这个⽅法中会遍历整个 spring-boot 项⽬的 classpath 下 ClassLoader 中所有 jar 包下的 spring.factories⽂件。也就是说我
们可以在⾃⼰的 jar 中配置 spring.factories ⽂件,不会影响到其它地⽅的配置,也不会被别⼈的配置覆盖。
Spring Factories 在 Spring Boot 中的应⽤
在 Spring Boot 的很多包中都能够到 spring.factories ⽂件,接下来我们以 spring-boot-autoconfigure 包为例进⾏介绍
# Initializers
t.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
t.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.dition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.dition.OnBeanCondition,\
org.springframework.dition.OnClassCondition,\
org.springframework.dition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
结合前⾯的内容,可以看出 spring.factories ⽂件可以将 spring-boot 项⽬包以外的 bean(即在 pom ⽂件中添加依赖中的 bean)注册到 spring-boot 项⽬的 spring 容器。
由于@ComponentScan 注解只能扫描 spring-boot 项⽬包内的 bean 并注册到 spring 容器中,因此需要 @EnableAutoConfiguration 注解来注册项⽬包外的bean。
⽽ spring.factories ⽂件,则是⽤来记录项⽬包外需要注册的bean类名。
以上为个⼈经验,希望能给⼤家⼀个参考,也希望⼤家多多⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论