零、前言
Spring简直是java企业级应用开发人员的春天,我们可以通过Spring提供的ioc容器,避免硬编码带来的程序过度耦合。但是,启动一个Spring应用程序也绝非易事,他需要大量且繁琐的xml配置,开发人员压根不能全身心的投入到业务中去。
因此,SpringBoot诞生了,虽然本质上还是属于Spring,但是SpringBoot的优势在于以下两个特点:
- 约定大于配置
SpringBoot定义了项目的基本骨架,例如各个环境的配置文件统一放到resource中,使用active来启用其中一个。配置文件默认为application.properties,或者yaml、yml都可以。
- 自动装配
以前在Spring使用到某个组件的时候,需要在xml中对配置好各个属性,之后被Spring扫描后注入进容器。
而有了SpringBoot后,我们仅仅需要引入一个starter,就可以直接使用该组件,如此方便、快捷,得益于自动装配机制。
一、定义
什么是SpringBoot的自动装配?
SpringBoot 自动装配,英文是 Auto-Configuration ,是指在Spring Boot启动时,通过一系列机制和注解,将第三方组件或配置类加载到Spring容器中,并生成相应的Bean对象供使用。这一过程极大地简化了开发工作,提高了效率,为 SpringBoot 框架的 "开箱即用"提供了基础支撑;
具体来说,SpringBoot的自动装配主要依赖以下几个关键点:
- 注解 :SpringBoot提供了多个注解来实现自动装配,例如
@SpringBootApplication
、@EnableAutoConfiguration
和@ComponentScan
等。这些注解帮助SpringBoot识别并加载需要自动配置的类。 - 条件化配置 :SpringBoot利用条件化配置(
@Conditional
注解)来决定是否进行某些配置。例如,如果类路径下存在某个特定的jar包,则自动配置相应的功能。 - 元数据文件 :SpringBoot会在启动时扫描
META-INF/spring.factories
文件,从中获取需要进行自动装配的类,并执行这些类中的配置操作。 - 场景启动器:SpringBoot还引入了场景启动器(如WebMvcAutoConfiguration),根据项目中添加的依赖自动配置相应的功能。
- 约定大于配置:SpringBoot遵循"约定大于配置"的原则,通过默认配置减少开发者手动编写配置的必要性。例如,在没有明确配置的情况下,SpringBoot会自动配置数据库连接、视图解析器等。
- SPI机制 :自动装配与Spring的SPI(Service Provider Interface)机制相似,都是通过反射机制动态加载和实例化组件。更多SPI讲解:深入理解 Java SPI - 概念、原理、应用
二、启动流程
SpringBoot启动流程的简化版代码:
java
public static void run(Class<?> primaryClass) {
//1.创建一个 ApplicationContext 实例,即我们常说的IoC容器
ApplicationContext context = createApplicationContext();
// 2.将主类(primaryClass)注册到IoC容器中(简单但很重要的一步)
loadSourceCLass(context, primaryClass);
// 3.递归加载并处理所有的配置类
processConfigurationClasses(context);
// 4.实例化所有的单例Bean(Singleton Bean)
instantiateSingletonBeans(context);
// 5.如果时web应用,则启动web服务器(如Tomcat)
startWebServer(context);
}
图解:
详解第三步核心流程:
图解:
三、原理剖析
一切都从注解 @SpringBootApplication 说起:
java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@SpringBootApplication
**@SpringBootApplication **注解又是一个组合注解
java
@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 {...}
其中**@Target**、** @Retention**、@Documented 与**@Inherited**是元注解,即对注解的注解;
还包含了@SpringBootConfiguration与@EnableAutoConfiguration
以下注解都将省略这些元注解
@SpringBootConfiguration
java
@Configuration
public @interface SpringBootConfiguration {
}
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
可以看到,@SpringBootConfiguration 注解本质上是一个 @Configuration 注解,表明该类是一个配置类。
而 @Configuration 又被 @Component 注解修饰,代表任何加了**@Configuration** 注解的配置类,都会被注入进Spring容器中。
@EnableAutoConfiguration
该注解开启了自动配置的功能
java
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {...}
本身又包含了以下两个注解
@AutoConfigurationPackage 与 @Import(AutoConfigurationImportSelector.class)
@AutoConfigurationPackage
以前我们直接使用Spring的时候,需要在xml中的 context:component-scan 中定义好base-package,那么 Spring 在启动的时候,就会扫描该包下及其子包下被@Controller、@Service与@Component标注的类,并将这些类注入到容器中。
@AutoConfigurationPackage 则会将被注解标注的类,即主配置类,将主配置类所在的包当作base-package,而不用我们自己去手动配置了。
这也就是为什么我们需要将主配置类放在项目的最外层目录中 的原因。
那么容器是怎么知道主配置当前所在的包呢?
我们注意到,@AutoConfigurationPackage 中使用到了@Import注解
@Import注解会直接向容器中注入指定的组件
引入了AutoConfigurationPackages类中内部类 Registrar
java
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
而getName将会返回主配置类所在的包路径;这样,容器就知道了主配置类所在的包,之后就会扫描该包及其子包。
@Import(AutoConfigurationImportSelector.class)
该注解又引入了AutoConfigurationImportSelector类,而AutoConfigurationImportSelector中有一个可以获取候选配置的方法,即getCandidateConfigurations
java
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
其中核心方法SpringFactoriesLoader.loadFactoryNames ,第一个参数是EnableAutoConfiguration.class
loadFactoryNames方法
java
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) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
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 = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
可以看得出,loadSpringFactorie方法,会从META-INF/spring.factories文件中读取配置,将其封装为Properties对象,将每个key作为返回的map的key,将key对应的配置集合作为该map的value。
而loadFactoryNames则是取出key为EnableAutoConfiguration.class的配置集合
我们查看META-INF/spring.factories的内容(完整路径:\org\springframework\boot\spring-boot\2.7.17\spring-boot-2.7.17.jar!\META-INF\spring.factories)
可以看到,EnableAutoConfiguration对应的value,则是我们在开发中经常用到的组件,比如Rabbit、Elasticsearch与Redis等中间件。
到这里,我们可以知道getCandidateConfigurations方法会从META-INF/spring.factories中获取各个组件的自动配置类的全限定名。
图解
总结一下
参考
SpringBoot的自动装配原理、自定义starter与spi机制,一网打尽
目前讲的最透彻的SpringBoot自动配置