[关闭]
@Catyee 2021-08-09T13:04:18.000000Z 字数 11912 阅读 357

SpringBoot

Spring


Springboot是Spring组件的一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,使开发者能快速上手。

一、SpringBoot基本概念

二、SpringBoot如何管理依赖

SpringBoot采用了一个父工程来对版本进行统一的管理,所以大部分第三方包,我们无需关注版本,个别没有纳入SpringBoot管理的,才需要设置版本号。
这个父工程就是spring-boot-starter-parent,往上溯源,这个父工程的顶部就是一个pom文件,在这个pom文件中定义了各种依赖的jar包和版本。

SpringBoot将所有的常见开发功能,分成了一个个场景启动器(starter),这样我们需要开发什么功能,就导入什么场景启动器依赖即可。
比如我们需要开发一个web项目,就只需要把web starter引入即可:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>

这些starter也会引用其它依赖的的starter,比如web starter依赖spring mvc,但是在web starter中已经将这些依赖关系设置好了,我们只需要引入一个web starter就可以了。

三、SpringBoot自动化配置原理

SpringBoot项目都会有一个启动类,这个启动类上面会加一个@SpringBootApplication的注解:

  1. @SpringBootApplication
  2. public class Application {
  3. public static void main(String[] args) {
  4. SpringApplication.run(Application.class, args);
  5. }
  6. }

@SpringBootApplication是一个复合注解,里面有很多注解,

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @SpringBootConfiguration
  6. @EnableAutoConfiguration
  7. @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  8. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  9. public @interface SpringBootApplication {
  10. // ...
  11. }

但是重要的只有两个:

  1. @SpringBootConfiguration
  2. @EnableAutoConfiguration

@SpringBootConfiguration这个注解往上溯源,会发现就是Spring的@Configuration注解,而Spring的@Configuration注解又可以溯源到@Component这个注解,所以@SpringBootConfiguration实际上就是一个标注,表示这是一个SpringBoot的配置类。

SpringBoot实现自动配置的关键还是@EnableAutoConfiguration这个注解。

3.1 @EnableAutoConfiguration注解原理

@EnableAutoConfiguration注解往上溯源有两个比较重要的注解:

  1. @AutoConfigurationPackage
  2. @Import({AutoConfigurationImportSelector.class}):导入自动配置的组件。

这里出现了一个@Import注解,另外要给@AutoConfigurationPackage往上溯源,依然是@Import注解:

  1. @Import(AutoConfigurationPackages.Registrar.class)

所以最终@EnableAutoConfiguration可以溯源为两个@Import注解:

  1. @Import(AutoConfigurationPackages.Registrar.class)
  2. @Import({AutoConfigurationImportSelector.class})

第一个@Import注解将AutoConfigurationPackages.Registrar的实例加载进Spring的容器,这个Register会扫描@ComponentScan注解中标注的包之中的类,如果发现是符合条件的bean,就会被Spring加载。

第二个@Import注解是用来将所有符合条件的@Configuration配置都加载到容器中:

  1. // AutoConfigurationImportSelector类中的selectImport方法
  2. @Override
  3. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  4. if (!isEnabled(annotationMetadata)) {
  5. return NO_IMPORTS;
  6. }
  7. // 最终会调用SpringFactoriesLoader去加载符合条件的配置类
  8. AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
  9. return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  10. }

上面这段代买最终会调用SpringFactoriesLoader去加载符合条件的配置类:

  1. public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  2. // ... 省略部分代码
  3. public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
  4. String factoryTypeName = factoryType.getName();
  5. return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
  6. }
  7. // spring.factories文件的格式为:key=value1,value2,value3
  8. // 从所有的jar包中找到META-INF/spring.factories文件
  9. // 然后从文件中解析出key=factoryClass类名称的所有value值
  10. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  11. MultiValueMap<String, String> result = cache.get(classLoader);
  12. if (result != null) {
  13. return result;
  14. }
  15. try {
  16. // 取得资源文件的URL
  17. Enumeration<URL> urls = (classLoader != null ?
  18. classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  19. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  20. result = new LinkedMultiValueMap<>();
  21. // 遍历所有的URL
  22. while (urls.hasMoreElements()) {
  23. URL url = urls.nextElement();
  24. UrlResource resource = new UrlResource(url);
  25. // 根据资源文件URL解析properties文件
  26. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  27. for (Map.Entry<?, ?> entry : properties.entrySet()) {
  28. String factoryTypeName = ((String) entry.getKey()).trim();
  29. for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
  30. // 组装数据
  31. result.add(factoryTypeName, factoryImplementationName.trim());
  32. }
  33. }
  34. }
  35. cache.put(classLoader, result);
  36. return result;
  37. }
  38. catch (IOException ex) {
  39. throw new IllegalArgumentException("Unable to load factories from location [" +
  40. FACTORIES_RESOURCE_LOCATION + "]", ex);
  41. }
  42. }

