当前位置: 首页 > 科技观察

SpringBoot运行源码分析:Spring应用上下文准备

时间:2023-03-13 03:45:53 科技观察

Spring应用上下文准备上一节我们完成了应用上下文的创建,SpringApplication继续通过prepareContext方法准备应用上下文。首先通过图4-4从整体上理解prepareContext的核心功能和流程。结合流程图,看看SpringApplication中prepareContext方法的源码和功能注解。privatevoidprepareContext(ConfigurableApplicationContextcontext,ConfigurableEnvironmentenvironment,SpringApplicationRunListenerslisteners,ApplicationArgumentsapplicationArguments,BannerprintedBanner){//没置上下文的配置环境context.setEnvironment(environment);//应用上下文后置处理postProcessApplicationContext(context);//在context刷新之前,ApplicationContextInitializer初始化contextapplyInitializers(context);//通知监听器上下文准备完成。上面方法是上下文准备阶段,下面是上下文加载阶段listeners.contextPrepared(context);//打印日志并启动Profileif(this.logStartupInfo)-logStartupInfo(context.getParent()==nu1l);logStartupProfileInfo(context);}//获取ConfigurableListableBeanFactory并注册单例对象ConfigurableListableBeanFactorybeanFactory=context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments",applicationArguments);if(printedBanner!=null){//注册打印日志对象beanFactory.registerSingleton("springBootBanner",printedBanner);if(beanFactoryinstanceofDefaultlistableBeanFactory){//不设置是否允许盖书((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);//获取所有配置源,包括primarySources和sourcesSet<0bject>sources=getAllSources();Assert.notEmpty(sources,"Sourcesmustnotbeempty");//设置sourcesinBean被加载到contextload(context,sources.toArray(new0bject[0]));//监听的listenercontext被加载listeners.contextLoaded(context);}从流程图和具体代码可以看出,在该方法内完成两步操作:应用上下文的准备和加载。接下来,我们将详细解释具体的源代码。应用上下文准备阶段在上下文准备阶段,主要有三个步骤:为上下文设置环境,对应用上下文进行后处理,用ApplicationContextInitializer初始化上下文操作。首先是给context设置环境,代码和业务操作都很简单。publicvoidsetEnvironment(ConfigurableEnvironmentenvironment){//设置context的环境super.setEnvironment(environment);//设置context的reader属性的conditionEvaluator属性this.reader.settEnvironment(environment);//设置context的环境属性上下文this.scanner的扫描仪属性。setEnvironment(environment);}随后进行Spring应用上下文的后处理,这一步是通过postProcessApplicationContext方法完成的。protectedvoidpostProcessApplicationContext(ConfigurableApplicationConEextcontext){f(this.beanNameGenerator!=null){//如果beanNameGenerator为null,则根据默认名称注册当前beanNameGeneratorcontext.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATIONBEANNAMEGENERATOR,this.beanerNamesource)时为null,根据context的类型分别设置Resourceloader和CLassLoader(DefaultResourceLoader)context).setClassLoader(this.resourceLoader.getClassLoader());//如果为true,获取并设置转换服务f(this.addConversionService){context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());}postProcessApplicationContext方法主要完成context的后置操作,包含beanNameGeneratorResourceLoader.ClassLoader和ConversionService默认设置。该方法可以被子类覆盖以添加更多操作。而这个阶段beanNameGenerator和resourceLoader都是null,所以只操作最后一步的设置转换服务。最后,在通知侦听器上下文准备就绪之前,通过applyInitializers方法初始化上下文。使用的ApplicationContextInitializer正是我们在SpringApplication初始化阶段在itializers变量中设置的值,只是在通过getlnitializers方法获取的时候进行了去重和排序。protectedvoidapplyInitializers(ConfigurableApplicationContextcontext){//获取ApplicationContextInitializer集合并遍历for(ApplicationContextInitializerinitializer:getInitializers()){//解析当前初始化器。ClassrequiredType=GenericTypeResolver.resolveTypeArgument(text,initializer.initializer).class)实现的ApplicationContextInitializer的泛型参数;1Assert判断所需相似度是否匹配上下文类型Assert.isInstanceOf(requiredType,context,"Unabletocallinitializer.");//初始化contextinitializer.initialize(context);}}以上操作完成后,程序调用SpringApplicationRunListeners的contextPrepared方法通知监听器第一阶段的准备工作已经完成。应用上下文加载阶段应用上下文加载阶段包括以下步骤:打印log和Profile设置,设置是否允许覆盖注册,获取所有配置源,将配置源加载到上下文中,通知monitor上下文加载完成。进入应用上下文加载阶段的第一个操作就是打印log和设置profile,这里不再赘述。然后,获取ConfigurableListableBeanFactory并注册单例对象。注册的单例对象包括:ApplicationArguments和Banner。当BeanFactory为DefaultListableBeanFactory时,进入设置是否允许覆盖注册的处理逻辑。这里需要注意的是,当ApplicationArguments类单例对象被注册时,就意味着我们在使用Spring应用上下文的过程中可以通过依赖注入来使用该对象。@ResourceprivateApplicationArgumentsapplicationArguments;以上操作完成后,进入配置源信息处理阶段。这一步通过getAllSources方法合并配置源信息。publicSetgetAllSources(){Set<0object>allSources=newLinkedHashSet<>();if(!CollectionUtils.isEmpty(this.primarySources)){allSources.addAll(this.primarySources);if(!CollectionUtils.isEmpty(this.sources)){allSources.addAll(this.sources);}}返回集合.unmodifiableSet(allSources);}上面的操作逻辑很简单,如果Set集合中没有primarySources配置源或者sources配置源,就会添加到Set中,并将Set设置为不可修改,并返回。前面章节提到,变量primarySources的值来自于SpringApplication的构造参数,变量sources的值来自于setResources方法。获取到所有的配置源信息后,通过load方法将配置源信息加载到context中,代码如下。protectedvoidload(ApplicationContextcontext,Object[]sources){/log打印BeanDefinitionLoaderloader=createBeanDefinitionLoader(getBeanDefinitionRegistry(context),sources);f(this.beanNameGenerator!=nu1l).loader.setBeanNameGenerator(this.beanNameGenerator);if(this.resourceLoader!=nu1l){loader.setResourceLoader(this.resourceLoader);if(this.environment!=null){loader.setEnvironment(this.environment);loader.load();}这个方法主要是使用BeanDefinitionLoader配置资源加载操作.如果我们进一步查看createBeanDefinitionLoader这个方法的源码,就会发现它最终调用了BeanDefinitionLoader的构造函数并进行了初始化。BeanDefinitionLoader(BeanDefinitionRegistryregistry,Object...sources){this.sources=sources;this.annotatedReader=newAnnotatedBeanDefinitionReader(registry);this.xmlReader=newXmlBeanDefinitionReader(registry);if(isGroovyPresent())this.groovyReader=newGroovyBeanDefinitionReader(registry);}通过BeanDefinitionLoader的构造方法可以看出,BeanDefinitionLoader支持基于AnnotatedBeanDefinitionReaderXmlBeanDefinitionReader、GroovyBeanDefinitionReader等多种类型的加载操作。创建BeanDefinitionLoader并设置基本属性后,调用它的load方法,最后执行如下代码。privateintload(0objectsource){Assert.notNull(source,"Sourcemustnotbenull");if(sourceinstanceofClass){returnload((Class)source);}if(sourceinstanceofResource)returnload((Resource)source);}if(sourceinstanceofPackage){returnload((Package)source);}if(sourceinstanceofCharSequence){returnload((CharSequence)source);thrownewIllegalArgumentException("Invalidsourcetype"+source.getClass());}从上面的代码可以看出,BeanDefinitionLoader支持加载范围包括:Class、Resource、Package和CharSequence四种。前面我们提到,变量源的来源包括primarySources配置源和sources配置源。初始化时接收到的变量primarySources的类型是Class,变量sources通过set(Set)方法接收到的参数是String集合。所以在实际使用过程中,Resource和Package的判断分支是永远不能进入执行阶段的。完成以上操作后,执行SpringApplicationRunListeners的contextLoaded方法通知监听器上下文加载完成,至此整个Spring应用上下文的准备阶段就完成了。