这一次带你搞懂SpringBoot核心原理(自动配置、事件驱动、Condition)

本文详细剖析了SpringBoot的启动原理,包括自动配置、事件驱动和Condition注解的工作机制。SpringBoot启动时,通过SpringApplicationRunListener发布事件,并利用SPI加载自动配置类。自动配置基于条件注解,如@ConditionalOnProperty,匹配配置文件条件加载Bean。事件驱动扩展了Spring的事件发布和监听。通过理解和掌握这些原理,能够更好地运用和定制SpringBoot应用。
摘要由CSDN通过智能技术生成

 

目录

  • 前言
  • 正文启动原理事件驱动自动配置原理Condition注解原理
  • 总结

前言

SpringBoot是Spring的包装,通过自动配置使得SpringBoot可以做到开箱即用,上手成本非常低,但是学习其实现原理的成本大大增加,需要先了解熟悉Spring原理。如果还不清楚Spring原理的,可以先查看博主之前的文章,本篇主要分析SpringBoot的启动、自动配置、Condition、事件驱动原理。

正文

启动原理

SpringBoot启动非常简单,因其内置了Tomcat,所以只需要通过下面几种方式启动即可:

 
@SpringBootApplication(scanBasePackages = {"cn.dark"}) public class SpringbootDemo { public static void main(String[] args) { // 第一种 SpringApplication.run(SpringbootDemo .class, args); // 第二种 new SpringApplicationBuilder(SpringbootDemo .class)).run(args); // 第三种 SpringApplication springApplication = new SpringApplication(SpringbootDemo.class); springApplication.run(); } } 

可以看到第一种是最简单的,也是最常用的方式,需要注意类上面需要标注@SpringBootApplication注解,这是自动配置的核心实现,稍后分析,先来看看SpringBoot启动做了些什么?在往下之前,不妨先猜测一下,run方法中需要做什么?对比Spring源码,我们知道,Spring的启动都会创建一个ApplicationContext的应用上下文对象,并调用其refresh方法启动容器,SpringBoot只是Spring的一层壳,肯定也避免不了这样的操作。另一方面,以前通过Spring搭建的项目,都需要打成War包发布到Tomcat才行,而现在SpringBoot已经内置了Tomcat,只需要打成Jar包启动即可,所以在run方法中肯定也会创建对应的Tomcat对象并启动。以上只是我们的猜想,下面就来验证,进入run方法:

 
public ConfigurableApplicationContext run(String... args) { // 统计时间用的工具类 StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); // 获取实现了SpringApplicationRunListener接口的实现类,通过SPI机制加载 // META-INF/spring.factories文件下的类 SpringApplicationRunListeners listeners = getRunListeners(args); // 首先调用SpringApplicationRunListener的starting方法 listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 处理配置数据 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); // 启动时打印banner Banner printedBanner = printBanner(environment); // 创建上下文对象 context = createApplicationContext(); // 获取SpringBootExceptionReporter接口的类,异常报告 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 核心方法,启动spring容器 refreshContext(context); afterRefresh(context, applicationArguments); // 统计结束 stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } // 调用started listeners.started(context); // ApplicationRunner // CommandLineRunner // 获取这两个接口的实现类,并调用其run方法 callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { // 最后调用running方法 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; } 

SpringBoot的启动流程就是这个方法,先看getRunListeners方法,这个方法就是去拿到所有的SpringApplicationRunListener实现类,这些类是用于SpringBoot事件发布的,关于事件驱动稍后分析,这里主要看这个方法的实现原理:

 
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); } private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 加载上来后反射实例化 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; } public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; 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 factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } } 

一步步追踪下去可以看到最终就是通过SPI机制根据接口类型从META-INF/spring.factories文件中加载对应的实现类并实例化,SpringBoot的自动配置也是这样实现的。为什么要这样实现呢?通过注解扫描不可以么?当然不行,这些类都在第三方jar包中,注解扫描实现是很麻烦的,当然你也可以通过@Import注解导入,但是这种方式不适合扩展类特别多的情况,所以这里采用SPI的优点就显而易见了。回到run方法中,可以看到调用了createApplicationContext方法,见名知意,这个就是去创建应用上下文对象:

 
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext"; protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch (this.webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break; case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break; default: contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); } 

注意这里通过反射实例化了一个新的没见过的上下文对象AnnotationConfigServletWebServerApplicationContext,这个是SpringBoot扩展的,看看其构造方法:

 
public AnnotationConfigServletWebServerApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } 

如果你有看过Spring注解驱动的实现原理,这两个对象肯定不会陌生,一个是支持注解解析的,另外一个是扫描包用的。上下文创建好了,下一步自然就是调用refresh方法启动容器:

 
private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } } protected void refresh(ApplicationContex
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值