简述springboot自动配置_如何配制溶液

简述springboot自动配置_如何配制溶液阅读收获:+1|type_1_2:理解SpringBoot自动配置原理SpringBoot是什么SpringBoot的诞生就是为了简化Spring中繁琐的XML配置,其本质依然还是Spring框架,使用SpringBoot之后可以不使用任何XML配置来启动一个服务,使得我们在使用微服务架构时可以更加快速的建立一个应用。简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式。SpringBoot的特点 提供了固定的配置来简化配置…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

阅读收获

:+1|type_1_2: 理解SpringBoot自动配置原理

SpringBoot是什么

SpringBoot  的诞生就是为了简化  Spring  中繁琐的  XML  配置,其本质依然还是Spring框架,使用SpringBoot之后可以不使用任何 XML 配置来启动一个服务,使得我们在使用微服务架构时可以更加快速的建立一个应用。

简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式。

SpringBoot的特点

  • 提供了固定的配置来简化配置,即 约定大于配置

  • 尽可能地自动配置 Spring 和第三方库,即能 自动装配

  • 内嵌容器,创建独立的 Spring 应用

  • 让测试变的简单,内置了JUnit、Spring Boot Test等多种测试框架,方便测试

  • 提供可用于生产的特性,如度量、运行状况检查和外部化配置。

  • 完全不需要生成代码,也不需要 XML 配置。

启动类

下面探究SpringBoot的启动原理,关于一些细节就不赘述,我们捉住主线分析即可。

注意: 本文的 SpringBoot 版本为 2.6.1

3.1 @SpringBootApplication

一切的来自起源SpringBoot的启动类,我们发现main方法上面有个注解: @SpringBootApplication

@SpringBootApplication
public class SpringbootWorkApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWorkApplication.class, args);
    }
}
复制代码

@SpringBootApplication  标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的main方法来启动 SpringBoot 应用;它的本质是一个组合注解,我们点进去查看该类的元信息主要包含3个注解:

@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 {
复制代码
  • @SpringBootConfiguration (里面就是@Configuration,标注当前类为配置类,其实只是做了一层封装改了个名字而已)

  • @EnableAutoConfiguration (开启自动配置)

  • @ComponentScan (包扫描)

注:@Inherited是一个标识,用来修饰注解,如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解

我们下面逐一分析这3个注解作用

3.1.1 @SpringBootConfiguration

我们继续点 @SpringBootConfiguration 进去查看源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
复制代码

@Configuration 标注在某个类上,表示这是一个 springboot的 配置类 。可以向容器中注入组件。

3.1.2 @ComponentScan

  • @ComponentScan :配置用于 Configuration 类的组件扫描指令。

  • 提供与  Spring XML  的  <context:component-scan>  元素并行的支持。

  • 可以  basePackageClasses  或 basePackages  来定义要扫描的特定包。如果没有定义特定的包,将从声明该注解的类的 包开始扫描 。

3.1.3 @EnableAutoConfiguration

  • @EnableAutoConfiguration顾名思义就是: 开启自动导入配置

  • 这个注解是SpringBoot的重点,我们下面详细讲解

@EnableAutoConfiguration

  • 我们点进去看看该注解有什么内容

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage   //自动导包
@Import({AutoConfigurationImportSelector.class}) //自动配置导入选择
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}
复制代码

4.1 @AutoConfigurationPackage

  • 自动导入配置包

  • 点进去查看代码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}
复制代码

@Import  为spring的注解,导入一个配置文件,在springboot中为给容器导入一个组件,而导入的组件由 AutoConfigurationPackages.class的内部类 Registrar.class  执行逻辑来决定是如何导入的。

4.1.1 @Import({Registrar.class})

点Registrar.class进去查看源码如下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    Registrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        //断点
        AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
    }

    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
    }
}
复制代码

注:Registrar实现了 ImportBeanDefinitionRegistrar 类,就可以被注解@Import导入到spring容器里。

这个地方打断点

简述springboot自动配置_如何配制溶液

运行可以查看到 (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]) 的值为 com.ljw.springbootwork :当前启动类所在的包名

结论: @AutoConfigurationPackage 就是将主配置类(@SpringBootApplication 标注的类)所在的包下面所有的组件都扫描注冊到 spring 容器中。

4.2 @Import({AutoConfigurationImportSelector.class})

作用:AutoConfigurationImportSelector 开启自动配置类的导包的选择器 ,即是带入哪些类,有选择性的导入