这段代码会从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回。看看META-INF/spring.factories的文件内容:

  1. // 来自 org.springframework.boot.autoconfigure下的META-INF/spring.factories
  2. // 配置的key = EnableAutoConfiguration,与代码中一致
  3. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  4. org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
  5. org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
  6. org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration\
  7. .....

回顾一下,@EnableAutoConfiguration中导入了AutoConfigurationImportSelector.class类,这个类的selectImports()方法通过SpringFactoriesLoader加载到大量的配置类,而每一个配置类则根据条件化配置来做出决策,以实现自动配置

@EnableAutoConfiguration注解最终就是借助@Import注解,将所有符合自动配置条件的bean定义加载到IoC容器,注意@Import是Spring的注解。

3.2 条件注解

SpringBoot自动配置的核心是源于条件注解,就是指在不同的条件下生成不同的bean,或者是在某个bean创建完成后,才去生成其他的bean,诸如此类,在特定的条件下创建bean的行为。比较常见的条件注解有:

  1. @Conditional 依赖的条件
  2. @ConditionalOnBean 在某个Bean存在的条件下
  3. @ConditionalOnMissingBean 在某个Bean不存在的条件下
  4. @ConditionalOnClass 在某个Class存在的条件下
  5. @ConditionalOnMissingClass 在某个Class不存在的条件下

四、SpringBoot启动流程

4.1 springboot加载fatjar

首先要说明Springboot应用通过idea启动和通过java命令去启动流程是不一样的。

一般我们会通过spring-boot-maven-plugin插件将整个应用打成一个FatJar,但这个FatJar和普通的FatJar不太一样,普通的FatJar构建时会将依赖的jar包中的类展平,放到一个同一个目录。但是Spring构建的FatJar不会将依赖的jar包展平,而是完整的保留了依赖的那些jar包,我们可以通过java命令解压spring构建的jar包来看:

  1. jar -xvf <jar name>

解压后的结果:

  1. [root@temp lib]# tree
  2. .
  3. ├── BOOT-INF
  4.    ├── classes // 项目中编写的类
  5.    └── lib // 依赖的jar包
  6. ├── META-INF
  7.    ├── MANIFEST.MF // 修改过后的启动文件
  8.    └── maven
  9. ├── org
  10.    └── springframework
  11.    └── boot
  12.    └── loader // spring boot加载器
  13. ...

项目依赖的第三方jar包完整的存在lib目录中,但是这种打包方式不符合java标准,java标准的类加载器无法加载这种FatJar,所以SpringBoot自己实现了一套类加载器,包括JarLauncher 、WarLauncher 、PropertiesLauncher,这些Launcher都可以加载嵌套在FatJar中的jar包,相应的启动类也就变了,我们看MANIFEST.MF中的内容:

  1. Implementation-Title: Catyee Test Service
  2. Implementation-Version: 1.5.0
  3. Built-By: root
  4. Implementation-Vendor-Id: com.catyee.test
  5. Spring-Boot-Version: 2.0.9.RELEASE
  6. Implementation-Vendor: Catyee
  7. Main-Class: org.springframework.boot.loader.PropertiesLauncher
  8. Start-Class: com.catyee.test.TestBootApplication
  9. Spring-Boot-Classes: BOOT-INF/classes/
  10. Spring-Boot-Lib: BOOT-INF/lib/
  11. Created-By: Apache Maven 3.3.3
  12. Build-Jdk: 1.8.0_131
  13. Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo
  14. ot-starter-parent/test/test-service

