SpringBoot源码分析(7)--prepareContext/准备应用上下文

news2025/1/10 3:23:50

文章目录

  • 一、前言
  • 二、prepareContext
    • 2.1、context.setEnvironment
    • 2.2、postProcessApplicationContext(context);
    • 2.3、applyInitializers(context)
    • 2.4、发布ApplicationContextInitializedEvent事件
    • 2.5、打印启动和profile日志
    • 2.6、注册单例Bean
      • 2.6.1、手工注册单例Bean流程
    • 2.7、初始化BeanDefinitionLoader, 加载Application
    • 2.8、发布contextLoaded事件
      • 2.8.1、ConfigFileApplicationListener
      • 2.8.2、LoggingApplicationListener
      • 2.8.3、BackgroundPreinitializer
      • 2.8.4、DelegatingApplicationListener
  • 三、总结

一、前言

本文基于spring-boot-2.2.14.BUILD-SNAPSHOT源码分析prepareContext准备应用上下文这一步骤。

二、prepareContext

承接上文,本文继续SpringApplication的run方法往下分析,看prepareContext这行代码
在这里插入图片描述
请求参数:

参数类型参数简要说明
ConfigurableApplicationContext contextcreateApplicationContext()方法的返回值,代表应用上下文
ConfigurableEnvironment environment系统的环境变量信息的接口类
SpringApplicationRunListeners listenersSpringApplicationRunListener的集合类
ApplicationArguments applicationArguments应用参数
Banner printedBanner打印的Banner信息

进入方法实现:

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
	// 1、设置环境对象
	//统一ApplicationContext和Application,使用Application的environment
	context.setEnvironment(environment);
	// 2、注册组件 设置ApplicationContext的beanNameGenerator、resourceLoader、
	postProcessApplicationContext(context);
	// 3、应用初始化器对ApplicationContext进行初始化处理(Initializers在构造SpringApplication时就从spring.factories中加载到了)
	applyInitializers(context);
	// 4、发布ApplicationContext准备妥当事件
	listeners.contextPrepared(context);
	// 5、打印startup日志信息
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// 6 、添加特定的单例beans到 beanFactory中
	// Add boot specific singleton beans
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	if (this.lazyInitialization) {
		context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
	}
	// Load the sources加载资源
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	// 加载启动类,见启动类注入容器中
	load(context, sources.toArray(new Object[0]));
	// 触发contextLoaded事件
	listeners.contextLoaded(context);
}

准备应用上下文环境AnnotationConfigServletWebServerApplicationContext, 执行了以下8个步骤

  1. 统一ApplicationContext和Application使用的environment
  2. 后置处理ApplicationContext
  3. 执行Initializers
  4. 发布contextPrepared事件
  5. 打印启动和profile日志
  6. 注册单例bean
  7. 加载启动类
  8. 发布contextLoaded事件

2.1、context.setEnvironment

统一ApplicationContext和Application使用的environment

public class AnnotationConfigServletWebServerApplicationContext
        extends ServletWebServerApplicationContext implements AnnotationConfigRegistry {
        
    @Override
    public void setEnvironment(ConfigurableEnvironment environment) {
        //显式调用父类AbstractApplicationContext的setEnvironment方法
        super.setEnvironment(environment);
        //调用AnnotatedBeanDefinitionReader#setEnvironment()方法
        this.reader.setEnvironment(environment);        
        //ClassPathBeanDefinitionScanner继承了ClassPathScanningCandidateComponentProvider,所以调用了父类setEnvironment方法
        this.scanner.setEnvironment(environment);
    }
    
}

将context中相关的environment全部替换成SpringApplication中创建的environment。还记得《SpringBoot源码分析(5)–createApplicationContext创建应用上下文》中的疑问吗,引申下就是:之前我们的应用中有两个environment,一个在context中,一个在SpringApplication中。经过此方法后,就只会存在SpringApplication中的environment了,而context中的原environment会被回收。

