Springboot自动装配原理和手动实现一个starter

5,081 阅读8分钟

Springboot自动装配

在使用springboot开发项目的时候,当要引入mysql,rabbitmq等组件的时候,会很容易做到只需要在pom.xml或者build.gradle中引入相关start依赖,我们就可以在项目中直接使用,或者只要在配置文件中添加相关服务配置即可使用。

那么这又是因为什么呢?因为springboot的自动装配帮助我们进行了封装从而让开发者可以将更多精力放到本身业务的开发中。那么springboot又是怎么实现的呢?对此我比较好奇,因此记录下自己了解springboot自动装配的实现方式,接下来从两个方面来了解自动装配:自动装配实现原理,然后自己创建一个starter来巩固对于自动装配的理解。

实现原理

通过在springboot中启动类添加@SpringBootApplication可以快速启动一个springboot应用,那么我们就从@SpringBootApplication来了解自动装配,点开@SpringBootApplication注解可以看到包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解。

@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: 标记当前类为配置类;
  • @EnableAutoConfiguration 打开自动装配(自动配置着重来看该注解);
  • @ComponentScan 配置包扫描定义的扫描路径,把符合扫描规则的类装配到spring容器; 那么接下来就主要分析下@EnableAutoConfiguration这个注解都做了什么。

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

进入EnableAutoConfiguration之后可以看到主要的两个主要注解@AutoConfigurationPackage和@Import({AutoConfigurationImportSelector.class}),那么分别进入两个注解看下有什么作用。

@AutoConfigurationPackage

package org.springframework.boot.autoconfigure;

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

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

在注解中可以看到通过@Import导入了Registrar这个类,那么在继续深入到Registrar类中,Registrar实现了ImportBeanDefinitionRegistrar接口, 我们都知道ImportBeanDefinitionRegistrar接口可以自动导入Bean到容器中,那么看下Registrar是怎么实现的。

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

查看AutoConfigurationPackages.register方法,可以看到register中会将packageNames下的Bean注入容器中,那么我们看下这个packageNames具体会是什么路径。

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    if (registry.containsBeanDefinition(BEAN)) {
        AutoConfigurationPackages.BasePackagesBeanDefinition beanDefinition = (AutoConfigurationPackages.BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);
        beanDefinition.addBasePackages(packageNames);
    } else {
        registry.registerBeanDefinition(BEAN, new AutoConfigurationPackages.BasePackagesBeanDefinition(packageNames));
    }

}

通过debug发现packageNames是我们服务启动类SpringBootStartMessageTestApplication类的package路径com.troyqu.springbootstartmessagetest,也就是说@Import({Registrar.class})会将我们服务下的Bean注册到容器中。 image.png

@Import({AutoConfigurationImportSelector.class})

对于各个starter的自动装配的实现逻辑就在AutoConfigurationImportSelector类里面。AutoConfigurationImportSelector的处理逻辑通过selectImports()来选择需要导入的组件。AutoConfigurationImportSelector实现的是DeferredImportSelector接口。这是一个延迟导入的类关于selectImports()的调用查看链接AutoConfigurationImportSelector#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方法中会返回AutoConfigurationEntry的getConfigurations(),configurations是一个List<String>,那么我们看下AutoConfigurationEntry是怎么生成的。在getAutoConfigurationEntry()方法中可以看到最终的List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); 然后后续代码中移除掉了重复的配置,也就是说configurations主要和getAttributes()getCandidateConfigurations()相关,那么接下来看下getAttributes()getCandidateConfigurations()

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

getAttributes()中的内容比较简单,接着看getCandidateConfigurations()

protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
    String name = this.getAnnotationClass().getName();
    AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
    Assert.notNull(attributes, () -> {
        return "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?";
    });
    return attributes;
}

protected Class<?> getAnnotationClass() {
    return EnableAutoConfiguration.class;
}

在getCandidateConfigurations()中通过SpringFactoriesLoader.loadFactoryNames来获取最终的configurations,并且可以通过断言发现会使用到META-INF/spring.factories文件,那么我们再进入SpringFactoriesLoader.loadFactoryNames()中来看下最终的实现。

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

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

SpringFactoriesLoader

SpringFactoriesLoader中会读取META-INF/spring.factories文件下的内容到Map中,再结合传入的factoryType=EnableAutoConfiguration.class,因此会拿到 org.springframework.boot.autoconfigure.EnableAutoConfiguration为key对应的各个XXAutoConfiguration的值,然后springboot在结合各个starter中的代码完成对于XXAutoConfiguration中的Bean的加载动作。

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

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

到最后我们在回想一遍完整的自动配置的工作流程。

  1. 通过@SpringBootApplication包含@EnableAutoConfiguration实现自动装配的功能;
  2. @EnableAutoConfiguration通过@AutoConfigurationPackage实现对于当前项目中Bean的加载;
  3. @EnableAutoConfiguration通过@Import({AutoConfigurationImportSelector.class})实现对于引入的start中的XXAutoConfiguration的加载;
  4. AutoConfigurationImportSelector类中通过SpringFactoriesLoader读取 META-INF/spring.factories中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的各个XXAutoConfiguration的值,然后springboot在结合各个start中的代码完成对于XXAutoConfiguration中的Bean的加载动作;
  5. AutoConfigurationImportSelector类通过实现DeferredImportSelector延迟导入接口,在容器初始化的过程中在完成以上动作;

自定义start实现自动装配

在了解了springboot自动装配的实现方式后,那么我们可以自己手动编写一个starter来了解自动装配。 github地址:github.com/swallretu/s…

自定义starter