点AutoConfigurationImportSelector.class进入查看源码,这个类中有两个方法见名知意:

  1. selectImports:选择需要导入的组件

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}
复制代码
  1. getAutoConfigurationEntry:根据导入的@Configuration类的AnnotationMetadata返回AutoConfigurationImportSelector.AutoConfigurationEntry

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
         // 这打个断点,看看 返回的数据
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        //删除重复项
        configurations = this.removeDuplicates(configurations);
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        //检查
        this.checkExcludedClasses(configurations, exclusions);
        //删除需要排除的依赖
        configurations.removeAll(exclusions);
        configurations = this.getConfigurationClassFilter().filter(configurations);
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}
复制代码

this.getCandidateConfigurations(annotationMetadata, attributes)这里断点查看

简述springboot自动配置_如何配制溶液

configurations数组长度为133,并且文件后缀名都为  **AutoConfiguration

结论:  这些都是候选的配置类,经过去重,去除需要的排除的依赖,最终的组件才是这个环境需要的所有组件。有了自动配置,就不需要我们自己手写配置的值了,配置类有默认值的。

我们继续往下看看是如何返回需要配置的组件的

4.2.1 getCandidateConfigurations(annotationMetadata, attributes)

方法如下:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
复制代码

这里有句断言: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.”);

意思是:“在 META-INF/spring.factories 中没有找到自动配置类。如果您使用自定义包装,请确保该文件是正确的。“

结论:  即是要loadFactoryNames()方法要找到自动的配置类返回才不会报错。

4.2.1.1 getSpringFactoriesLoaderFactoryClass()

我们点进去发现:this.getSpringFactoriesLoaderFactoryClass()返回的是 EnableAutoConfiguration.class 这个注解。这个注解和@SpringBootApplication下标识注解是同一个注解。

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}
复制代码

结论: 获取一个能加载自动配置类的类,即SpringBoot默认自动配置类为EnableAutoConfiguration

4.2.2 SpringFactoriesLoader

SpringFactoriesLoader工厂加载机制是Spring内部提供的一个约定俗成的加载方式,只需要在模块的META-INF/spring.factories文件,这个Properties格式的文件中的key是接口、注解、或抽象类的全名,value是以逗号 “ , “ 分隔的实现类,使用SpringFactoriesLoader来实现相应的实现类注入Spirng容器中。

注:会加载 所有jar包 下的classpath路径下的META-INF/spring.factories文件,这样文件不止一个。

4.2.2.1 loadFactoryNames()

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
   ClassLoader classLoaderToUse = classLoader;
   if (classLoaderToUse == null) {
      classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
   }
   String factoryTypeName = factoryType.getName();
   return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
复制代码

断点查看factoryTypeName:

简述springboot自动配置_如何配制溶液

先是将  EnableAutoConfiguration.class  传给了  factoryType  

然后 String factoryTypeName = factoryType.getName(); ,所以 factoryTypeName  值为  org.springframework.boot.autoconfigure.EnableAutoConfiguration

4.2.2.2 loadSpringFactories()

接着查看loadSpringFactories方法的作用

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    //断点查看
   Map<String, List<String>> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   result = new HashMap<>();
   try {
      //注意这里:META-INF/spring.factories
      Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
      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();
            String[] factoryImplementationNames =
                  StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
            for (String factoryImplementationName : factoryImplementationNames) {
            //断点
               result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                     .add(factoryImplementationName.trim());
            }
         }
      }

      // Replace all lists with unmodifiable lists containing unique elements
      //去重,断点查看result值
      result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
            .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
      cache.put(classLoader, result);
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
   return result;
}
复制代码

这里的 FACTORIES_RESOURCE_LOCATION 在上面有定义:META-INF/spring.factories

public final class SpringFactoriesLoader {

   /**
    * The location to look for factories.
    * <p>Can be present in multiple JAR files.
    */
   public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
复制代码

META-INF/spring.factories文件在哪里??在所有引入的java包的当前类路径下的META-INF/spring.factories文件都会被读取,如:

简述springboot自动配置_如何配制溶液

断点查看result值如下:

简述springboot自动配置_如何配制溶液

该方法作用是加载所有依赖的路径META-INF/spring.factories文件,通过map结构保存,key为文件中定义的一些标识工厂类,value就是能自动配置的一些工厂实现的类,value用list保存并去重。

简述springboot自动配置_如何配制溶液

在回看  loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());

因为  loadFactoryNames  方法携带过来的第一个参数为  EnableAutoConfiguration.class ,所以  factoryType  值也为  EnableAutoConfiguration.class ,那么  factoryTypeName  值为  EnableAutoConfiguration 。拿到的值就是META-INF/spring.factories文件下的key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration的值

