• Spring Boot自动配置原理懂后轻松写一个自己的starter


    目前很多Spring项目的开发都会直接用到Spring Boot。因为Spring原生开发需要加太多的配置,而使用Spring Boot开发很容易上手,只需遵循Spring Boot开发的约定就行了,也就是约定大于配置,无需觉得它神奇,它的底层都是使用的Spring。聊完这个原理带着大家轻松写一个自己的starter。

    要学习它的自动配置原理首先自己要创建一个Spring Boot项目,创建项目过程很简单就不介绍了。学习它的自动配置原理,首先要看的就是如下这个注解(SpringBootApplication)。这个注解大家都是很熟悉,这个注解是由如下三个注解组成如下:

    复制代码
    //第一个注解
    @SpringBootConfiguration
    //第二个注解
    @EnableAutoConfiguration
    //第三个注解
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
    复制代码

    上面三个注解都是太太重要了,本文由于聊自动配置所以就只讲EnableAutoConfiguration这个注解,Spring Boot的自动配置原理精髓都在这个注解里面。好了那就先看这个注解的代码如下:

    复制代码
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    复制代码

    看到这个注解一眼就能瞧到它帮助我们导入了一个AutoConfigurationImportSelector 。由于很多地方遇到了这个Import注解,所以先简单说一下这个注解的作用。

    1:给Spring容器自动注入导入的类。如下使用,就是帮助Spring容器自动导入了一个TableEntity对象,在项目中你不需要new 对象,也不需要给这个对象加任何注解了,就可以直接使用TableEntity对象了。

    @Configuration
    @Import(TableEntity.class)
    public class TestConfig {
    }

    2:给容器导入一个ImportSelector,比如上文讲的那个AutoConfigurationImportSelector  。通过字符串数组的方式返回配置类的全限定名,然后把这些类注入到Spring容器中,我们也就可以直接在Spring容器中使用这些类了。

    好了讲了上面那2段作用我们主要分析的也就是下面这段代码了。

    复制代码
    public class AutoConfigurationImportSelector {
      @Override
      //作用就是Spring会把这个方法返回的数组中所有全限定名的类注入到Spring容器中
      //供使用者直接去使用这些类。
      public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
          return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
            //这个方法是Spring Boot 自动配置要说的
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
            annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
      }
    复制代码

    然后我们后面主要分析的也就是getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata),这个方法。

    复制代码
    //这个方法中的每个方法都很重要,一个一个说
      protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
          AnnotationMetadata annotationMetadata) {
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        //1:见名知意,获取候选配置类
        List configurations = getCandidateConfigurations(annotationMetadata, attributes);
        // 2:去除重复的配置类,这个方法太好了。
        configurations = removeDuplicates(configurations);
        //3 :去除使用者排除的配置类。
        Set exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
      }
    复制代码

    getCandidateConfigurations这个方法的意思就是获取候选的配置类(也就是Spring Boot已经自动配置的那些类),如下:(PS我们一看那个报错信息就能猜出来Spring从这个【META-INF/spring.factories】下找配置类)

    复制代码
    protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List 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;
      }
    复制代码

    主要找配置类信息的就是如下代码了。

    复制代码
    public static List loadFactoryNames(Class factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
      }
    
      private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
       // 1:第一步先从缓存中找,找不到在循环遍历找,
       // 由于Spring代码逻辑太复杂,Spring很多地方都采用这种缓存的设计
        MultiValueMap result = cache.get(classLoader);
        if (result != null) {
          return result;
        }
       // public static final String 下面代码用到的常量值如下
       // FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
        try {
        // 扫描 代码中的所有META-INF/spring.factories"文件
          Enumeration urls = (classLoader != null ?
              classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
              ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
          result = new LinkedMultiValueMap<>();
          //循环遍历加载上面所说的文件下的文件,并把它们放入到
          // 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 factoryClassName = ((String) entry.getKey()).trim();
              for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                result.add(factoryClassName, factoryName.trim());
              }
            }
          }
          //放缓存中一份,后面要加载从这个缓存中直接取,
          // 如果看全代码可知Spring Boot 缓存的不止有配置类,还有其他类。
          cache.put(classLoader, result);
          return result;
        }
        catch (IOException ex) {
          throw new IllegalArgumentException("Unable to load factories from location [" +
              FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
      }
    复制代码

    从上面代码可知Spring主要是从META-INF/spring.factories文件中加载配置类,那么就带大家看一看Spring Boot自己已经配置的类有哪些。

    后面就回到这个(removeDuplicates)去重方法,如下:

    protected final  List removeDuplicates(List list) {
        return new ArrayList<>(new LinkedHashSet<>(list));
      }

    为什么要把这单独一行代码列出来呢?是因为我感觉这段去重复代码用的太好了,自从看了这段代码,后面博主自己写去重逻辑的时候也就参照Spring大佬这一行代码写去重逻辑(PS:如果自己业务去重逻辑没有其他逻辑的时候参考使用),简单,效率应该也不低毕竟大佬们这样用了。

    后面代码逻辑就是一些去除用户自己要排除,要过滤掉的配置类。然后就会使用Spring的ImportSelector这个特性(PS具体Spring是怎么把这些返回权限定名的类加载的容器中的,是Spring加载类方面的知识,本文不做具体介绍)

    好了,然后带着大家创建一个自己的starter(PS:命名规范我是参考了mybatis-spring,毕竟是大神们的命名规范,记好约定大于配置,哈哈哈)的starter 。

    复制代码
    1: 创建一个工程,信息如下:
           scott-spring-boot-starter
           scottspringbootstarter
              0.0.1-SNAPSHOT
    2:再创建一个工程 也就是autoconfigure项目。
    如下:
          com.spring.starter
          scott-spring-boot-starter-autoconfigure
          0.0.1-SNAPSHOT
      在pom文件中引入如下(一般下面的是必须引入的):
    
                org.springframework.boot
                spring-boot-starter-parent
                2.1.8.RELEASE
                
          
          
                
                      org.springframework.boot
                      spring-boot-starter
                
              
    3:创建HelloService。
    public class HelloService {
        HelloProperties helloProperties ;
    
        public String sayHello(String name){
            return helloProperties.getPrefix()+"-"+name+helloProperties.getSuffix() ;
        }
        public HelloProperties getHelloProperties() {
            return helloProperties;
        }
        public void setHelloProperties(HelloProperties helloProperties) {
            this.helloProperties = helloProperties;
        }
    }
    
    
    4: 创建相应的properies文件。
    @ConfigurationProperties(prefix="scott.hello")  //以 scott.hello开头的。
    public class HelloProperties {
          
          private String prefix ;
          
          private String suffix ;
          public String getPrefix() {
                return prefix;
          }
          public void setPrefix(String prefix) {
                this.prefix = prefix;
          }
          public String getSuffix() {
                return suffix;
          }
          public void setSuffix(String suffix) {
                this.suffix = suffix;
          }
          
    5:创建自定义的配置文件如下:
    @Configuration
    @ConditionalOnWebApplication // 在web环境下才生效
    @EnableConfigurationProperties(HelloProperties.class) // 属性文件生效
    public class HelloServiceAutoConfiguration {
          @Autowired
          HelloProperties helloProperties;
       
          @Bean
          public HelloService helloService() {
                HelloService service = new HelloService();
                service.setHelloProperties(helloProperties);
                return service;
          };
    }
    复制代码

    6:在META-INF 文件夹下创建 spring.factories 文件,写入如下自己的配置类 。Spring Boot自动配置规约,约定大于规范,如下图的配置所示:

    7:在scottspringbootstarter项目的pom文件中引入自定义的 autoconfigure如下:

    复制代码
    scott-spring-boot-starter
        scottspringbootstarter
        1.0-SNAPSHOT
        
            
                com.spring.starter
                scott-spring-boot-starter-autoconfigure
                1.0-SNAPSHOT
            
    
        
    复制代码

    8:自定义starter就好了,然后就可以在我们自定义的工程中引入scottspringbootstarter就可以使用了。

    如下使用方法,配置yml文件,然后使用对应的服务,So Easy:

     

  • 相关阅读:
    svn问题集
    调整C / C ++编译器以在多核应用程序中获得最佳并行性能:第二部分
    Tensorflow—第四讲网络八股扩展
    IEEE Standard for SystemVerilog—Chapter14. Clocking blocks
    mysql源码分析——InnoDB的内存应用整体架构源码
    什么是Barr-C?
    【计算机网络笔记】路由算法之距离向量路由算法
    (十一)numpy中的meshgrid使用
    Vue 3 相对于 Vue2,模板和组件的一些变化
    Pytest接口自动化测试实战演练
  • 原文地址:https://www.cnblogs.com/scott1102/p/17140461.html