关于这点我们上篇有提到过一个瑕疵,因为这里虽然替换了容器原生的environement,但之前初始化SpringBootExceptionReporter的时候,已经把原生的environment设置到了异常分析器中,这些分析器持有的environment没有得到同步的更新,并不是我们真正使用的环境对象。

2.2、postProcessApplicationContext(context);

执行了以下三步

  1. 设置ApplicationContext的beanNameGenerator
  2. 设置ApplicationContext的resourceLoader和classLoader
  3. 设置ApplicationContext的类型转换Service
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
    //beanNameGenerator默认为null,所以此处没有设置
    if (this.beanNameGenerator != null) {
        //如果beanNameGenerator不为空
        //那么注册一个名为internalConfigurationBeanNameGenerator
        //值为beanNameGenerator的单例bean
        context.getBeanFactory().registerSingleton(
                AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                this.beanNameGenerator);
    }
    //resourceLoader默认为null,所以此处没有设置
    if (this.resourceLoader != null) {
        //如果resourceLoader不为空
        if (context instanceof GenericApplicationContext) {
            //context是GenericApplicationContext子类
            //那么设置上下文context的resourceLoader
            ((GenericApplicationContext) context)
                    .setResourceLoader(this.resourceLoader);
        }
        if (context instanceof DefaultResourceLoader) {
            //如果当前上下文是DefaultResourceLoader的子类
            //那么设置上下文context的classLoader
            ((DefaultResourceLoader) context)
                    .setClassLoader(this.resourceLoader.getClassLoader());
        }
    }
    //this.addConversionService默认为true
    if (this.addConversionService) {
        //设置类型转换Service
        context.getBeanFactory().setConversionService(
                ApplicationConversionService.getSharedInstance());
    }
}

首先看SpringApplication对象中有没有自定义的BeanNameGenerator,有的话就注册到容器的单例池,这个对象是用来给容器中的Bean生成名字的,Spring容器new出来的时候会默认生成一个,默认的命名策略就是类名小写,不过SpringApplication中的该对象默认是null的

然后看SpringApplication对象有没有自定义ResourceLoader,有的话就赋值给容器,这个我们之前也分析过,默认也是null的

最后一个if分支,addConversionService在SpringApplication对象的构造函数里就默认设置为true,所以会走if,它为容器设置了一个ConversonService,这个类是用来做类型转换的,比如String转Integer等等,其实在之前的文章中已经见过几次了
在这里插入图片描述

2.3、applyInitializers(context)

加载的是META-INF/spring.factories中的ApplicationContextInitializer列表 ,并依次调用其initialize方法

protected void applyInitializers(ConfigurableApplicationContext context) {
	for (ApplicationContextInitializer initializer : getInitializers()) {
	    //断言判断initializer的类型是否符合条件
		Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
				ApplicationContextInitializer.class);
		Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
		//执行各个initializer的初始化initialize方法
		initializer.initialize(context);
	}
}

initializers在SpringApplication初始化期间获取到,获取源码可参考《SpringBoot源码分析(2)–SpringBoot启动源码(万字图文源码debug讲解springboot启动原理)》, 一共获取到7个初始化器:

  • DelegatingApplicationContextInitializer
  • SharedMetadataReaderFactoryContextInitializer
  • ContextIdApplicationContextInitializer
  • ConfigurationWarningsApplicationContextInitializer
  • ServerPortInfoApplicationContextInitializer
  • ConditionEvaluationReportLoggingListener
  • RSocketPortInfoApplicationContextInitializer

本文先梳理prepareContext方法的脉络,至于这些内置的ApplicationContextInitializer做了哪些初始化,我们下篇文章《SpringBoot源码分析(8)–内置ApplicationContextInitializer》单独分析

所有的这些初始化类都没有进行启动服务的实质性操作,都是通过注册对象,埋点,后面invokeBeanFactoryPostProcessors才真正调用初始化方法,而且在项目启动之前

2.4、发布ApplicationContextInitializedEvent事件

// 4、发布ApplicationContext准备妥当事件
listeners.contextPrepared(context);

