springboot自动配置原理以及spring.factories文件的作用详解(springboot自动配置了什么)

  本篇文章为你整理了springboot自动配置原理以及spring.factories文件的作用详解(springboot自动配置了什么)的详细内容,包含有springboot factories springboot自动配置了什么 springboot自动配置的原理总结 springboot自定义配置文件如何加载 springboot自动配置原理以及spring.factories文件的作用详解,希望能帮助你了解 springboot自动配置原理以及spring.factories文件的作用详解。

  先说说我们自己的应用程序中Bean加入容器的办法:

  

package com.ynunicom.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 ={"com.ynunicom.dc.dingdingcontractapp"}

  public class DingdingContractAppApplication {

   public static void main(String[] args) {

   SpringApplication.run(DingdingContractAppApplication.class, args);

  }

 

  bean加入容器

  我们在应用程序的入口设置了 @SpringBootApplication标签,默认情况下他会扫描所有次级目录。

  如果增加了 scanBasePackages属性,就会扫描所有被指定的路径及其次级目录。

  那么它在扫描的是什么东西呢?

  是这个:@Component

  所有被扫描到的 @Component,都会成为一个默认的singleton(单例,即一个容器里只有一个对象实体)加入到容器中。

  认识到以上这点,便于我们理解springboot自动配置的机制。

  接下来让我们看看在自己的应用程序中实现配置的方法。

  如图:

  

package com.ynunicom.dc.dingdingcontractapp.configuration;

 

  import org.springframework.context.annotation.Bean;

  import org.springframework.context.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();

  }

 

  RestTemplateConfig

  这里我们设置了一个配置,往容器中加入了一个RestTemplate。

  首先说 @Configuration,这个标签继承了 @Component标签,我们可以在标签内容看到:

  

//

 

  // Source code recreated from a .class file by IntelliJ IDEA

  // (powered by Fernflower decompiler)

  package org.springframework.context.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 org.springframework.core.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 "";

  }

 

  Configuration

  可以看到其中是有 @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引包

  然后去maven的依赖里看它的自动配置类MybatisPlusAutoConfiguration

  

@Configuration

 

  @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})

  @ConditionalOnSingleCandidate(DataSource.class)

  @EnableConfigurationProperties({MybatisPlusProperties.class})

  @AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})

  public class MybatisPlusAutoConfiguration implements InitializingBean {

 

  MybatisPlusAutoConfiguration

  可以看到有上文提到的 @Configuration,还有从application.yml载入自动配置的 @EnableConfigurationProperties({MybatisPlusProperties.class})

  这个注解的具体内容请查看我另一篇博文,对其进行了解释:

  迅速学会@ConfigurationProperties的使用

  也就是说,springboot只要能扫描到MybatisPlusAutoConfiguration类的 @Configuration注解,其中的所有配置就能自动加入到容器中,这一过程由上面提到的SpringFactoriesLoader 起作用,它会去寻找 “META-INF/spring.factories” 文件,我们可以在 mybatis-plus的依赖中找到它:

  

  SpringFactoriesLoader为什么要读取它呢?因为它内部是这样的

  

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

 

   com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\

   com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

 

  factories

  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

  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";

   private static final Log logger = LogFactory.getLog(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) {

   Assert.notNull(factoryClass, "factoryClass must not be null");

   ClassLoader classLoaderToUse = classLoader;

   if (classLoader == null) {

   classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();

   List String factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);

   if (logger.isTraceEnabled()) {

   logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);

   List T result = new ArrayList(factoryNames.size());

   Iterator var5 = factoryNames.iterator();

   while(var5.hasNext()) {

   String factoryName = (String)var5.next();

   result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));

   AnnotationAwareOrderComparator.sort(result);

   return result;

   public static List String loadFactoryNames(Class ? factoryClass, @Nullable ClassLoader classLoader) {

   String factoryClassName = factoryClass.getName();

   return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());

   private static Map String, List String loadSpringFactories(@Nullable ClassLoader classLoader) {

   MultiValueMap String, String result = (MultiValueMap)cache.get(classLoader);

   if (result != null) {

   return result;

   } else {

   try {

   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 factoryClassName = ((String)entry.getKey()).trim();

   String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());

   int var10 = var9.length;

   for(int var11 = 0; var11 var10; ++var11) {

   String factoryName = var9[var11];

   result.add(factoryClassName, factoryName.trim());

   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 [" + factoryClass.getName() + "]");

   } else {

   return ReflectionUtils.accessibleConstructor(instanceClass, new Class[0]).newInstance();

   } catch (Throwable var4) {

   throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), var4);

  }

 

  SpringFactoriesLoader

  从代码中我们可以知道,在这个方法中会遍历整个 spring-boot 项目的 classpath 下 ClassLoader 中所有 jar 包下的 spring.factories文件。也就是说我们可以在自己的 jar 中配置 spring.factories 文件,不会影响到其它地方的配置,也不会被别人的配置覆盖。

  Spring Factories 在 Spring Boot 中的应用

  在 Spring Boot 的很多包中都能够找到 spring.factories 文件,接下来我们以 spring-boot-autoconfigure 包为例进行介绍

  

# Initializers

 

  org.springframework.context.ApplicationContextInitializer=\

  org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\

  org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

  # Application Listeners

  org.springframework.context.ApplicationListener=\

  org.springframework.boot.autoconfigure.BackgroundPreinitializer

  # Auto Configuration Import Listeners

  org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\

  org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

  # Auto Configuration Import Filters

  org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\

  org.springframework.boot.autoconfigure.condition.OnBeanCondition,\

  org.springframework.boot.autoconfigure.condition.OnClassCondition,\

  org.springframework.boot.autoconfigure.condition.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-boot-autoconfigure 包

  结合前面的内容,可以看出 spring.factories 文件可以将 spring-boot 项目包以外的 bean(即在 pom 文件中添加依赖中的 bean)注册到 spring-boot 项目的 spring 容器。

  由于@ComponentScan 注解只能扫描 spring-boot 项目包内的 bean 并注册到 spring 容器中,因此需要 @EnableAutoConfiguration 注解来注册项目包外的bean。

  而 spring.factories 文件,则是用来记录项目包外需要注册的bean类名。

  以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

  

  转自:https://www.zhangshengrong.com/p/281oqxEqNw/

  

  

  

  

  

 

 

  

  以上就是springboot自动配置原理以及spring.factories文件的作用详解(springboot自动配置了什么)的详细内容,想要了解更多 springboot自动配置原理以及spring.factories文件的作用详解的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: