SpringBoot自动装配原理以及自定义starter


前言

大家都知道一个Spring项目的搭建是及其繁琐的,需要写很多xml配置文件,集成一个框架进Spring都需要增加一个xml配置文件。即使我们可以使用javaConfig的方式减少xml的配置,其实也是没有更加方便,也是要建立很多的config的bean。

SpringBoot解决的问题的就是配置的动态注入。在这里SpringBoot的搭建就不必多说了,这篇博文主要侧重的是SpringBoot的自动装配原理。

SpringBoot启动类

首先从SpringBoot启动类注解开始入手,也就是@SpringBootApplication注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 表示这是一个SpringBoot的配置类
@SpringBootConfiguration
// 开启自动配置功能,将以前所需xml配置,交由SpringBoot管理
@EnableAutoConfiguration
// 扫描包
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

}

在这里的注解中,最主要的一个就是@EnableAutoConfiguration注解,这里面有一个@Import(AutoConfigurationImportSelector.class)注解,这里表示注入了一个AutoConfigurationImportSelector class的类,这个类里面有着自动装配的原理。

DeferredImportSelector

AutoConfigurationImportSelector 类实现了DeferredImportSelector,这个类是ImportSelector的一个变种,大家都知道ImportSelector的功能主要是批量注入BeanDefinition。

package org.springframework.context.annotation;

import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.Nullable;

public interface DeferredImportSelector extends ImportSelector {

    @Nullable
    default Class<? extends Group> getImportGroup() {
        return null;
    }

    interface Group {

        void process(AnnotationMetadata metadata, DeferredImportSelector selector);

        Iterable<Entry> selectImports();

        class Entry {

            private final AnnotationMetadata metadata;

            private final String importClassName;

            public Entry(AnnotationMetadata metadata, String importClassName) {
                this.metadata = metadata;
                this.importClassName = importClassName;
            }

            public AnnotationMetadata getMetadata() {
                return this.metadata;
            }
            
            public String getImportClassName() {
                return this.importClassName;
            }

            @Override
            public boolean equals(@Nullable Object other) {
                if (this == other) {
                    return true;
                }
                if (other == null || getClass() != other.getClass()) {
                    return false;
                }
                Entry entry = (Entry) other;
                return (this.metadata.equals(entry.metadata) && this.importClassName.equals(entry.importClassName));
            }

            @Override
            public int hashCode() {
                return (this.metadata.hashCode() * 31 + this.importClassName.hashCode());
            }

            @Override
            public String toString() {
                return this.importClassName;
            }
        }
    }

}

简单看下这个类,发现它继承了ImportSelector,说明它有着ImportSelector所拥有的的所有功能,它有区别于ImportSelector就是有一个

getImportGroup方法,并且有一个Group的接口。

这里我们看下官方的解释

从这段翻译的注释中我们可以发现它所具有的功能主要是2块,

  • 延迟加载,在所有BeanDefinition都解析完成后才会解析DeferredImportSelector所注入的BeanDefiition
  • 提供了import group功能,主要是在提供了在当前组实现排序,过滤功能,不影响其他import group

怎样玩转DeferredImportSelector

上面主要是介绍了DeferredImportSelector的实现功能,下面我们来简单说下如何使用

  1. 首先他有个getImportGroup方法,首先我们得实现这个方法,这个方法返回是一个Group,这个方法具体功能返回null,走原来的ImportSelector逻辑,否则走返回的Group中的processor逻辑
  2. 实现Group接口

下面我简单写了个Demo

package com.dm;

import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

public class MyDeferredImportSelector implements DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.dm.bean.User"};
    }

    /**
     * 返回值为空调用MyDeferredImportSelector#selectImports
     * 不为空调用MyGroup#process selectImports
     */
    @Override
    public Class<? extends Group> getImportGroup() {
        return MyGroup.class;
    }

    private static class MyGroup implements DeferredImportSelector.Group{

        AnnotationMetadata metadata;

        @Override
        public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
            this.metadata = metadata;
        }

        @Override
        public Iterable<Entry> selectImports() {
            return Collections.singletonList(new Entry(this.metadata, "com.dm.bean.Log"));
        }
    }
}

以下面代码打印出所有容器中所有bean,从结果可以反映出getImportGroup,返回值的影响

@SpringBootApplication
@Slf4j
public class MyApplication implements CommandLineRunner {

    @Resource
    private ApplicationContext applicationContext;

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