Application容器初始化完成事件, 对该事件感兴趣的监听器有

  • BackgroundPreinitializer
  • DelegatingApplicationListener

BackgroundPreinitializer
扩展点, 后台进程初始化器, 用于多线程执行后台耗时任务, 在这里不处理ApplicationContextInitializedEvent事件

DelegatingApplicationListener
扩展点, 代理监听器, 继续分发事件, 不处理ApplicationContextInitializedEvent事件

2.5、打印启动和profile日志

//logStartupInfo默认为true
if (this.logStartupInfo) {
    //判断是否有父容器,打印项目启动信息
    // Starting Demo3Application on pcname with PID 12372 (E:\workspace\demo3\target\classes started by username in E:\workspace\demo3)
    logStartupInfo(context.getParent() == null);

    //打印profile
    //No active profile set, falling back to default profiles: default
    logStartupProfileInfo(context);
}

这段代码判断当前容器是否有父容器,如果没有的话就认为是项目启动的根容器,会打印一行日志,包括启动类、当前的服务器名、项目路径、PID等

2023-07-18 10:35:07.105  INFO 3136 --- [           main] com.example.demo.Demo3Application        : Starting Demo3Application on hualsd with PID 3136 (D:\WorkSpace\demo3\target\classes started by 188 in D:\WorkSpace\demo3)
2023-07-18 10:35:32.693  INFO 3136 --- [           main] com.example.demo.Demo3Application        : The following profiles are active: sit

2.6、注册单例Bean

注册了两个单例Bean

  • 命令行参数bean, 名称为springApplicationArguments, 值为applicationArgument
  • banner bean, 名称为springBootBanner, 值为printedBanner
//注册命令行参数bean
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
    //banner bean
    beanFactory.registerSingleton("springBootBanner", printedBanner);
}

最终registerSingleton方法会把他们注册到singletonObjects容器中,从名字我们就可以看出来,这是个存放单例对象的容器。
在这里插入图片描述

2.6.1、手工注册单例Bean流程

调用DefaultListableBeanFactory#registerSingleton方法, 显示调用父类DefaultSingletonBeanRegistry#registerSingleton方法

DefaultListableBeanFactory 手工注册单例Bean
手工注册单例Bean, 不同于扫描bean定义, 然后注册单例bean, 手工注册的单例Bean, 没有维护到beanDefinitionMap中, 而是将beanName维护到manualSingletonNames中

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {

    //注册单例bean
    public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
        super.registerSingleton(beanName, singletonObject);

        //判断bean的创建过程是否已经开始了
        //调用抽象父类AbstractBeanFactory#hasBeanCreationStarted()方法
        //判断AbstractBeanFactory成员变量alreadyCreated Set不为空
        if (hasBeanCreationStarted()) {
            //bean创建过程已经开始了
            //锁住成员变量beanDefinitionMap
            synchronized (this.beanDefinitionMap) {
                if (!this.beanDefinitionMap.containsKey(beanName)) {
                    //如果bean定义Map,  beanDefinitionMap已经包含了bean
                    //维护到手工单例bean名称manualSingletonNames中
                    Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames.size() + 1);
                    updatedSingletons.addAll(this.manualSingletonNames);
                    updatedSingletons.add(beanName);
                    this.manualSingletonNames = updatedSingletons;
                }
            }
        }
        else {
            // bean还没有注册过, 仍处于启动注册阶段
            if (!this.beanDefinitionMap.containsKey(beanName)) {
                //如果beanDefinitionMap不包含beanName
                //那么添加到manualSingletonNames
                this.manualSingletonNames.add(beanName);
            }
        }

        //清空allBeanNamesByType和singletonBeanNamesByType
        clearByTypeCache();
    }
}

DefaultSingletonBeanRegistry手工注册单例Bean
将beanName添加到registeredSingletons中, beanName和对应的对象保存singletonObjects中, 并删除beanName对应的beanFactory, earlySingleton

