MENU

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

November 16, 2020 • Read: 2744 • 后端

前言

本文主要从源码角度分析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 {

}
  1. 如果在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

  1. 点击@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 {};

}
  1. 进入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