SpringBootܻ原理深入及源码剖析

依赖管理

问题一:为什么某些Maven依赖不需要指定版本号?

因为spring-boot-starter-parent中已经定义了常用依赖匹配的版本号.

按住Ctrl,点击spring-boot-starter-parent的版本号

image-20221013155539028

可见pom文件中的parent是spring-boot-dependencies,然后再次按住Ctrl,点击版本号

image-20221013155647320

我们会发现这个pom中维护了一大堆依赖的版本号

image-20221013155755730

问题二:Springboot是如何引入这些jar包的

在我们做web项目时,除了上面的parent外,还会引入spring-boot-starter-web,而在这个依赖中,则定义了web项目需要的jar依赖.

image-20221013160305222

依据不同的使用场景,spring官方也提供了不同的starter封装

image-20221013160428204

问题三:Springboot是如何进行自动配置的

我们进入查看启动类上的SpringBootApplication注解

@Target({ElementType.TYPE}) // 注解适用范围,Type表示注解可以描述类,接口,注解或枚举类
@Retention(RetentionPolicy.RUNTIME) // 表示注解的生命周期,Runtime运行时
@Documented // 表示注解可以记录在javadoc中
@Inherited // 表示可以被子类继承该注解
@SpringBootConfiguration // 表明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan( // 包扫描器
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
}
1.@SpringBootConfiguration

声明当前类为配置类



@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}

2.@EnableAutoConfiguration

表示开启自动配置功能,也是实现自动化配置的注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 自动配置包
@Import({AutoConfigurationImportSelector.class}) // 自动配置类扫描导入
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

Class<?>[] exclude() default {};

String[] excludeName() default {};
}

  • @AutoConfigurationPackage
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({AutoConfigurationPackages.Registrar.class}) //导入Registrar中注册的组件
public @interface AutoConfigurationPackage {
String[] basePackages() default {};

Class<?>[] basePackageClasses() default {};
}

我们继续查看Registrar中的实现

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}

public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}

可以发现其中有一个registerBeanDefinitions方法,就是将主程序类所在包及所有子包下的组件扫描的Spring容器中

  • @Import({AutoConfigurationImportSelector.class})

将AutoConfigurationImportSelector类导入到容器中,这个类可以将所有符合条件的@Configuration注解类配置加载到Spring的IOC容器中.

public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}

深入loadMetadata方法

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
// 需要加载的配置类的类路径
return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
}

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
// 读取spring-autoconfigure-metadata.properties信息生成的url
Enumeration<URL> urls = classLoader != null ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
Properties properties = new Properties();

// 解析urls枚举对象中的信息,封装成properties对象并加载
while(urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource((URL)urls.nextElement())));
}
// 根据封装好的properties对象生成AutoConfigurationMetada对象
return loadMetadata(properties);
} catch (IOException var4) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", var4);
}
}

深入getCandidateConfigurations方法

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
/**
* getSpringFactoriesLoaderFactoryClass 返回EnableAutoConfiguration.class
* getBeanClassLoader 返回beanClassLoader
*/
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}

protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}

继续点开loadFactoryNames方法

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
// 如果类加载器不为空,则加载spring.factories文件,封装其中Configuration文件为Enumeration
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
MultiValueMap<String, String> result = new LinkedMultiValueMap();
// 循环Enumeration对象,封装为Properties对象
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();

while(var6.hasNext()) {
Map.Entry<?, ?> entry = (Map.Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;

for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}

cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}

实际上,加载的就是这个路径下的配置文件

image-20221013164539737

以web项目为例,在项目中加入了Web环境依赖启动器,对应的WebMvcAutoConfiguration就会生效,打开这个配置类,就会发现该类通过全注解的方式对Spring所需环境进行了默认配置.

总结

springboot底层自动配置的步骤是:

1.springboot应用启动

2.@SpringBootApplication起作用

3.@EnableAutoConfiguration

4.@AutoConfigurationPackage:扫描加载启动类同级及子级目录下的配置类

5.@Import(AutoConfigurationImportSelector.class):加载classpath上jar中的META-INF/spring.factories文件中指定的配置类

3.@ComponentScan

扫描启动类下面的bean对象

总结
|- @SpringBootConfiguration
|- @Configuration //通过javaConfig的方式添加组件到IOC容器中
|- @EnableAutoConfiguration
|- @AutoConfigurationPackage //自动配置包,与@ComponmentScan扫描到的添加到IOC中
|- @Import(AutoConfigurationImportSelector.class) // 扫描META-INF/spring.factories中指定的bean到IOC容器中
|- @ComponentScan //包扫描

自定义Starter

1.引入自动配置依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>

2.定义JavaBean

@EnableConfigurationProperties(SimpleBean.class)
@ConfigurationProperties(prefix = "simple")
@Data
public class SimpleBean {

private int id;

private String name;

}

3.编写配置类

@Configuration
public class MyAutoConfiguration {

static {
System.out.println("MyAutoConfiguration init....");
}

@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}

}

4.配置启动类加载路径

在resources下创建/META-INF/spring.factories文件,指定配置类全路径

image-20221014135815091

5.测试starter

在测试包中引入自定义starter

<dependency>
<groupId>org.example</groupId>
<artifactId>my-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

在配置文件application.properties中加入前缀配置

simple.id=1
simple.name=test

编写测试方法

@Autowired
private SimpleBean simpleBean;

@Test
public void testStarter(){
System.out.println(simpleBean);
}

执行原理

我们从启动类开始断点查看程序执行步骤,首先是调用了SpringApplication的run方法.

在run方法中,主要做了两个操作,一个是SpringApplication的初始化,以及调用run()启动项目

(new SpringApplication(primarySources)).run(args);

1.SpringApplication初始化

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//把项目启动类.class设置为属性存储起来
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
//判断当前WebApplicationType类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//设置初始化器,最后会调用这些初始化器
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//设置监听器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//用于推断并设置项目main()方法启动的主程序启动类
this.mainApplicationClass = this.deduceMainApplicationClass();
}

2.run()方法执行

 public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
// 1.获取监听器并启动
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();

Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 2.根据SpringApplicationRunListeners以及参数来准备环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
// 准备Banner打印器,就是console中输出的艺术字体
Banner printedBanner = this.printBanner(environment);
// 3.创建Spring容器
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
// 4.Spring容器前置处理
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 5.刷新容器
this.refreshContext(context);
// 6.容器刷新后置处理
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
// 7.发出结束执行的事件
listeners.started(context);
// 返回容器
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}

try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}

image-20221014143737733

Springboot缓存管理

默认缓存

1.启动类使用@EnableCaching注解

@SpringBootApplication
@EnableCaching // 开启SpringBoot基于注解的缓存管理支持
public class Springboot01DemoApplication {

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

}

2.查询方法添加@Cacheable注解配置缓存

// 根据id查询实体类
@Cacheable(cacheNames = "a")
public A findById(String id){
A a = dao.findById(id)
return a;
}

springboot默认使用的是SimpleCacheConfiguration配置类,用CurrentMap当作底层数据结构进行数据缓存

整合Redis进行缓存

1.添加Spring Data Redis依赖启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

当我们加入此依赖后,Springboot会使用RedisCacheConfiguration当作生效的自动配置类,容器使用的缓存管理器是RedisCacheManager类,创建的Cache是RedisCache

2.添加redis连接配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
3.将缓存对象进行序列化

对缓存的实体类实现Serializable,添加序列化ID

4.使用上面同样的注解,即可实现基于Redis的缓存