//默认单例bean注册器
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    
    //缓存单例bean, key为bean名称,value为bean实例
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    //缓存beanFactory, key为bean名称, value为beanFactory
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    //早期单例缓存, key为bean名称, value为bean实例
    //为了解决循环依赖而引入的
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    //单例bean名称set
    private final Set<String> registeredSingletons = new LinkedHashSet<>(256);

    //正在创建的单例bean名称set
    private final Set<String> singletonsCurrentlyInCreation =
            Collections.newSetFromMap(new ConcurrentHashMap<>(16));


    //手工注册单例bean
    @Override
    public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
        //判断名称和值不可以为空
        Assert.notNull(beanName, "Bean name must not be null");
        Assert.notNull(singletonObject, "Singleton object must not be null");
        synchronized (this.singletonObjects) {
            //判断bean是否为空
            Object oldObject = this.singletonObjects.get(beanName);
            if (oldObject != null) {
                //不为空抛异常
                throw new IllegalStateException("Could not register object [" + singletonObject +
                        "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
            }
            //添加一个单例bean
            addSingleton(beanName, singletonObject);
        }
    }
    
    //添加一个单例bean
    protected void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            //保存到singletonObjects的map中
            this.singletonObjects.put(beanName, singletonObject);
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
            //添加beanName
            this.registeredSingletons.add(beanName);
        }
    }
}

接着注册单例bean继续往下分析。

设置是否允许同名覆盖(setAllowBeanDefinitionOverriding),默认情况下为false(allowBeanDefinitionOverriding属性默认值)。如果为true,后面的BeanDefinition数据会将前面的覆盖掉。
在这里插入图片描述

添加beanFactory懒加载后置处理器(addBeanFactoryPostProcessor),由于默认情况下并未启动懒加载,所以默认情况下懒加载后置处理器也不会被添加
在这里插入图片描述

2.7、初始化BeanDefinitionLoader, 加载Application

接下来看一个比较重要的load方法

Set<Object> sources = this.getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
this.load(context, sources.toArray(new Object[0]));

getAllSources获取的是SpringApplication对象的primarySources属性,而该属性在SpringApplication构造函数中赋了值,也就是我们的启动类Demo3Application.class
在这里插入图片描述

接下来进入load方法

protected void load(ApplicationContext context, Object[] sources) {
    if (logger.isDebugEnabled()) {
        logger.debug(
                "Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
    }
    //实例化BeanDefinitionLoader
    BeanDefinitionLoader loader = createBeanDefinitionLoader(
            getBeanDefinitionRegistry(context), sources);
    //this.beanNameGenerator为null
    if (this.beanNameGenerator != null) {
        loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    //this.resourceLoader为null
    if (this.resourceLoader != null) {
        loader.setResourceLoader(this.resourceLoader);
    }
    //this.environment为null
    if (this.environment != null) {
        loader.setEnvironment(this.environment);
    }
    //调用load()方法,加载各个sources
    loader.load();
}

首先生成一个BeanDefinitionLoader, 用于加载SpringApplication的成员变量sources, 当前sources列表中只有Demo3Application.class一个对象。

先通过createBeanDefinitionLoader方法创建一个BeanDefinitionLoader,它可以将一个类加载成BeanDefinition,第一个参数就是spring容器,第二个参数是我们的启动类。

BeanDefinitionLoader构造方法

  /**
 * 构造函数
   */
  BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
      Assert.notNull(registry, "Registry must not be null");
      Assert.notEmpty(sources, "Sources must not be empty");
      //传入的sources, 目前只有Demo3Application.class
      this.sources = sources;
      this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
      this.xmlReader = new XmlBeanDefinitionReader(registry);
      if (isGroovyPresent()) {
          //使用了groovy
          this.groovyReader = new GroovyBeanDefinitionReader(registry);
      }
      this.scanner = new ClassPathBeanDefinitionScanner(registry);
      //排除sources扫描
      this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
  }

在BeanDefinitionLoader的构造方法中,会创建一个AnnotatedBeanDefinitionReader对象,这个类在spring容器的构造函数中已经创建过一次了,这里没有直接使用spring容器的,而是又新建了一个,会重复走一遍Reader的构造流程,但是其中往spring容器注册bean的方法执行前都做了判空的校验,所以不会重复注册,类似如下代码

if (!registry.containsBeanDefinition("org.springframework.context.annotation.internalAutowiredAnnotationProcessor")) {
    def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, "org.springframework.context.annotation.internalAutowiredAnnotationProcessor"));
}