注意看Main-Class,这是这个jar包的入口类,可以看到入口类并不是我们用@SpringBootApplication注解标注的类,而是SpringBoot的PropertiesLauncher,MANIFEST.MF中还新增了一个Start-Class,这个里面才保存了我们用@SpringBootApplication注解标注的类,所以当我们用java命令来启动springboot应用的时候,实际上是先启动spring boot的launcher,通过latcher来加载FatJar中的资源,最终会调用到Start-Class中定义的程序启动入口,从这个入口开始启动Spring。

4.2 SpringBoot启动Spring容器

SpringBoot整个启动流程分为两个步骤:初始化一个SpringApplication对象、执行该对象的run方法

4.2.1 初始化

当我们运行SpringApplication的main方法时,调用静态方法run()首先是实例化,SpringApplication初始化的时候主要做主要做三件事:

初始化流程中最重要的就是通过SpringFactoriesLoader找到spring.factories文件中配置的ApplicationContextInitializer和ApplicationListener两个接口的实现类名称,以便后期构造相应的实例。

ApplicationContextInitializer的主要目的是在ConfigurableApplicationContext做refresh之前,对ConfigurableApplicationContext实例做进一步的设置或处理。

ConfigurableApplicationContext继承自ApplicationContext,其主要提供了对ApplicationContext进行设置的能力。

实现一个ApplicationContextInitializer非常简单,因为它只有一个方法,但大多数情况下我们没有必要自定义一个ApplicationContextInitializer,即便是Spring Boot框架,它默认也只是注册了两个实现,毕竟Spring的容器已经非常成熟和稳定,你没有必要来改变它。

而ApplicationListener的目的就没什么好说的了,它是Spring框架对Java事件监听机制的一种框架实现,具体内容在前文Spring事件监听机制这个小节有详细讲解。这里主要说说,如果你想为Spring Boot应用添加监听器,该如何实现?

Spring Boot提供两种方式来添加自定义监听器:

通过SpringApplication.addListeners(ApplicationListener... listeners)或者SpringApplication.setListeners(Collection> listeners)两个方法来添加一个或者多个自定义监听器

既然SpringApplication的初始化流程中已经从spring.factories中获取到ApplicationListener的实现类,那么我们直接在自己的jar包的META-INF/spring.factories文件中新增配置即可:

  1. org.springframework.context.ApplicationListener=\ cn.moondev.listeners.xxxxListener

关于SpringApplication的初始化,我们就说这么多。

4.2.2 运行run方法

run方法定义了启动方案,包括启动流程的监听模块、加载配置环境模块、及核心的创建上下文环境模块

  1. // SpringApplication中的run方法:
  2. public ConfigurableApplicationContext run(String... args) {
  3. <!--1、这个是一个计时器,没什么好说的-->
  4. StopWatch stopWatch = new StopWatch();
  5. stopWatch.start();
  6. ConfigurableApplicationContext context = null;
  7. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  8. <!--2、这个也不是重点,就是设置了一些环境变量-->
  9. configureHeadlessProperty();
  10. <!--3、获取事件监听器SpringApplicationRunListener类型,并且执行starting()方法-->
  11. SpringApplicationRunListeners listeners = getRunListeners(args);
  12. listeners.starting();
  13. try {
  14. <!--4、把参数args封装成DefaultApplicationArguments,这个了解一下就知道-->
  15. ApplicationArguments applicationArguments = new DefaultApplicationArguments(
  16. args);
  17. <!--5、这个很重要准备环境了,并且把环境跟spring上下文绑定好,并且执行environmentPrepared()方法-->
  18. ConfigurableEnvironment environment = prepareEnvironment(listeners,
  19. applicationArguments);
  20. <!--6、判断一些环境的值,并设置一些环境的值-->
  21. configureIgnoreBeanInfo(environment);
  22. <!--7、打印banner-->
  23. Banner printedBanner = printBanner(environment);
  24. <!--8、创建上下文,根据项目类型创建上下文-->
  25. context = createApplicationContext();
  26. <!--9、获取异常报告事件监听-->
  27. exceptionReporters = getSpringFactoriesInstances(
  28. SpringBootExceptionReporter.class,
  29. new Class[] { ConfigurableApplicationContext.class }, context);
  30. <!--10、准备上下文,执行完成后调用contextPrepared()方法,contextLoaded()方法-->
  31. prepareContext(context, environment, listeners, applicationArguments,
  32. printedBanner);
  33. <!--11、这个是spring启动的代码了,这里就回去里面就回去扫描并且初始化单实列bean了-->
  34. //这个refreshContext()加载了bean,还启动了内置web容器,需要细细的去看看
  35. refreshContext(context);
  36. <!--12、啥事情都没有做-->
  37. afterRefresh(context, applicationArguments);
  38. stopWatch.stop();
  39. if (this.logStartupInfo) {
  40. new StartupInfoLogger(this.mainApplicationClass)
  41. .logStarted(getApplicationLog(), stopWatch);
  42. }
  43. <!--13、执行ApplicationRunListeners中的started()方法-->
  44. listeners.started(context);
  45. <!--执行RunnerApplicationRunnerCommandLineRunner)-->
  46. callRunners(context, applicationArguments);
  47. }
  48. catch (Throwable ex) {
  49. handleRunFailure(context, listeners, exceptionReporters, ex);
  50. throw new IllegalStateException(ex);
  51. }
  52. listeners.running(context);
  53. return context;
  54. }

