您的当前位置:首页正文

Springboot自动配置的原理分析

来源:九壹网

1.什么是springboot的自动配置?

       关于springboot的自动配置,简单来说就是为了从spring.factories文件中获取到对应的需要进行自动装配的类,并生成相应的Bean对象,然后将它们交给spring容器来帮我们进行管理。

2.springboot的自动配置有什么用?

      众所周知,springboot框架中集成了很多我们常用到的技术,如持久层的JDBC技术,视图层的SpringMVC技术等,如果没有自动配置,就需要我们人为去手动配置,这样一来就会很麻烦,而通过自动配置,就很好的解决了这个问题。当我们创建一个Springboot项目时,springboot框架会自动帮我们配置好框架所集成的所有技术,我们只管使用即可,从而也大大提升了我们的开发效率。

3.springboot的自动配置是如何实现的?

   (1)@SpringBootApplication:

      这个注解是springboot项目主启动类上的一个注解,是一个组合注解,也就是由其他注解组合起来,它的主要作用就是标记说明这个类是springboot的主启动类,springboot应该运行这个类里面的main()方法来启动程序。

@SpringBootApplication
public class Enable04Application {

    public static void main(String[] args) {
        SpringApplication.run(Enable04Application.class, args);
    }

}

          @SpringBootApplication注解中有几个子注解,我们一起分析一下:

@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 {

         《1》@SpringBootConfiguration

这个注解包含了@Configuration,@Configuration里面又包含了一个@Component注解,也就是说,这个注解标注在哪个类上,就表示当前这个类是一个配置类,而配置类也是spring容器中的组件。

//@SpringBootConfiguration注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

//@SpringBootConfiguration注解中的子注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}

      《2》@EnableAutoConfiguration :

此注解是springboot框架中实现自动配置最关键的注解,主要用于开启自动配置。

      《3》@ComponentScan:

      此注解主要用于自动扫描符合条件的组件或bean对象,并将其注入IOC容器中,这个注解我们也曾在spring框架中单独使用过,主要用于通过注解指定spring在创建容器时要扫描的包。

    (2) @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 {};
}

         《1》 @AutoConfigurationPackage:

        此注解主要用于自动配置包:将启动类同级所在包及此包下所有组件都自动扫描到容器中,这也是我们在springboot项目中将所有代码都写在启动类所在的包下的原因。

//@AutoConfigurationPackage注解详情
//主要通过@import注解指向的配置类完成扫描到的包内的组件注入IOC容器中
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

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

       《2》@Import({AutoConfigurationImportSelector.class}):

    此注解主要用于通过AutoConfigurationImportSelector类中的selectImports()方法将方法的返回值注入容器中。

   (3)AutoConfigurationImportSelector:

         我们前面了解过@import注解的用法中有一种用法就是导入ImportSelector实现类:根据实现类中重写方法的返回值String类型数组中的内容,导入对应的类。而对于上面的类我们通过类名也能发现它是ImportSelector接口的实现类,因此在此@import注解的作用也是如此。

       《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());
        }
    }

      此方法主要用于将方法返回值对应的配置类导入容器中,但是要导入的配置类 是怎么找到的呢?

     在 selectImports()方法中调用了一个getAutoConfigurationEntry()方法

     《2》getAutoConfigurationEntry()方法:

  此方法主要用于获取自动配置的入口信息,这些信息将用于决定哪些自动配置类应该被加载到 Spring 容器中。

//getAutoConfigurationEntry方法详情:

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);
        }
    }

     而在getAutoConfigurationEntry()方法中又调用了一个getCandidateConfigurations(annotationMetadata, attributes)方法

    《3》 getCandidateConfigurations(annotationMetadata, attributes)方法:

此方法确保了系统能够找到并考虑所有可能的自动配置选项,以便根据应用程序的实际情况进行智能的配置选择 。

//getCandidateConfigurations方法详情:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
        ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

 在此方法中又调用了loadFactoryNames():

    《4》 loadFactoryNames():

//loadFactoryNames方法详解
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

        在loadFactoryNames()方法中又调用了loadSpringFactories()方法:

    《5》 loadSpringFactories():

//loadSpringFactories方法详情:
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();

            try {
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");

                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 factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }

                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }

       重点就在这个loadSpringFactories()方法中,在此方法中它会查找所有在META-INF

径下的spring.factories文件 ,此文件中的内容都是以键值对的形式存在。其中有一个以EnableAutoConfiguration为key,以要加载的配置类的全路径为value的键值对,方法通过加载此键值对进而加载注入springboot自动配置的多个配置类,实现springboot的自动配置。

4.总结

    1.通过@SpringBootApplication注解声明springboot项目的启动类

    2.通过@SpringBootConfiguration注解声明此类也是一个配置类

    3.通过@EnableAutoConfiguration注解开启springboot的自动配置

    4.通过@Import({AutoConfigurationImportSelector.class})注解将要自动配置的组件注入容            器 中。

    5. 通过AutoConfigurationImportSelector类中的selectImports()方法完成自动配置组件的           注 入。

    6.在selectImports()方法中调用getAutoConfigurationEntry()方法。

    7.在getAutoConfigurationEntry()中又调用getCandidateConfigurations(annotationMetadata,               attributes)方法。

    8.在上面方法中又调用了loadFactoryNames()方法。

    9.在loadFactoryNames()方法中又调用了loadSpringFactories()。

   10. 在loadSpringFactories()方法中会找到需要加载的配置类的位置:在META-INF路径下                    的 spring.factories文件中以EnableAutoConfiguration为key所映射的value值,此value              值 即表示springboot自动配置时所加载的所有配置类。

5.注意事项:

    (1)如果新建的springboot项目中有新的坐标添加,则将坐标对应的配置类也一并加载。

    (2)虽然这些配置类都会加载,但是否初始化bean还取决于这些配置类上的@condition注解                 的 判断结果,而并非全部初始化bean对象。

    (3)自动配置所加载的配置类的位置可能不一定在上述路径位置

 Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
       

        上述代码说明了此配置类可能还存在于另一个位置:

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.

   此文件中的所有内容即是springboot框架在自动配置时所加载的所有配置类的全类名。

 

因篇幅问题不能全部显示,请点此查看更多更全内容

Top