回到load方法,接下来几个if分支都不会进,默认情况下SpringApplication中的beanNameGenerator、resourceLoader、environment都是null,注意我们真正使用的environment是在SpringApplication对象的run方法中创建的,并没有赋值给它自己的environment变量,所以这里依然是null

进入最后一行load方法

 /**
  * 加载sources
  */
 public int load() {
     int count = 0;
     for (Object source : this.sources) {
         count += load(source);
     }
     return count;
 }

正常情况下启动类只有一个,继续跟进load方法

//加载Object资源
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) {
        //加载Resource资源
        return load((Resource) source);
    }
    if (source instanceof Package) {
        //加载Package资源
        return load((Package) source);
    }
    if (source instanceof CharSequence) {
        //加载字符串资源
        return load((CharSequence) source);
    }
    throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

我们启动类是class类型,走第一个分支

//加载类资源
private int load(Class<?> source) {
    if (isGroovyPresent()
            && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
        // 使用了groovy,加载groovy资源
        GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
                GroovyBeanDefinitionSource.class);
        load(loader);
    }
    //如果有@Component注解
    if (isComponent(source)) {
        this.annotatedReader.register(source);
        return 1;
    }
    return 0;
}

isComponent方法判断启动类上是否有@Component注解,启动类加了@SpringBootApplication注解,它是一个复合注解,内部包含了@Component注解,所以这个分支成立,进入register方法

public class AnnotatedBeanDefinitionReader {

    //Class列表注册Bean定义
    public void register(Class<?>... annotatedClasses) {
        for (Class<?> annotatedClass : annotatedClasses) {
            //单个Bean注册
            registerBean(annotatedClass);
        }
    }
}

registerBean调用doRegisterBean

public class AnnotatedBeanDefinitionReader {

     //单个Class注册bean
    public void registerBean(Class<?> annotatedClass) {
        doRegisterBean(annotatedClass, null, null, null);
    }
    
}

最终将我们的启动类转化为BeanDefinition注册到spring容器的BeanDefinitionMap中,后续会以此为起点,开始扫描项目中的Controller、Service等等注册到容器中

 //注册Bean定义
  <T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
          @Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {
      //生成注解BeanDefinition
      AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
      
      //判断是否符合@Conditional注解的条件
      //不满足的话, 就不注册Bean
      if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
          return;
      }
      //设置instanceSupplier, //AbstractAutowireCapableBeanFactory#createBeanInstance中调用了instanceSupplier.get()生成bean实例
      abd.setInstanceSupplier(instanceSupplier);
      //Scope元空间
      ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
      abd.setScope(scopeMetadata.getScopeName());
      //生成Bean名称
      String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

      //处理Lazy, Primary, DependsOn, Role, Description注解
      AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
      if (qualifiers != null) {
          for (Class<? extends Annotation> qualifier : qualifiers) {
              if (Primary.class == qualifier) {
                  abd.setPrimary(true);
              }
              else if (Lazy.class == qualifier) {
                  abd.setLazyInit(true);
              }
              else {
                  abd.addQualifier(new AutowireCandidateQualifier(qualifier));
              }
          }
      }
      for (BeanDefinitionCustomizer customizer : definitionCustomizers) {
          //beanDefinition定制器
          customizer.customize(abd);
      }

      //bean定义容器
      BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
      
      //Scope代理模式处理
      //ScopedProxyMode.DEFAULT和NO不需要代理处理
      //INTERFACES使用JDK动态代理
      //TARGET_CLASS使用CGLIB动态代理
      definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
      
      //注册Bean定义
      BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
  }