利用SPI机制扫描 META-INF/spring.factories 这个文件,并且加载 ApplicationContextInitializer、ApplicationListener 接口实例。

1、ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用

2、ApplicationListener 当springboot启动时事件change后都会触发

五、Spring容器启动过程

Spring容器最终会调用referesh方法,这个方法用于真正启动一个Spring容器,这是一个模板方法的设计模式,里面定义好了启动流程:

  1. // Spring 启动启动容器的方法:
  2. public void refresh() throws BeansException, IllegalStateException {
  3. synchronized (this.startupShutdownMonitor) {
  4. // Prepare this context for refreshing.
  5. // 1、容器启动前的准备工作,这里面有第一个容器扩展点initPropertySources,用户在自定义IOC容器时可以重写,完成一些环境变量属性的初始化工作。
  6. prepareRefresh();
  7. // 2、获取BeanFactory;默认实现是DefaultListableBeanFactory,在创建容器的时候创建的
  8. // Tell the subclass to refresh the internal bean factory.
  9. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  10. // 3、BeanFactory的预准备工作(BeanFactory进行一些设置,比如context的类加载器,BeanPostProcessor和XXXAware自动装配等)
  11. // Prepare the bean factory for use in this context.
  12. prepareBeanFactory(beanFactory);
  13. try {
  14. // 4、BeanFactory准备工作完成后进行的后置处理工作
  15. // Allows post-processing of the bean factory in context subclasses.
  16. postProcessBeanFactory(beanFactory);
  17. //5、执行BeanFactoryPostProcessor的方法
  18. // Invoke factory processors registered as beans in the context.
  19. invokeBeanFactoryPostProcessors(beanFactory);
  20. // 6、注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执行
  21. // Register bean processors that intercept bean creation.
  22. registerBeanPostProcessors(beanFactory);
  23. // 7、初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
  24. // Initialize message source for this context.
  25. initMessageSource();
  26. //8、初始化事件派发器
  27. // Initialize event multicaster for this context.
  28. initApplicationEventMulticaster();
  29. // 9、子类重写这个方法,在容器刷新的时候可以自定义逻辑;如创建Tomcat,Jetty等WEB服务器
  30. // Initialize other special beans in specific context subclasses.
  31. onRefresh();
  32. // 10、注册应用的监听器。就是注册实现了ApplicationListener接口的监听器bean,这些监听器是注册到ApplicationEventMulticaster中的
  33. // Check for listener beans and register them.
  34. registerListeners();
  35. // 11、初始化所有剩下的非懒加载的单例bean
  36. // Instantiate all remaining (non-lazy-init) singletons.
  37. finishBeanFactoryInitialization(beanFactory);
  38. // 12、完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事件(ContextRefreshedEvent)
  39. // Last step: publish corresponding event.
  40. finishRefresh();
  41. }
  42. catch (BeansException ex) {
  43. ...
  44. } finally {
  45. ...
  46. }
  47. }
  48. }

参考:https://www.cnblogs.com/grasp/p/11942735.html

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注