简述springboot自动配置_如何配制溶液

getOrDefault  当  Map  集合中有这个 key 时,就使用这个 key值,如果没有就使用默认值空数组

结论:

  • loadSpringFactories()该方法就是从“META-INF/spring.factories”中加载给定类型的工厂实现的完全限定类名放到map中

  • loadFactoryNames()是根据SpringBoot的启动生命流程,当需要加载自动配置类时,就会传入org.springframework.boot.autoconfigure.EnableAutoConfiguration参数,从map中查找key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,这些值通过反射加到容器中,之后的作用就是用它们来做自动配置,这就是Springboot自动配置开始的地方

  • 只有这些自动配置类进入到容器中以后,接下来这个自动配置类才开始进行启动

  • 当需要其他的配置时如监听相关配置:listenter,就传不同的参数,获取相关的listenter配置。

流程总结图

简述springboot自动配置_如何配制溶液

常用的Conditional注解

  • 在加载自动配置类的时候,并不是将spring.factories的配置全部加载进来,而是通过@Conditional等注解的判断进行动态加载

  • @Conditional其实是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。

  • 常用的Conditional注解:

    • @ConditionalOnClass :classpath中存在该类时起效

    • @ConditionalOnMissingClass :classpath中不存在该类时起效

    • @ConditionalOnBean :DI容器中存在该类型Bean时起效

    • @ConditionalOnMissingBean :DI容器中不存在该类型Bean时起效

    • @ConditionalOnSingleCandidate :DI容器中该类型Bean只有一个或@Primary的只有一个时起效

    • @ConditionalOnExpression :SpEL表达式结果为true时

    • @ConditionalOnProperty :参数设置或者值一致时起效

    • @ConditionalOnResource :指定的文件存在时起效

    • @ConditionalOnJndi :指定的JNDI存在时起效

    • @ConditionalOnJava :指定的Java版本存在时起效

    • @ConditionalOnWebApplication :Web应用环境下起效

    • @ConditionalOnNotWebApplication :非Web应用环境下起效

@Import支持导入的三种方式

  1. 带有@Configuration的配置类

  2. ImportSelector 的实现

  3. ImportBeanDefinitionRegistrar 的实现

来源:稀土掘金

作者:小伙子ave

链接:https://juejin.cn/post/7046554366068654094

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/171543.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • struts2 拦截器_struts2自定义拦截器

    struts2 拦截器_struts2自定义拦截器拦截器(interceptor)是Struts2最强大的特性之一,也可以说是struts2的核心,拦截器可以让你在Action和result被执行之前或之后进行一些处理。同时,拦截器也可以让你将通用的代码模块化并作为可重用的类。Struts2中的很多特性都是由拦截器来完成的。拦截是AOP的一种实现策略。拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action…

  • C++ 单例模式_c 单例模式

    C++ 单例模式_c 单例模式原创文章,转载请注明出处。本文主要从单例的用处以及问题所做介绍

  • Random的nextInt方法[通俗易懂]

    Random的nextInt方法[通俗易懂]Random类中的nextInt()方法(注:不带参数)会随机产生所有有效的整数,包括正数,负数和零。Random类中的nextInt(intn)方法会产生[0,n)之间的随机int值(包含0,不包含n)两种生成介于[0,100)的随机数的方法:方法一:用Random类中的nextInt()方法packagech06;importjava.util.Random;…

  • IoC控制反转「建议收藏」

    IoC控制反转「建议收藏」IoC控制反转IoC控制反转(InversionofControl,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(DependencyInjection,简称DI),还有一种方式叫“依赖查找”(DependencyLookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。DIDI—DependencyInjection,即“依赖注入”:

  • PHP 手机短信验证码 laravel 实现流程

    PHP 手机短信验证码 laravel 实现流程

    2021年10月25日
  • 查询电脑用户名命令_怎么改电脑管理员用户名

    查询电脑用户名命令_怎么改电脑管理员用户名2016-12-1809:07:531.首先我们点击电脑左下角的开始,然后点击“控制面板”2.在弹出的窗口里点击“添加或删除用户账户”3.点击“创建一个新账户”4.填上你要开的账号的名字5.勾选你要开的账号…2017-01-0414:07:08无线网络(wifi)名的更改方法:1.打开电脑的wifi,搜索路由器默认wifi名(路由器背面铭牌有写),连接wifi网络。2.打开电…

    2022年10月14日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号