SpringBoot理解

1. spring-boot-starter

决定一个项目是否为SpringBoot项目的关键是看是否集成了spring-boot-starter-parent。

1.1 spring-boot-starter-parent

spring-boot-starter-parent中提前配置好了常见第三方组件的版本号,方便直接引用,若遇到需要指定版本的场景,推荐使用如下方法指定版本:

  1. 查询spring-boot-depedencies中该依赖的标签名;
  2. 在当前项目中重写该配置项。

1.2 starter的一些默认配置

SpringBoot提供了大量的官方starter,供开发者使用,其命名方式spring-boot-starter-,若要自定义starter,官方推荐命名为-spring-boot-starter

无论是官方starter还是第三方starter,都要依赖于spring-boot-starter,而starter的自动装配核心依赖是spring-boot-autoconfigure。
SpringBoot提供了大量的开发场景下的默认配置,以达到快速开发的目的。例如:

  1. SpringBoot默认扫描启动类所造包下的所有注解,注册成为bean
  2. SpringBoot默认servlet请求中的上传文件的最大大小是1MB
  3. ……

这些配置的值最终都会映射到某一个类上,这些类都会被注册为bean。

2. 一些核心注解

2.1 @Configuration

带有@Configuration注解的类可以看做是原生Spring中的xml配置文件,每一个带有@Bean的方法都相当于是注册了一个bean,bean名称默认是方法名,可以通过name属性指定。

proxyBeanMethods

proxyBeanMethods属性是配置类中较为重要的属性,也是SpringBoot实现Full/Lite模式的关键属性。
proxyBeanMethods含义是设置配置类是否启用代理模式,默认为true。true模式下,每次调用配置类中返回bean的方法时,都会先去容器中查找是否有当前实例,保证了实例的单例性,此时就称为Full模式;当proxyBeanMethods为false时,配置类不会开启代理模式,每次调用配置类中的方法获取bean,不进行判断,直接创建一个新的实例并返回,此时就是Lite模式。

俩种模式的选取建议:当配置类中的Bean没有被其他组件依赖时,使用Lite模式,加快容器启动速度,减少判断;当配置类中的bean被依赖时,使用Full模式。

2.2 @Import

@Import用于手动注册bean到容器中,可以添加到@SpringBootApplication(启动类)、@Configuration(配置类)、@Component(组件类)上。

1
@Import{XXX1.class, XXX2.class}

2.3 @Conditional

@Conditional含义是条件装配,Spring中提供了很多派生注解来满足不同场景下的条件装配,例如:

  • @ConditionalOnBean(name=”xxx”) 当容器中存在指定名称的bean实例时,注册当前bean
  • @ConditionalOnMissingBean (name=”xxx”) 当容器中不存在指定名称的bean实例时,注册当前bean
  • @ConditionalOnWarDeployment 当项目为war包部署时,注册当前bean
  • @ConditionalOnProperty 当存在某个参数时候,注册当前bean
  • ……

2.4 @ImportResource

@ImportResource的作用是解析原生spring中的xml配置文件,导入其中的配置信息

1
@ImportResource("classpath:xxx.xml")

2.5 @ConfigurationProperties

2.5.1 @Component+@ConfigurationProperties

@ConfigurationProperties的作用是将核心配置文件配置文件中的配置信息绑定到bean中的属性,该注解只能使用在被容器管理的bean上

1
2
3
@Component
@ConfigurationProperties(prefix = "xxx")
public class XXX {}
2.5.2 @EnableConfigurationProperties+@ConfigurationProperties

大多数场景下,SpringBoot回避免直接在某个类上加@Component注解,这时就需要第二种获取配置信息的注解,在某个类上加@ConfigurationProperties注解用于接收配置信息,在另外一个配置类上加@EnableConfigurationProperties并且指定刚刚的参数映射类,可以达到一样的效果,SpringBoot底层大量使用了这种方式,例如kafka自动配置:

1
2
3
4
5
6
7
@ConfigurationProperties(prefix = "spring.kafka")
public class KafkaProperties {}

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(KafkaTemplate.class)
@EnableConfigurationProperties(KafkaProperties.class)
public class KafkaAutoConfiguration {}

3. 自动装配原理入门

由源码可知,@SpringBootApplication是一个合成注解,其中包含的三个核心注解,实现了自动装配的功能。

1
2
3
4
5
6
7
8
9
10
11
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)

3.1 @SpringBootConfiguration

源码可知,@SpringBootConfiguration其内部就是一个@Confiuration配置类,并且设置了proxyBeanMethods默认值为true;

3.2 @ComponentScan

包扫描注解的高级用法,引入了SpringBoot中俩个自定义的扫描器,完成了包扫描的设置。

3.3 @EnableAutoConfiguration

@EnableAutoConfiguration是实现自动装配最核心的注解,也是一个合成注解,其内部是:

1
2
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
3.3.1 @AutoConfigurationPackage

@AutoConfigurationPackage内部:

1
@Import({Registrar.class})

而Registrar源码如下

1
2
3
4
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}

这段代码的实际含义就是找到主启动类所在的包,扫描这个包下的所有注解,完成bean注册。

3.3.2 @Import({AutoConfigurationImportSelector.class})

引入AutoConfigurationImportSelector类,最终的导入逻辑落到了这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}

// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;

由此可知,SpringBoot在启动时会扫描所有jar包下的META-INF/spring.factories文件,加载其中的配置信息,关键在于spring-boot-autoconfigure-2.6.3.jar中的META-INF/spring.factories文件,这个文件中有一段配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
......

所以,综上所述,SpringBoot做的事其实就是将J2EE开发所以用到的所有解决方案整合到spring-boot-autoconfigure-2.6.3.jar中的META-INF/spring.factories文件中,SpringBoot在容器启动时,会加载这个文件中所有配置,但是查看autoconfigure包中的源码可以发现,其中所有的配置类都是条件装配(@Conditional),这样,只要导入了对应的starter,容器就会帮我们完成自动配置。

由autoconfigure包中的源码可知,SpringBoot会在底层做好所有的配置,但如果用户配置了自己的实例,则以用户的优先。

总结:

SpringBoot启动时会加载所有的自动配置类(XXXAutoConfiguration),
每个自动配置类会按条件进行注册,大部分配置类还会关联外部的配置项,所以SpringBoot整合的组件都可以从application.properties中获取配置信息。由此,SpringBoot既完成了自动装配,按需加载的容器实现,又极大地简化了开发配置。

Author: Aaron
Link: https://xjsir.cn/2022/11/12/springboot/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.