    @Override
    public void run(String... args) {
        String[] names = applicationContext.getBeanDefinitionNames();
        Arrays.stream(names).forEach(log::info);
//        log.info("项目启动成功,容器注入javaBean:{}个.",names.length);
    }
}

源码级别看DeferredImportSelector加载逻辑

这段逻辑主要是SpringIOC的逻辑,这里不具体跟了,首先找到入口

// Spring 核心类
AbstractApplicationContext#refresh
// 调用bean工厂的后置处理器
AbstractApplicationContext#invokeBeanFactoryPostProcessors
PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
// 一般是解析配置类功能的bean工厂的后置处理器ConfigurationClassPostProcessor,调用带注册的bean工厂的后置处理器,一般用于bean定义的加载,比如@ComponentScan,@Import等
PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
// 解析bean定义
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
// 解析
ConfigurationClassParser#parse

在ConfigurationClassParser#parse这个方法中我们可以看到在配置类完全解析完之后调用了processDeferredImportSelectors方法来处理延时的DeferredImportSelectors。

我们看看processDeferredImportSelectors方法干了什么

processDeferredImportSelectors

private void processDeferredImportSelectors() {
        List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
        this.deferredImportSelectors = null;
        if (deferredImports == null) {
            return;
        }

        //对多个DeferredImportSelector进行排序分组
        deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
        Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
        Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
        for (DeferredImportSelectorHolder deferredImport : deferredImports) {
            // 从这可以看出你实现了自定义的Group就会使用你自己的Group否则就使用DeferredImportSelectorGrouping,就会调用你实现的默认selectImports方法
            // 这里也会对实现了同Group的进行分组
            Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
            DeferredImportSelectorGrouping grouping = groupings.computeIfAbsent(
                    (group != null ? group : deferredImport),
                    key -> new DeferredImportSelectorGrouping(createGroup(group)));
            grouping.add(deferredImport);
            configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
                    deferredImport.getConfigurationClass());
        }
        //正在的调用延时的DeferredImportSelector的selectImport方法
        for (DeferredImportSelectorGrouping grouping : groupings.values()) {
            // getImports方法很重要
            grouping.getImports().forEach(entry -> {
                ConfigurationClass configurationClass = configurationClasses.get(entry.getMetadata());
                try {
                    processImports(configurationClass, asSourceClass(configurationClass),
                            asSourceClasses(entry.getImportClassName()), false);
                }
                catch (BeanDefinitionStoreException ex) {
                    throw ex;
                }
                catch (Throwable ex) {
                    throw new BeanDefinitionStoreException(
                            "Failed to process import candidates for configuration class [" +
                            configurationClass.getMetadata().getClassName() + "]", ex);
                }
            });
        }
    }

getImports

public Iterable<Group.Entry> getImports() {
   for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
       // 调用group的process方法,如果group没有group就为DefaultDeferredImportSelectorGroup,调用默认selectImports方法
      this.group.process(deferredImport.getConfigurationClass().getMetadata(),
            deferredImport.getImportSelector());
   }
    // 调用group的selectImports方法
   return this.group.selectImports();
}

process&&selectImports

AutoConfigurationImportSelector#AutoConfigurationGroup#process&&selectImports

从上面所说的DeferredImportSelector逻辑可以看出springBoot会首先执行process方法,然后在执行selectImports方法,我们进入process方法看看做了什么

@Override
public void process(AnnotationMetadata annotationMetadata,
                    DeferredImportSelector deferredImportSelector) {
    // 核心方法(版本不一样代码可能不一样,但大体逻辑差不太多)
    String[] imports = deferredImportSelector.selectImports(annotationMetadata);
    for (String importClassName : imports) {
        this.entries.put(importClassName, annotationMetadata);
    }
}
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   //&nbsp;从META‐INF/spring.factories中获得候选的自动配置类
   List<String> configurations = getCandidateConfigurations(annotationMetadata,
         attributes);
   // 去重
   configurations = removeDuplicates(configurations);
   // EnableAutoConfiguration属性,获取需要进行排除的类,为了后续的排除
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   //&nbsp;@EnableAutoConfiguration.exclude
  &nbsp;//&nbsp;@EnableAutoConfiguration.excludeName
  &nbsp;//&nbsp;spring.autoconfigure.exclude&nbsp;进行检查是否在所有配置类中,否则报错
   checkExcludedClasses(configurations, exclusions);
   // 排除
   configurations.removeAll(exclusions);
   // 读取spring.factories
   // OnBeanCondition  OnClassCondition  OnWebApplicationCondition
   configurations = filter(configurations, autoConfigurationMetadata);
   //&nbsp;这个方法是调用实现了AutoConfigurationImportListener把候选的配置名单和排除的配置名单传进去做扩展
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return StringUtils.toStringArray(configurations);
}

