SpringBoot
简介SpringBoot 是 Spring 项目中的一个子工程,Spring Boot 与 Spring Framework、Spring Cloud 并称为 Spring 的三大核心产品。
SpringBoot 的官方地址:https://spring.io/projects/spring-boot
从官方文档我们可以看出,Spring Boot 是为了让我们能够简单快速地搭建独立的、能够直接运行的基于 Spring 的产品级应用程序。
SpringBoot 通过 Spring 平台和大量的第三方库,只需要最小化的 Spring 配置就能让我们快速的构建庞大的 Spring 项目,做到开箱即用,迅速上手,让我们关注与业务而非配置。
SpringBoot 主要特点是:
war
文件;在 SpringBoot 开始流行之前,我们一般都是使用 Strust2 或者 SpringMVC 框架来开发 web 应用,但是这两个框架都有一个特点就是配置非常的繁琐,要写一大堆的配置文件,虽然 Spring 在支持了注解开发之后稍微有些改善,但有的时候还是会比较麻烦。
这种情况下,SpringBoot 的优势就体现出来了,通过 SpringBoot 特有的自动配置就可以做到只需一个 properties
或 yml
配置文件就可以简化 SpringMVC 中需要在 xml 中配置的一大堆的 bean。
SpringBoot
自动配置原理SpringBoot
的自动配置原理每个 SpringBoot 工程的主启动类都有一个 @SpringBootApplication
注解,比如:
@SpringBootApplication(scanBasePackages = {"com.example.demo"})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
我们点进入看一下这个注解:
@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 {
可以看到 SpringBootApplication 注解是由一系列的注解组合而成,其中最核心的就是 @EnableAutoConfiguration
注解,这个注解的含义就是开启自动装配,能够直接被各种组件的 bean 装配到 IOC 容器中,我们点进去看一下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
同样,EnableAutoConfiguration 也是一个复合注解,其中关键的注解是 @Import(AutoConfigurationImportSelector.class)
,这个 Import 注解导入了 AutoConfigurationImportSelector.class 这个类,而这个类是我们自动装配的导入选择器,点进这个类去,可以看到一个 selectImports()
核心方法:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 加载元数据
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 获取自动配置的 Entry 实体
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
我们点进 getAutoConfigurationEntry
这个方法去看一下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// 省略代码
......
// 核心代码:获取候选的配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 后面是对配置的去重、排除等操作
......
}
这里面的核心方法就是 getCandidateConfigurations()
,这个方法的作用是加载所有自动配置类的名字,源码如下:
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;
}
我们跟踪 loadFactoryNames() 方法来到了最终加载资源的方法 loadSpringFactories()
,里面加载资源的核心代码是:
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 properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
// 解析里面的 key-value 值
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
其中获取资源地址中的 FACTORIES_RESOURCE_LOCATION
常量的定义值为 META-INF/spring.factories
:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
这块代码的核心流程是这样子的:
META-INF/spring.factories
文件,得到该文件的 URL 路径地址;properties
对象;我们进入 SpringBoot 自动配置的 jar 包去看一下 META-INF/spring.factories 文件内容,具体所在路径如下(以 2.2.10版本为例):
在文件内容中找到 EnableAutoConfiguration 的部分值如下:
所以,在加载自动配置组件的代码中,factoryTypeName 就是 org.springframework.boot.autoconfigure.EnableAutoConfiguration,而 factoryImplementationName 就是对应的每个值,比如:org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,代表要自动配置的类。相当于把 EnableAutoConfiguration 值里面的这些组件都加到了容器中。
原理分析总结:
至此,我们基本上已经知道了整个自动装配的流程:扫描并获取所有 jar 包类路径下的 META-INF/spring.factories 文件的 URL 地址,将这些文件加载成 properties 对象,然后获取其中 EnableAutoConfiguration
所有的值,每个值都是自动配置类,最终都会添注入到容器中。
Starter
组件的自动配置原理上面我们分析了 SpringBoot 自动配置的原理,那么组件是如何进行自动装配的呢?SpringBoot 的 META-INF/spring.factories 文件中包含了上百个自动配置类,难道 SpringBoot 启动的时候就会把这些所有组件实例化到容器中?
下面,我们就来分析一下:自动配置的组件是如何自动装配到容器中的。
我们以 Redis 为例,从 META-INF/spring.factories 文件中的 EnableAutoConfiguration 值中可以找到 Redis 的自动配置类为:org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,我们点进去看一下这个自动配置类的源码:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
这里面有4个注解修饰着 RedisAutoConfiguration 自动配置类,我们逐一分析它们的作用。
先分析 @Configuration
这个注解,它表示所修饰的类为配置类,熟悉 Spring 开发的都知道,在配置类中,我们可以通过 @Bean
注解很方便的将组件的 Bean 注册到 IOC 容器中。这里,Redis 为我们注册了 redisTemplate 和 stringRedisTemplate 两个 Bean,用于操作 Redis 服务器。同时使用 @ConditionalOnMissingBean
注解表示只有当 Bean 不存在时才注册到容器中。
接着再来分析一下 @EnableConfigurationProperties(RedisProperties.class)
这个注解,它的作用是用来生效 @ConfigurationProperties
注解的,显然,RedisProperties.class 就是个被 @ConfigurationProperties 注解所修饰的配置属性类:
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
......
/**
* Redis server host.
*/
private String host = "localhost";
/**
* Login password of the redis server.
*/
private String password;
/**
* Redis server port.
*/
private int port = 6379;
......
}
配置属性类是用来从 properties 或 yml 配置文件中加载配置值的,通过 prefix
还可以指定配置的前缀名称,所以,我们可以这样配置 Redis 所需的属性配置:
spring:
redis:
host: http://localhost
port: 6379
password: 1234
这样,RedisProperties 在实例化的时候就会读取对应的值注入到相应的属性上。
再来看一下 @Import
这个注解,它给我们的自动配置类导入了两个类:LettuceConnectionConfiguration.class 和 JedisConnectionConfiguration.class ,它们是用来连接 Redis 服务器的配置类,一个是使用 Lettuce,另一个使用 Jedis,这两个都是常用的连接 Redis 的组件。以 JedisConnectionConfiguration.class 为例:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
class JedisConnectionConfiguration extends RedisConnectionConfiguration {
// 连接配置依赖 RedisProperties 属性配置
JedisConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
ObjectProvider<RedisClusterConfiguration> clusterConfiguration) {
super(properties, sentinelConfiguration, clusterConfiguration);
}
可以看到 Redis 连接配置类需要依赖 RedisProperties 属性配置类,很明显,只有通过属性配置类我们才能获取 Redis 服务器的地址、端口、密码等信息,有了这些信息才能连接到 Redis 服务器。
最后就是最关键的注解:@ConditionalOnClass(RedisOperations.class)
,这个注解决定了 RedisAutoConfiguration 是否会生效,它的意思是只有当存在 RedisOperations.class 这个类的时候才会生效 RedisAutoConfiguration,而 RedisOperations.class 是一个最终执行操作 Redis 命令的类,它的定义在 spring-data-redis 包下,所以,需要引入对应的依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
现在,我们可以对 Redis 组件自动配置原理进行总结了:
@ConditionalOnClass
注解的限制,我们需要提供 RedisOperations.class 这个类的定义,而这个类在 spring-data-redis 包下,所以,只有引入了相应的依赖才能生效整个组件;redisTemplate
和 stringRedisTemplate
这两个 Bean 用来操作 Redis;至此,我们已经基本知道了组件是如何被自动装配的了,关键点就是在 META-INF/spring.fatories 文件包含相应的自动配置类,然后再自动配置类中实例化相应的组件、属性配置类等各种 bean。
1)条件依赖注解:
@ConditionalOnClass
:应用中包含某个类时,对应的配置才生效;@ConditionalOnMissingClass
:应用中不包含某个类时,对应的配置才生效;@ConditionalOnBean
:Spring 容器中存在指定 class 的实例对象时,对应的配置才生效;@ConditionalOnMissionBean
:Spring 容器中不存在指定 class 的实例对象时,对应的配置才生效;@ConditionalOnProperty
:项目配置文件中存在指定属性值时,对应的配置才生效;@ConditionalOnResource
:指定资源文件存在时,对应的配置才生效;@ConditionalOnWebApplication
:当前处于 Web 环境(WebApplicationContext)时,对应的配置才生效;@ConditionalOnNotWebApplication
:当前不处于 Web 环境时,对应的配置才生效;@ConditionalOnExpression
:项目配置文件中存在指定属性值时,对应的配置才生效;与 ConditionalOnProperty 不同的是,该注解使用的是 SpringEL 表达式;2)加载顺序注解:
@AutoConfigureAfter
:在指定的 Configuration 类之后加载;@AutoConfigureBefore
:在指定的 Configuration 类之前加载;@AutoConfigureOrder
:指定 Configuration 类的加载顺序,默认值为0;