Spring Boot 源码分析(三)

应用上下文

创建SpringBootExceptionReporter

java 复制代码
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
      new Class[] { ConfigurableApplicationContext.class }, context);

SpringBootExceptionReporter 是 Spring Boot 框架中的一个组件,它负责处理并报告在 Spring Boot 应用启动过程中发生的异常。这个类不是直接暴露给应用开发者使用的,而是 Spring Boot 内部用来提供友好的异常报告和诊断信息。

当 Spring Boot 应用启动失败时,SpringBootExceptionReporter 会捕获异常,并尝试以易于理解的方式输出异常信息和可能的原因。这通常包括堆栈跟踪、错误描述以及可能的解决方案或建议。

应用上下文预处理

应用上下文设置环境

java 复制代码
context.setEnvironment(environment);
java 复制代码
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;
...
@Override
public void setEnvironment(ConfigurableEnvironment environment) {
   super.setEnvironment(environment);
   this.reader.setEnvironment(environment);
   this.scanner.setEnvironment(environment);
}

名词解释

AnnotatedBeanDefinitionReader

AnnotatedBeanDefinitionReader 是 Spring 框架中的一个类,它用于从注解的类、接口、方法或字段中读取 Bean 定义,并将其注册到 BeanDefinitionRegistry 中。这允许 Spring 容器管理和自动装配这些组件。

当你在 Spring 配置中使用 @Component, @Service, @Repository, @Controller 等注解时,AnnotatedBeanDefinitionReader 会扫描这些注解,并创建相应的 BeanDefinition 对象。这些对象描述了 Spring 容器中 Bean 的元数据,如类名、生命周期回调、属性注入等。

ClassPathBeanDefinitionScanner

ClassPathBeanDefinitionScanner 是 Spring 框架中的一个类,它用于扫描类路径(classpath)上的特定包,以查找带有特定注解的类,并将这些类注册为 Spring 容器中的 Bean 定义。这是 Spring 的组件扫描(Component Scanning)功能的核心部分,它使得开发者能够使用注解来自动发现和配置 Bean,而不是通过传统的 XML 配置方式。

ClassPathBeanDefinitionScanner 通常与 @ComponentScan 注解一起使用,后者在 Spring Boot 应用中尤为常见。当 Spring 容器启动时,它会根据 @ComponentScan 注解指定的包路径来扫描类,并使用 ClassPathBeanDefinitionScanner 来查找带有 @Component@Service@Repository@Controller 等注解的类。

二者区别

它们最大的不同在于AnnotatedBeanDefinitionReader支持注册单个的BeanDefinition,而ClassPathBeanDefinitionScanner会一次注册所有扫描到的BeanDefinition

容器后处理

java 复制代码
postProcessApplicationContext(context);

内部实现

java 复制代码
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
   if (this.beanNameGenerator != null) {
      //1、如果当前容器名字的生成器不为空,则往容器工厂中注册容器名字生成器。
      context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
            this.beanNameGenerator);
   }
   if (this.resourceLoader != null) {
      //2、如果配置的加载器不为空,那么此时需要将配置加载器、类加载器都保存到容器工厂中。   
      if (context instanceof GenericApplicationContext) {
         ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
      }
      if (context instanceof DefaultResourceLoader) {
         ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
      }
   }
   if (this.addConversionService) {
      //3、如果进行了占位符值的转换,那么此时将相应的转换服务也保存到容器中。
      context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
   }
}

加载ApplicationContextInitializers

java 复制代码
applyInitializers(context);

SPI获取到所有的ApplicationContextInitializers,在这里将被执行。

内部实现

java 复制代码
protected void applyInitializers(ConfigurableApplicationContext context) {
	// 调用每一个实现类的initialize方法
	for (ApplicationContextInitializer initializer : getInitializers()) {
		Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
		Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
		initializer.initialize(context);
	}
}
// 将之前获取到的集合进行排序并返回只读集合
public Set<ApplicationContextInitializer<?>> getInitializers() {
	return asUnmodifiableOrderedSet(this.initializers);
}

发布ApplicationContextInitializedEvent事件

java 复制代码
listeners.contextPrepared(context);

记录活动的Profiles日志

java 复制代码
if (this.logStartupInfo) {
   logStartupInfo(context.getParent() == null);
   logStartupProfileInfo(context);
}

获取beanFactory

java 复制代码
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
  1. getBeanFactory()
    getBeanFactory()ApplicationContext接口的一个方法,用于获取底层的BeanFactoryBeanFactory是Spring框架中用于管理bean的接口,而ApplicationContext则是BeanFactory的子接口,提供了更多的功能,比如支持国际化、事件传播等。
  2. ConfigurableListableBeanFactory
    ConfigurableListableBeanFactoryBeanFactory接口的一个实现,它提供了额外的配置能力,并且能够列出容器中定义的bean。它通常用于更高级的配置和编程场景。

将参数对象和banner注册为单例模式

java 复制代码
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
    beanFactory.registerSingleton("springBootBanner", printedBanner);
}

是否允许覆盖Bean定义

和spring.main.allow-bean-definition-overriding参数有关

java 复制代码
if (beanFactory instanceof DefaultListableBeanFactory) {
   ((DefaultListableBeanFactory) beanFactory)
         .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}

设置懒加载后处理器

java 复制代码
if (this.lazyInitialization) {
   context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}