定义MessageAutoConfiguration类来作为自定义starter的自动装配类,并且注入MessagePublic到spring容器中(通过在starter中构建MessagePublic Bean则用户只需要引入starter之后不需要在手动创建MessagePublic Bean即可使用)。

@Configuration
public class MessageAutoConfiguration {

    @Bean
    MessagePublic messagePublic(MessagePublicProperties properties){
        System.out.println("使用自动装配初始化MessagePublic Bean: message public自动装配中...");
        return new MessagePublic(properties);
    }
}

创建MessagePublic模拟消息发送类,为了简化代码只在发送类中打印一下消息发送类的相关配置信息。

package com.troyqu;


public class MessagePublic {

    MessagePublicProperties properties;

    public MessagePublic(MessagePublicProperties properties) {
        this.properties = properties;
        System.out.println("查看监听器中已配置的事件:" + properties.events);
    }

}

配置MessagePublicProperties类用来提供消息发布类中需要配置的消息事件,示例简化代码只设置一个events属性且添加默认值,当业务代码中如果只是引入starter并未做events配置时使用默认events。通过添加@ConfigurationProperties使MessagePublicProperties中的属性都可以通过配置文件配置,也需要在配置类中配置EnableConfigurationProperties。 @EnableConfigurationProperties(MessagePublicProperties.class)

package com.troyqu;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "messagestarter.public")
public class MessagePublicProperties {

    String events = "defaultEvent";

    public String getEvents() {
        return events;
    }

    public void setEvents(String events) {
        this.events = events;
    }
}

在MessageAutoConfiguration的基础上,我们可以参考官方的其他starter来配置starter的enable属性和自动配置的Bean的生成策略。

  • 通过添加@ConditionalOnProperty指定只有在代码中配置了属性messagepublic.enabled=true时,才会将MessageAutoConfiguration添加到ApplicationContext中,我们也实现了通过参数配置来控制start是否生效。@ConditionalOnProperty(value = "messagepublic.enabled", havingValue = "true")
  • 通过在MessagePublic Bean中添加@ConditionalOnMissingBean来控制是否自动创建MessagePublic Bean(当start被引入项目Context中无MessagePublic Bean的时候,再自动创建MessagePublic Bean)。
package com.troyqu;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(MessagePublicProperties.class)
@ConditionalOnProperty(value = "messagepublic.enabled", havingValue = "true")
public class MessageAutoConfiguration {

    @ConditionalOnMissingBean
    @Bean
    MessagePublic messagePublic(MessagePublicProperties properties){
        System.out.println("使用自动装配初始化MessagePublic Bean: message public自动装配中...");
        return new MessagePublic(properties);
    }
}

配置spring.factories中EnableAutoConfiguration类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.troyqu.MessageAutoConfiguration

构建本地项目到本地仓库

mvn install:install-file -Dfile=./target/spring-boot-start-message-1.0-SNAPSHOT.jar -DgroupId=com.troyqu -DartifactId=spring-boot-start-message -Dversion=1.0-SNAPSHOT -Dpackaging=jar -f pom.xml

接下来我们将starter上传到本地仓库,然后创建测试项目来查看效果。

  • 配置messagepublic.enabled属性后starter才可使用;
  • 当缺少MessagePublic Bean的时候由starter创建defaultEvents的Bean;
  • 引入start项目中可以通过messagestarter.public.evnets属性来配置events属性;

测试start

配置中引入MessagePublic,也可以通过注释掉MessagePublic Bean来使用starter自动配置的Bean。

package com.troyqu.springbootstartmessagetest;

import com.troyqu.MessagePublic;
import com.troyqu.MessagePublicProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    /**
     * 测试@ConditionalOnMissingBean条件
     * 如果
     * @return
     */
    @Bean
    MessagePublic messagePublic(){
        System.out.println("手动初始化MessagePublic Bean: message public创建中...");
        MessagePublicProperties prop = new MessagePublicProperties();
        String events = "不使用ConditionalOnMissingBean初始化-events";
        prop.setEvents(events);
        return new MessagePublic(prop);
    }
}

配置文件配置starter enabled和message events

messagepublic:
  enabled: true
messagestarter:
  public:
    events: event1

启动项目效果展示

2022-01-09 22:12:02.729  INFO 1597 --- [           main] .s.SpringBootStartMessageTestApplication : Starting SpringBootStartMessageTestApplication using Java 1.8.0_281 on qujianfeideMacBook-Pro.local with PID 1597 (/Users/qujianfei/gitProject/spring-demo/spring-boot-start/spring-boot-start-message-test/target/classes started by qujianfei in /Users/qujianfei/gitProject/spring-demo/spring-boot-start/spring-boot-start-message-test)
2022-01-09 22:12:02.734  INFO 1597 --- [           main] .s.SpringBootStartMessageTestApplication : No active profile set, falling back to default profiles: default
手动初始化MessagePublic Bean: message public创建中...
查看监听器中已配置的事件:不使用ConditionalOnMissingBean初始化-events
2022-01-09 22:12:03.872  INFO 1597 --- [           main] .s.SpringBootStartMessageTestApplication : Started SpringBootStartMessageTestApplication in 1.697 seconds (JVM running for 3.106)

总结

  • 通过分析代码了解到自动装配是通过@EnableAutoConfiguration来实现的,并且自动装配的入口类定义在META-INF/spring.factories中key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的value中;
  • 另外也参考starter的实现来手动创建一个starter可以更近一步加深对于starter的理解,主要是更好的理解了starter的设计思想,也可以将这种思想带入到业务中更好的提取一些公共starter;