// 获取所有的配置类
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                                                  AnnotationAttributes attributes) {
    // 从META-INF/spring.factories中读取
    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;
}

没排除前

进行OnBeanCondition OnClassCondition OnWebApplicationCondition匹配排除后

自动配置类原理

这里主要讲了OnBeanCondition OnClassCondition OnWebApplicationCondition是怎么过滤配置类的

我们在getCandidateConfigurations下面的getSpringFactoriesLoaderFactoryClass发现它加载了spring.factories中的EnableAutoConfiguration

我们找到spring-boot-autoconfigure下面的spring.factories,这里面包含了springboot所有会自动装配的类

我们找到一个匹配到的类看看是如何进行匹配的,这里我们就找到DispatcherServletAutoConfiguration

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
@EnableConfigurationProperties(ServerProperties.class)
public class DispatcherServletAutoConfiguration {

    /*
     * The bean name for a DispatcherServlet that will be mapped to the root URL "/"
     */
    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

    /*
     * The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
     */
    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

    @Configuration
    @Conditional(DefaultDispatcherServletCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    protected static class DispatcherServletConfiguration {

        private final WebMvcProperties webMvcProperties;

        public DispatcherServletConfiguration(WebMvcProperties webMvcProperties) {
            this.webMvcProperties = webMvcProperties;
        }

        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet() {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(
                    this.webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(
                    this.webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet.setThrowExceptionIfNoHandlerFound(
                    this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
            return dispatcherServlet;
        }

        @Bean
        @ConditionalOnBean(MultipartResolver.class)
        @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
        public MultipartResolver multipartResolver(MultipartResolver resolver) {
            // Detect if the user has created a MultipartResolver but named it incorrectly
            return resolver;
        }

    }

}

@EnableConfigurationProperties

这个注解主要是表示了启用的配置类,在这里启用的配置类是ServerProperties

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

    /**
     * Server HTTP port.
     */
    private Integer port;

    /**
     * Network address to which the server should bind.
     */
    private InetAddress address;

    @NestedConfigurationProperty
    private final ErrorProperties error = new ErrorProperties();

    /**
     * Whether X-Forwarded-* headers should be applied to the HttpRequest.
     */
    private Boolean useForwardHeaders;

    /**
     * Value to use for the Server response header (if empty, no header is sent).
     */
    private String serverHeader;
    
    。。。。。。。。。。。

简单的copy了一个局部,从这局部我们也可以看出这个自动装配类的配置项主要有哪些,比如server.port,server.address等等

@Conditional

剩下的比较重要的就是@Conditional注解,这些注解共同决定了是否启用这个配置类

注解名字 判断条件
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean
@ConditionalOnMissingBean 容器中不存在指定Bean
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnJndi JNDI存在指定项

在DispatcherServletAutoConfiguration中共有2个@Conditional注解,@ConditionalOnClass和@ConditionalOnWebApplication表示它满足servlet环境且存在DispatcherServlet类就加载成功。

我们在换一个AutoConfiguration看看是否会加载成功,我们选择ElasticsearchAutoConfiguration

@Configuration
@ConditionalOnClass({ Client.class, TransportClientFactoryBean.class })
@ConditionalOnProperty(prefix = "spring.data.elasticsearch", name = "cluster-nodes", matchIfMissing = false)
@EnableConfigurationProperties(ElasticsearchProperties.class)
public class ElasticsearchAutoConfiguration {

从这里看共有2个@Conditional,@ConditionalOnClass和@ConditionalOnProperty

  • 必须存在Client.class和TransportClientFactoryBean.class
  • 这里有个matchIfMissing,true表示不匹配也可以,false表示必须匹配,而且spring.data.elasticsearch.cluster-nodes必须存在

springboot自定义starter使用

再使用springboot starter之前,首先先了解下命名规范

使用springboot starter会需要定义2个jar包,一个starter包一个autoconfigure包

命名规范

官方命名空间

  • 模式:spring-boot-starter-模块名,spring-boot-autoconfigure
  • 举例:spring-boot-starter-web

自定义命名空间

  • 模式:模块-spring-boot-starter,模块-spring-boot-autoconfigure
  • 举例:mybatis-spring-boot-starter,mybatis-spring-boot-autoconfigure

Demo

父pom

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.8.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <groupId>com.dm</groupId>
  <artifactId>customer-springboot-starter</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>customer-springboot-starter</name>
  <packaging>pom</packaging>
  <description>SpringBoot自定义starter</description>

  <modules>
    <module>dm-spring-boot-starter</module>
    <module>dm-spring-boot-autoconfigure</module>
  </modules>

  <properties>
    <java.version>1.8</java.version>
  </properties>

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

</project>

dm-spring-boot-autoconfigure

这个包主要就是自动装配的功能

pom
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>com.dm</groupId>
    <artifactId>customer-springboot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <artifactId>dm-spring-boot-autoconfigure</artifactId>
  <name>dm-spring-boot-autoconfigure</name>
  <description>自动配置</description>

  <properties>
    <java.version>1.8</java.version>
  </properties>

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

    <!--‐导入配置文件处理器,配置文件进行绑定就会有提示-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.20</version>
      <!--此配置保证lombok不会被父引用依赖,引入此依赖包不会引用lombok依赖-->
      <optional>true</optional>
    </dependency>
  </dependencies>

</project>
DmAutoConfiguration
@Configuration
@ConditionalOnProperty(prefix = "dm.enable", name = "auto", havingValue = "true")
@EnableConfigurationProperties(DmProperties.class)
@ConditionalOnClass({Slf4j.class, Data.class})
@Slf4j
public class DmAutoConfiguration {

    @Resource
    DmProperties dmProperties;

    @Bean
    @ConditionalOnMissingBean
    public UserServiceClient init() {
        log.info("================================初始化UserServiceClient================================");
        UserServiceClient userServiceClient = new UserServiceClient();
        String name = dmProperties.getName();
        userServiceClient.setName(name);
        userServiceClient.setWelcome("欢迎回来" + name);
        return userServiceClient;
    }
}
DmProperties
@ConfigurationProperties(prefix = "dm")
@Data
public class DmProperties {

    private String name;

}
UserServiceClient
@Data
public class UserServiceClient {

    private String name;

    private String welcome;
}

在DmAutoConfiguration注解上我们发现生效条件

  • dm.enable.auto=true
  • 有lombok包下的Slf4j.class,Data.class

resources下面创建META-INF/spring.factories文件

spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.starter.dm.DmAutoConfiguration

dm-spring-boot-starter

这个包简单了,只是维护了一个pom包,是个空项目

pom
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.dm</groupId>
    <artifactId>customer-springboot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <artifactId>dm-spring-boot-starter</artifactId>
  <name>dm-spring-boot-starter</name>
  <description>启动器</description>

  <properties>
    <java.version>1.8</java.version>
  </properties>

    <dependencies>
      <!--引入autoconfigure-->
      <dependency>
        <groupId>com.dm</groupId>
        <artifactId>dm-spring-boot-autoconfigure</artifactId>
        <version>1.0-SNAPSHOT</version>
      </dependency>

      <!--如果当前starter 还需要其他的类库就在这里引用-->
    </dependencies>
</project>

使用项目

项目引入dm-spring-boot-starter包即可

 <dependency>
     <groupId>com.dm</groupId>
     <artifactId>dm-spring-boot-starter</artifactId>
     <version>1.0-SNAPSHOT</version>
 </dependency>
@SpringBootApplication
@Slf4j
public class MyApplication implements CommandLineRunner {

    @Resource
    private UserServiceClient userServiceClient;

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

    @Override
    public void run(String... args) {
        log.info(userServiceClient.toString());
    }
}
配置类

application.yml

dm:
  enable:
    auto: true
  name: 哈哈哈哈
验证


文章作者: dm
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 dm !
评论
 上一篇
MySQL单机及主从搭建 MySQL单机及主从搭建
MySQL单机搭建MySQL下载 这里使用的版本是mysql-8.0.20-linux-glibc2.12-x86_64 MySQL下载有多种方式,可以选择官网也可以选择镜像 官网地址:https://downloads.mysql.co
2022-06-15
下一篇 
SpringBoot使用外部tomcat SpringBoot使用外部tomcat
前言大家都知道SpringBoot有内置Tomcat的功能,在启动原理中有介绍SpringBoot内置Tomcat的原理。那么如果我们不像使用SpringBoot的内置Tomcat,想用自己的web容器怎么操作。springboot支持默认
2022-04-27
  目录