作用是检查当前对象是否启用了懒加载(this.lazyInitialization是否为true)。如果启用了懒加载,那么就向Spring的ApplicationContext中注册一个LazyInitializationBeanFactoryPostProcessor,以便在后续的bean工厂处理过程中应用懒加载配置。这样做可以使得部分bean在第一次使用时才进行初始化,从而优化应用启动时间和资源使用。

获取Sources并加载

java 复制代码
// 获取所有的来源文件信息 这里主要指的是启动类。
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 根据相应的sources去进行加载
load(context, sources.toArray(new Object[0]));
// 加载完成后发送相应的事件消息。
listeners.contextLoaded(context);
java 复制代码
 protected void load(ApplicationContext context, Object[] sources) {
    if (logger.isDebugEnabled()) {
       logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
    }
     //创建加载器
    BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
    // 注入bean名字生成器、资源加载器、环境变量信息
    if (this.beanNameGenerator != null) {
       loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
       loader.setResourceLoader(this.resourceLoader);
    }
    if (this.environment != null) {
       loader.setEnvironment(this.environment);
    }
     //进行实质性的加载
    loader.load();
 }

这里的sources主要指的是Application启动类。上述代码会根据context获取对应的容器注册类,也就是BeanDefinitionRegistry。通俗来说,BeanDefinition Registry就是一个大型的Map结构,其Key是Bean的名字,value是对应BeanDefinition。

BeanDefinition其实就是一个包含所有类信息的定义对象。将其存储在Registry中,是为了方便后续采用反射机制来生成bean,并对相应的Bean进行修改和属性的替换。

java 复制代码
int load() {
   int count = 0;
   for (Object source : this.sources) {
      count += load(source);
   }
   return count;
}

首先是会对每个资源都调用相应的load方法。(这里通常只有启动类。)在load方法中,会判断当前资源的类型,根据当前Source类型是类、配置、包还是文本信息,来调用不同的加载方法。

java 复制代码
private int load(Object source) {
   Assert.notNull(source, "Source must not be null");
   if (source instanceof Class<?>) {
      return load((Class<?>) source);
   }
   if (source instanceof Resource) {
      return load((Resource) source);
   }
   if (source instanceof Package) {
      return load((Package) source);
   }
   if (source instanceof CharSequence) {
      return load((CharSequence) source);
   }
   throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

对类进行加载的方法源代码如下所示:

java 复制代码
private int load(Class<?> source) {
   // 判断当前该资源类是否是采用grovvy定义的资源类
   if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
      // 若是,则采用特定的资源类加载器进行加载。
      GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
      load(loader);
   }
   // 否则判断当前source是否符合1、非匿名类;2、对Grovvy加载关闭;3、拥有无参构造器
   if (isComponent(source)) {
      //满足以上三点,才能将相应source进行注册。
      this.annotatedReader.register(source);
      return 1;
   }
   return 0;
}

this.annotatedReader.register(source),该方法最终会调用doRegisterBean方法,实现细节如下:

java 复制代码
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
       @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
       @Nullable BeanDefinitionCustomizer[] customizers) {
     //新建BeanDefinition
    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
    if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
       return;
    }
    abd.setInstanceSupplier(supplier);
     //获取bean定义的作用范围:如单例、多例等。
    ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
    abd.setScope(scopeMetadata.getScopeName());
     // 生成bean的名字信息
    String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
    AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
    if (qualifiers != null) {
       for (Class<? extends Annotation> qualifier : qualifiers) {
           //设置这个bean的一些属性:如是否需要懒初始化、当前bean是否属于优先加载的等
          if (Primary.class == qualifier) {
             abd.setPrimary(true);
          }else if (Lazy.class == qualifier) {
             abd.setLazyInit(true);
          }else {
             abd.addQualifier(new AutowireCandidateQualifier(qualifier));
          }
       }
    }
     //紧接着还需要对bean做一些自定义化的处理.
    if (customizers != null) {
       for (BeanDefinitionCustomizer customizer : customizers) {
          customizer.customize(abd);
       }
    }
     // 将对应的beanDefinition及bean名字组装成新的holder进行处理.简单来说就是对内容进行聚合并提供getter、setter方法信息。
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
     //设置当前bean是否需要被aop进行增强代理
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
     //最后一步将新的这个bean对象保存到registry中.
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
 }
相关推荐
苹果醋325 分钟前
React源码02 - 基础知识 React API 一览
java·运维·spring boot·mysql·nginx
Hello.Reader1 小时前
深入解析 Apache APISIX
java·apache
菠萝蚊鸭1 小时前
Dhatim FastExcel 读写 Excel 文件
java·excel·fastexcel
旭东怪1 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
007php0071 小时前
Go语言zero项目部署后启动失败问题分析与解决
java·服务器·网络·python·golang·php·ai编程
∝请叫*我简单先生1 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl
ssr——ssss2 小时前
SSM-期末项目 - 基于SSM的宠物信息管理系统
java·ssm
一棵星2 小时前
Java模拟Mqtt客户端连接Mqtt Broker
java·开发语言
鲤籽鲲2 小时前
C# Random 随机数 全面解析
android·java·c#
荆州克莱2 小时前
mysql中局部变量_MySQL中变量的总结
spring boot·spring·spring cloud·css3·技术