前言
大家都知道一个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的实现功能,下面我们来简单说下如何使用
- 首先他有个getImportGroup方法,首先我们得实现这个方法,这个方法返回是一个Group,这个方法具体功能返回null,走原来的ImportSelector逻辑,否则走返回的Group中的processor逻辑
- 实现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);
// 从META‐INF/spring.factories中获得候选的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 去重
configurations = removeDuplicates(configurations);
// EnableAutoConfiguration属性,获取需要进行排除的类,为了后续的排除
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// @EnableAutoConfiguration.exclude
// @EnableAutoConfiguration.excludeName
// spring.autoconfigure.exclude 进行检查是否在所有配置类中,否则报错
checkExcludedClasses(configurations, exclusions);
// 排除
configurations.removeAll(exclusions);
// 读取spring.factories
// OnBeanCondition OnClassCondition OnWebApplicationCondition
configurations = filter(configurations, autoConfigurationMetadata);
// 这个方法是调用实现了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: 哈哈哈哈
验证