执行完毕后,可以看到Spring容器的BeanDefinitionMap中,已经添加了我们的启动类,而前面几个类都是在new容器的时候,内部AnnotatedBeanDefinitionReader初始化的过程中注册到容器里的
在这里插入图片描述

2.8、发布contextLoaded事件

调用listeners.contextLoaded(context), 发布了一个ApplicationPreparedEvent事件。

跟之前的事件发布机制一样,最终调用了EventPublishingRunListener的contextLoaded方法

public void contextLoaded(ConfigurableApplicationContext context) {
	for (ApplicationListener<?> listener : this.application.getListeners()) {
		if (listener instanceof ApplicationContextAware) {
			((ApplicationContextAware) listener).setApplicationContext(context);
		}
		context.addApplicationListener(listener);
	}
	this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}

这个for循环,遍历了SpringApplication对象所有的监听器,也就是最开始创建SpringApplication的时候,从META-INF/spring.factories中加载到的ApplicationListener,在循环中,判断Listener是否实现了ApplicationContextAware接口,如果是的话就把Spring容器赋给他

这个Aware的回调本来也是在Spring容器refresh的过程执行的,但是由于这里的监听器可能仅仅是存储在容器的一个列表属性里,而并不会注册到容器中,也就不会作为Bean管理起来,后续就没办法真正在spring容器的refresh过程以正常的方式触发回调,所以就在这里手动赋值了

然后在for循环的最后一个条件里,将其添加到spring容器的监听器列表,我们之前有提到过,容器启动后,事件发布的职能会转交给容器进行,而这里正是重要的一步,将内置的监听器列表交给了容器,有了监听器列表, 自然可以向它们广播事件了

最后发布事件ApplicationPreparedEvent,发布流程跟之前一样,最终感兴趣的监听器有四个:

  • ConfigFileApplicationListener
  • LoggingApplicationListener
  • BackgroundPreinitializer
  • DelegatingApplicationListener

2.8.1、ConfigFileApplicationListener

配置文件监听器

public class ConfigFileApplicationListener
        implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
    //事件处理
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent(
                    (ApplicationEnvironmentPreparedEvent) event);
        }
        if (event instanceof ApplicationPreparedEvent) {
            //处理ApplicationPreparedEvent
            onApplicationPreparedEvent(event);
        }
    }
    
    //处理ApplicationPreparedEvent
    private void onApplicationPreparedEvent(ApplicationEvent event) {
        this.logger.switchTo(ConfigFileApplicationListener.class);
        addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());
    }
    
    //applicationContext中添加一个PropertySourceOrderingPostProcessor
    protected void addPostProcessors(ConfigurableApplicationContext context) {
        //用于重排序PropertySourceOrderingPostProcessor
        context.addBeanFactoryPostProcessor(
                new PropertySourceOrderingPostProcessor(context));
    }
}

2.8.2、LoggingApplicationListener

日志监听器

public class LoggingApplicationListener implements GenericApplicationListener {

    private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
        ConfigurableListableBeanFactory beanFactory = event.getApplicationContext()
                .getBeanFactory();
        //注册日志单例bean
        if (!beanFactory.containsBean(LOGGING_SYSTEM_BEAN_NAME)) {
            beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem);
        }
    }
    
}

2.8.3、BackgroundPreinitializer

后台预初始化器, 当前不做任务处理, 方便以后扩展

2.8.4、DelegatingApplicationListener

代理监听器, 不做任何处理, 方便以后扩展

三、总结

这一步的主要作用是为下面刷新applicationContext做准备

  • 统一了ApplicationContext和Application的environment
  • 设置ApplicationContext的beanNameGenerator,resouceLoader和classLoader, - 并设置beanFactory的类型转换Service
  • 执行Initializer
  • 发布ApplicationContextInitializedEvent
  • 打印启动日志和profile日志
  • 手工注册命令行和banner两个单例Bean
  • 初始化BeanDefinitionLoader, 加载启动类sources
  • 发布contextLoaded事件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/842285.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

