MENU

springboot源码解析(二)自动配置

November 16, 2020 • Read: 3315 • 后端

前言

本文主要从源码角度分析springboot如何进行自动配置的。

springboot中的自动装配

springboot最核心的一点

特性:零xml、自动装配、内嵌tomcat。spring可以利用servlet3.0实现零xml和内嵌tomcat,但是自动装配实现不了

首先我们需要弄清楚自动装配做了什么,我先举个例子:

比如:

  1. 在单纯的spring项目中,我们开启AOP的方式是这样:

    先在pom.xml中导入AOP的坐标(引入相关依赖)

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
    </dependency>

    然后,在配置来中开启AOP,然后编写相关类来实现逻辑

    @Configuration
    @ComponentScan("com.example")
    @EnableAspectJAutoProxy // 这里开启AOP
    public class AppConfig {
    
    }
  2. 如果在springboot中,启用AOP

    我们只需要在pom.xml加入相关依赖就行,然后直接使用就可以

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

1. 自定义自动装配

  1. 在下面目录创建 resources/META-INF/spring.factories文件

    # 里面添加如下内容,key固定,value自己写类
    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.demo.config.MyAutoConfiguration
  2. 编写MyAutoConfiguration配置类

    @Configuration
    // 可以自定义加载的条件,可以说必须吧,因为一般也是满足什么条件才加载。
    // 比如:@ConditionalOnClass(DispatcherServlet.class),要存在这个类才加载
    @Conditional(MyCondition.class)
    // 会先加载这个,配置类里面加载相关设置。当然不是必须
    @EnableConfigurationProperties(MyProperties.class)
    public class MyAutoConfiguration {
        // 赋值后,后面可以来使用
        private final MyProperties myProperties;
    
        public MyAutoConfiguration(MyProperties properties) {
            this.myProperties = properties;
        }
        // ...todo
    }
  3. 编写配置类和条件类

    MyProperties类

    @ConfigurationProperties(prefix = "my", ignoreUnknownFields = true)
    public class MyProperties {
    
        private String name;
        private String password;
    }

    MyCondition类

    public class MyCondition implements Condition {
        // 返回true才会加载。
        // 这个方法里面写相关的逻辑,来返回true 或 false
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            // ...todo
            return false;
        }
    }
自定义一个自动装配已经完成,下面进入源码分析了。

2. 源码解析

  1. 从springboot项目的启动类开始,App类

    @SpringBootApplication
    public class App {
        public static void main(String[] args) {
    //        SpringApplication.run(App.class);
            SpringApplication application = new SpringApplication(App.class);
            application.run(args);
        }
    }
  2. 点击@SpringBootApplication注解

    image-20201116224907121.png

  3. 点击@EnableAutoConfiguration注解

    这里面用了 @Import注解,这里是利用spring中的扩展方式之一,后面可能会将spring中主要的扩展方式

    @AutoConfigurationPackage
    // 这里面又是从 spring.factories配置文件中拿 key为 EnableAutoConfiguration.class 的所有value值
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        Class<?>[] exclude() default {};
        String[] excludeName() default {};
    
    }
  4. 进入AutoConfigurationImportSelector类

    这里利用了@Import扩展方式的特点,通过全限定类名,加载出类,然后放入到spring容器中

    里面重要的方法:

    selectImports()方法

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
        // 这里面又是从 spring.factories配置文件中拿 key为 EnableAutoConfiguration.class 的所有value值
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                                                                                  annotationMetadata);
        // 最后在这个方法的返回值使用,然后spring内部,会通过全限定类名的字符串加载出类,然后放入到spring容器中
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

getAutoConfigurationEntry()方法

只要用的是 AutoConfigurationEntry类中的configurations属性,里面保存这需要被加载的类的全限定类名的集合

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
                                                           AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 这里面又是从 spring.factories配置文件中拿 key为 EnableAutoConfiguration.class 的所有value值
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    // 剔除一些配置,比如:因为项目中并没有加入相关的依赖(rabbitMQ),所以也不能创建对象
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

getCandidateConfigurations()方法

返回所有的key为 EnableAutoConfiguration 的所有类的全限定类名

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 这里面又是从 spring.factories配置文件中拿 key为 EnableAutoConfiguration.class 的所有value值
    // getSpringFactoriesLoaderFactoryClass()方法返回的是 EnableAutoConfiguration.class
    List<String> 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;
}
  1. SpringFactoriesLoader类

    里面的 loadSpringFactories()方法返回,key为EnableAutoConfiguration的所有在spring.factories文件中的value值

    public final class SpringFactoriesLoader {
    
        /**
         * The location to look for factories.
         * <p>Can be present in multiple JAR files.
         */
        // 这里就是要写在相关目录下的原因,这里会加载类路径下的META-INF目录下的 spring.factories文件
        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
        // ......
    
        public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
        }
    
        private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
            // 这里首先尝试从cache中拿取,如果不为空,直接就返回
            MultiValueMap<String, String> result = cache.get(classLoader);
            if (result != null) {
                return result;
            }
    
            try {
                // FACTORIES_RESOURCE_LOCATION 就是 "META-INF/spring.factories"
                Enumeration<URL> urls = (classLoader != null ?
                                         classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                                         ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
                result = new LinkedMultiValueMap<>();
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    // 变成资源文件
                    UrlResource resource = new UrlResource(url);
                    // 会把里面的键值对都存在properties里面
                    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 map中
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }
                // 放入到cache中
                cache.put(classLoader, result);
                return result;
            }
            // ......
        }
        // ......
    }
到这里,本篇博客也写的基本完成了,后面如果有需要可能会进行补充,如有错误,欢迎指出。又水了一篇,快乐。
Leave a Comment

已有 1 条评论
  1. wrhn wrhn     Windows 7 /    Google Chrome

    提供拼多多代发 京东快递 淘宝代发,无需签收,单号网www.kuaidzj.com