SpringBootܻ原理深入及源码剖析
依赖管理
问题一:为什么某些Maven依赖不需要指定版本号?
因为spring-boot-starter-parent中已经定义了常用依赖匹配的版本号.
按住Ctrl,点击spring-boot-starter-parent的版本号
可见pom文件中的parent是spring-boot-dependencies,然后再次按住Ctrl,点击版本号
我们会发现这个pom中维护了一大堆依赖的版本号
问题二:Springboot是如何引入这些jar包的
在我们做web项目时,除了上面的parent外,还会引入spring-boot-starter-web,而在这个依赖中,则定义了web项目需要的jar依赖.
依据不同的使用场景,spring官方也提供了不同的starter封装
问题三: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);
}
}
}
实际上,加载的就是这个路径下的配置文件
以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文件,指定配置类全路径
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);
}
}
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