尚品汇总结七:商品详情模块(面试专用)

一、业务介绍 订单业务在整个电商平台中处于核心位置&#xff0c;也是比较复杂的一块业务。是把“物”变为“钱”的一个中转站。 整个订单模块一共分四部分组成&#xff1a; 结算页面 在购物车列表页面中,有一个结算的按钮,用户一点击这个按钮时,跳转到结算页,结算页展示了用…

单细胞测序基础知识

构建文库 上机测序 根据不同的荧光检测不同的碱基 质量控制&#xff08;质控QC&#xff09; 去除低质量的序列 表达定量 统计reads数&#xff0c;进而得到表达矩阵 标准化 让所有样本处在同一起跑线上 主成分分析PCA 图中每个点都代表一个样本&#xff0c;不同颜色…

【Linux】网络套接字知识点补足

目录 1 地址转换函数 1.1 字符串转in_addr的函数: 1.2 in_addr转字符串的函数: 1.3 关于inet_ntoa 2 TCP协议通讯流程 1 地址转换函数 本节只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址但是我们通常用点分十进制的字符串…

[BabysqliV3.0]phar反序列化

文章目录 [BabysqliV3.0]phar反序列化 [BabysqliV3.0]phar反序列化 开始以为是sql注入 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ST1jvadM-1691302941344)(https://raw.githubusercontent.com/leekosss/photoBed/master/202308032140269.png)…

CentOS 7中,配置了Oracle jdk,但是使用java -version验证时,出现的版本是OpenJDK,如何解决?

1.首先&#xff0c;检查已安装的jdk版本 sudo yum list installed | grep java2.移除、卸载圈红的系统自带的openjdk sudo yum remove java-1.7.0-openjdk.x86_64 sudo yum remove java-1.7.0-openjdk-headless.x86_64 sudo yum remove java-1.8.0-openjdk.x86_64 sudo yum r…

java.util.NoSuchElementException: No value present-报错(已解决)

阿丹&#xff1a; 今天在spring-boot整合MongoDB的过程中出现了下面的错误&#xff0c;是因为追求新技术、更优雅产生的。 记录一下。 错误截图如下&#xff1a; 错误位置代码如下&#xff1a; 主要问题&#xff08;问题原因&#xff09;&#xff1a; 因为之前升级了我的jdk的…

Java基础——注解

1 概述 注解用于对Java中类、方法、成员变量做标记&#xff0c;然后进行特殊处理&#xff0c;至于到底做何种处理由业务需求来决定。 例如&#xff0c;JUnit框架中&#xff0c;标记了注解Test的方法就可以被当做测试方法进程执行 2 自定义注解 public interface 注解名称 {p…

GLTF在线场景编辑工具

推荐&#xff1a;使用 NSDT场景编辑器助你快速搭建可二次编辑的3D应用场景 以下是Babylon.js Sandbox的主要功能和特点&#xff1a; 1、创建和编辑场景&#xff1a;Babylon.js Sandbox允许用户在一个交互式的3D环境中创建和编辑glTF场景。您可以添加不同类型的物体、调整其位置…

重型并串式液压机械臂建模与simscape仿真

一、建模 Hydraulic manipulator Figure 1 shows different constituting parts of the manipulator considered, with every part labeled using numbers from 1 to 10. For each part, a CAD model is provided. Each file is named in accordance with the corresponding la…

基于YOLOv7的密集场景行人检测识别分析系统

密集场景下YOLO系列模型的精度如何&#xff1f;本文的主要目的就是想要基于密集场景基于YOLOv7模型开发构建人流计数系统&#xff0c;简单看下效果图&#xff1a; 这里实验部分使用到的数据集为VSCrowd数据集。 实例数据如下所示&#xff1a; 下载到本地解压缩后如下所示&…

K8s operator从0到1实战

Operator基础知识 Kubernetes Operator是一种用于管理和扩展Kubernetes应用程序的模式和工具。它们是一种自定义的Kubernetes控制器&#xff0c;可以根据特定的应用程序需求和业务逻辑扩展Kubernetes功能。 Kubernetes Operator基于Kubernetes的控制器模式&#xff0c;通过自…

cocos creator 的input.on 不生效

序&#xff1a; 1、执行input.on的时候发现不生效 2、一直按控制台也打印不出来console.log 3、先收藏这篇&#xff0c;因为到时候cocos要开发serveApi的时候&#xff0c;你得选一款趁手的后端开发并且&#xff0c;对习惯用ts写脚本的你来说&#xff0c;node是入门最快&#xf…

npm install报错 -> npm ERR! Unexpected token ‘.‘ 报错解决办法。

问题原因&#xff1a; 用nvm1.1.7的版本安装了16.x以上的node, 然后再下载依赖的时候就报错了&#xff1b;总结一下就是nvm版本太低了&#xff0c;他的里面没有集成高版本node导致的。 解决办法&#xff1a; 把nvm切换到新版本就行了。 1. 卸载掉当前所有的node nvm unins…

今天开始学习如何正式调查

本节要讲解三个内容 样本容量 调查方式 调查问卷的回收 在正式调查之前需要确定样本容量 就说要准备调查多少人确定好样本容量之后又要考虑设计的调查问卷 是以什么样的方式发出去 问卷的回收又要注意什么问题 要讲的主要内容 先看样本容量 样本容量确定的基本原…

【Paper Reading】CenterNet:Keypoint Triplets for Object Detection

背景 首先是借鉴Corner Net 表述了一下基于Anchor方法的不足&#xff1a; anchor的大小/比例需要人工来确认anchor并没有完全和gt的bbox对齐&#xff0c;不利于分类任务。 但是CornerNet也有自己的缺点 CornerNet 只预测了top-left和bottom-right 两个点&#xff0c;并没有…

S系列数字源表为何如此受欢迎?

为什么选择S系列数字源表? 性能强大-作为电压源和或电流源&#xff0c;并同步测量电流和或电压&#xff0c;支持四象限工作。可以限定电压或电流输出大小&#xff0c;预防器件损坏。覆盖3pA-3A的电流范围100μV-300V的电压范围&#xff0c;全量程测量精度0.03%。 灵活多样-支…

【高频面试题】微服务篇

文章目录 Spring Cloud1.Spring Cloud 5大组件有哪些&#xff1f;2.服务注册和发现是什么意思&#xff1f;Spring Cloud 如何实现服务注册发现&#xff1f;3.负载均衡如何实现的 ?4.什么是服务雪崩&#xff0c;怎么解决这个问题&#xff1f;5.微服务是怎么监控的 业务相关6.项…

arcgis--数据库构建网络数据集

1、打开arcmap软件&#xff0c;导入数据&#xff0c;如下&#xff1a; 该数据已经过处理&#xff0c;各交点处均被打断&#xff0c;并进行了拓扑检查。 2、在文件夹下新建文件数据库&#xff0c;名称为路网&#xff0c;在数据库下新建要素类&#xff0c;并导入道路shp文件&…

Matlab修改文本编码格式为UTF-8

一、修改文本编码格式 Matlab默认使用GBK编码格式&#xff0c;当代码中有中文注释时&#xff0c;注释显示乱码。 修改配置文件(安装目录下的bin目录有个lcdata.xml)&#xff0c;如下&#xff1a; 1. 删除 2. 修改 < encoding name”UTF-8”> < encoding_alias nam…

文件的管理

一、目录文件的管理 1、创建 2、编辑 3、查看内容 4、删除 二、普通文件的管理 1、创建 2、编辑 3、查看 1&#xff09;整个文档内容的查看 2&#xff09;文档列内容处理后查看 面试重点&#xff0c;年年考 3&#xff09;搜索文件内容查看&#xff0c;列出有关键字的一行 4、…