我们知道在SpringCloud中,当配置变更时,我们通过访问http://xxxx/refresh,可以在不启动服务的情况下获取最新的配置,那么它是如何做到的呢,当我们更改数据库配置并刷新后,如何能获取最新的数据源对象呢?下面我们看SpringCloud如何做到的。
一、环境变化
1.1、关于ContextRefresher
当我们访问/refresh时,会被RefreshEndpoint类所处理。我们来看源代码:
/*
*Copyright2013-2014theoriginalauthororauthors.
*
*LicensedundertheApacheLicense,Version2.0(the"License");
*youmaynotusethisfileexceptincompliancewiththeLicense.
*YoumayobtainacopyoftheLicenseat
*
*http://www.apache.org/licenses/LICENSE-2.0
*
*Unlessrequiredbyapplicablelaworagreedtoinwriting,software
*distributedundertheLicenseisdistributedonan"ASIS"BASIS,
*WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
*SeetheLicenseforthespecificlanguagegoverningpermissionsand
*limitationsundertheLicense.
*/
packageorg.springframework.cloud.endpoint;
importjava.util.Arrays;
importjava.util.Collection;
importjava.util.Set;
importorg.springframework.boot.actuate.endpoint.AbstractEndpoint;
importorg.springframework.boot.context.properties.ConfigurationProperties;
importorg.springframework.cloud.context.refresh.ContextRefresher;
importorg.springframework.jmx.export.annotation.ManagedOperation;
importorg.springframework.jmx.export.annotation.ManagedResource;
/**
*@authorDaveSyer
*@authorVenilNoronha
*/
@ConfigurationProperties(prefix="endpoints.refresh",ignoreUnknownFields=false)
@ManagedResource
publicclassRefreshEndpointextendsAbstractEndpoint>{
privateContextRefreshercontextRefresher;
publicRefreshEndpoint(ContextRefreshercontextRefresher){
super("refresh");
this.contextRefresher=contextRefresher;
}
@ManagedOperation
publicString[]refresh(){
Setkeys=contextRefresher.refresh();
returnkeys.toArray(newString[keys.size()]);
}
@Override
publicCollectioninvoke(){
returnArrays.asList(refresh());
}
}
通过源代码我们了解到:当访问refresh端点时,实际上执行的是ContextRefresher的refresh方法,那么我们继续追踪源代码,找到其refresh方法:
publicsynchronizedSetrefresh(){
Mapbefore=extract(
this.context.getEnvironment().getPropertySources());
addConfigFilesToEnvironment();
Setkeys=changes(before,
extract(this.context.getEnvironment().getPropertySources())).keySet();
this.context.publishEvent(newEnvironmentChangeEvent(context,keys));
this.scope.refreshAll();
returnkeys;
}
我们可以看到refresh方法做了如下几件事情:
1)获取刷新之前的所有PropertySource
2)调用addConfigFilesToEnvironment方法获取最新的配置
3)调用changes方法更新配置信息
4)发布EnvironmentChangeEnvent事件
5)调用refreshScope的refreshAll方法刷新范围
我们重点关注一下2,3,4步骤
1.2、addConfigFilesToEnvironment方法
我们先来看看这个方法是怎么实现的:
/*fortesting*/ConfigurableApplicationContextaddConfigFilesToEnvironment(){
ConfigurableApplicationContextcapture=null;
try{
StandardEnvironmentenvironment=copyEnvironment(
this.context.getEnvironment());
SpringApplicationBuilderbuilder=newSpringApplicationBuilder(Empty.class)
.bannerMode(Mode.OFF).web(false).environment(environment);
//Justthelistenersthataffecttheenvironment(e.g.excludinglogging
//listenerbecauseithassideeffects)
builder.application()
.setListeners(Arrays.asList(newBootstrapApplicationListener(),
newConfigFileApplicationListener()));
capture=builder.run();
if(environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)){
environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);
}
MutablePropertySourcestarget=this.context.getEnvironment()
.getPropertySources();
StringtargetName=null;
for(PropertySource>source:environment.getPropertySources()){
Stringname=source.getName();
if(target.contains(name)){
targetName=name;
}
if(!this.standardSources.contains(name)){
if(target.contains(name)){
target.replace(name,source);
}
else{
if(targetName!=null){
target.addAfter(targetName,source);
}
else{
//targetNamewasnullsoweareatthestartofthelist
target.addFirst(source);
targetName=name;
}
}
}
}
}
finally{
ConfigurableApplicationContextcloseable=capture;
while(closeable!=null){
try{
closeable.close();
}
catch(Exceptione){
//Ignore;
}
if(closeable.getParent()instanceofConfigurableApplicationContext){
closeable=(ConfigurableApplicationContext)closeable.getParent();
}
else{
break;
}
}
}
returncapture;
}
1)该方法首先拷贝当前的Environment
2)通过SpringApplicationBuilder构建了一个简单的SpringBoot启动程序并启动
builder.application().setListeners(Arrays.asList(newBootstrapApplicationListener(),
newConfigFileApplicationListener()));
这里面会添加两个监听器分别为:BootstrapApplicationListener与ConfigFileApplicationListener,通过先前的学习,我们知道BootstrapApplicationListener是引导程序的核心监听器,而ConfigFileApplicationListener也是非常重要的类:
/*
*Copyright2012-2017theoriginalauthororauthors.
*
*LicensedundertheApacheLicense,Version2.0(the"License");
*youmaynotusethisfileexceptincompliancewiththeLicense.
*YoumayobtainacopyoftheLicenseat
*
*http://www.apache.org/licenses/LICENSE-2.0
*
*Unlessrequiredbyapplicablelaworagreedtoinwriting,software
*distributedundertheLicenseisdistributedonan"ASIS"BASIS,
*WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
*SeetheLicenseforthespecificlanguagegoverningpermissionsand
*limitationsundertheLicense.
*/
packageorg.springframework.boot.context.config;
importjava.io.IOException;
importjava.util.ArrayList;
importjava.util.Arrays;
importjava.util.Collection;
importjava.util.Collections;
importjava.util.Iterator;
importjava.util.LinkedHashSet;
importjava.util.LinkedList;
importjava.util.List;
importjava.util.Queue;
importjava.util.Set;
importorg.apache.commons.logging.Log;
importorg.springframework.beans.BeansException;
importorg.springframework.beans.CachedIntrospectionResults;
importorg.springframework.beans.factory.config.BeanFactoryPostProcessor;
importorg.springframework.beans.factory.config.ConfigurableListableBeanFactory;
importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.bind.PropertiesConfigurationFactory;
importorg.springframework.boot.bind.PropertySourcesPropertyValues;
importorg.springframework.boot.bind.RelaxedDataBinder;
importorg.springframework.boot.bind.RelaxedPropertyResolver;
importorg.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
importorg.springframework.boot.context.event.ApplicationPreparedEvent;
importorg.springframework.boot.env.EnumerableCompositePropertySource;
importorg.springframework.boot.env.EnvironmentPostProcessor;
importorg.springframework.boot.env.PropertySourcesLoader;
importorg.springframework.boot.logging.DeferredLog;
importorg.springframework.context.ApplicationEvent;
importorg.springframework.context.ConfigurableApplicationContext;
importorg.springframework.context.annotation.ConfigurationClassPostProcessor;
importorg.springframework.context.event.SmartApplicationListener;
importorg.springframework.core.Ordered;
importorg.springframework.core.annotation.AnnotationAwareOrderComparator;
importorg.springframework.core.convert.ConversionService;
importorg.springframework.core.convert.support.DefaultConversionService;
importorg.springframework.core.env.ConfigurableEnvironment;
importorg.springframework.core.env.EnumerablePropertySource;
importorg.springframework.core.env.MutablePropertySources;
importorg.springframework.core.env.PropertySource;
importorg.springframework.core.env.PropertySources;
importorg.springframework.core.io.DefaultResourceLoader;
importorg.springframework.core.io.Resource;
importorg.springframework.core.io.ResourceLoader;
importorg.springframework.core.io.support.SpringFactoriesLoader;
importorg.springframework.util.Assert;
importorg.springframework.util.ResourceUtils;
importorg.springframework.util.StringUtils;
importorg.springframework.validation.BindException;
/**
*{@linkEnvironmentPostProcessor}thatconfiguresthecontextenvironmentbyloading
*propertiesfromwellknownfilelocations.Bydefaultpropertieswillbeloadedfrom
*'application.properties'and/or'application.yml'filesinthefollowinglocations:
*
*- classpath:
*- file:./
*- classpath:config/
*- file:./config/:
*
*
*Alternativesearchlocationsandnamescanbespecifiedusing
*{@link#setSearchLocations(String)}and{@link#setSearchNames(String)}.
*
*Additionalfileswillalsobeloadedbasedonactiveprofiles.Forexampleifa'web'
*profileisactive'application-web.properties'and'application-web.yml'willbe
*considered.
*
*The'spring.config.name'propertycanbeusedtospecifyanalternativenametoload
*andthe'spring.config.location'propertycanbeusedtospecifyalternativesearch
*locationsorspecificfiles.
*
*Configurationpropertiesarealsoboundtothe{@linkSpringApplication}.Thismakesit
*possibletoset{@linkSpringApplication}propertiesdynamically,likethesources
*("spring.main.sources"-aCSVlist)theflagtoindicateawebenvironment
*("spring.main.web_environment=true")ortheflagtoswitchoffthebanner
*("spring.main.show_banner=false").
*
*@authorDaveSyer
*@authorPhillipWebb
*@authorStephaneNicoll
*@authorAndyWilkinson
*@authorEddúMeléndez
*/
publicclassConfigFileApplicationListener
implementsEnvironmentPostProcessor,SmartApplicationListener,Ordered{
privatestaticfinalStringDEFAULT_PROPERTIES="defaultProperties";
//Notetheorderisfromleasttomostspecific(lastonewins)
privatestaticfinalStringDEFAULT_SEARCH_LOCATIONS="classpath:/,classpath:/config/,file:./,file:./config/";
privatestaticfinalStringDEFAULT_NAMES="application";
/**
*The"activeprofiles"propertyname.
*/
publicstaticfinalStringACTIVE_PROFILES_PROPERTY="spring.profiles.active";
/**
*The"includesprofiles"propertyname.
*/
publicstaticfinalStringINCLUDE_PROFILES_PROPERTY="spring.profiles.include";
/**
*The"configname"propertyname.
*/
publicstaticfinalStringCONFIG_NAME_PROPERTY="spring.config.name";
/**
*The"configlocation"propertyname.
*/
publicstaticfinalStringCONFIG_LOCATION_PROPERTY="spring.config.location";
/**
*Thedefaultorderfortheprocessor.
*/
publicstaticfinalintDEFAULT_ORDER=Ordered.HIGHEST_PRECEDENCE+10;
/**
*Nameoftheapplicationconfiguration{@linkPropertySource}.
*/
publicstaticfinalStringAPPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME="applicationConfigurationProperties";
privatefinalDeferredLoglogger=newDeferredLog();
privateStringsearchLocations;
privateStringnames;
privateintorder=DEFAULT_ORDER;
privatefinalConversionServiceconversionService=newDefaultConversionService();
@Override
publicbooleansupportsEventType(ClasseventType){
returnApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)
||ApplicationPreparedEvent.class.isAssignableFrom(eventType);
}
@Override
publicbooleansupportsSourceType(Class>aClass){
returntrue;
}
@Override
publicvoidonApplicationEvent(ApplicationEventevent){
if(eventinstanceofApplicationEnvironmentPreparedEvent){
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent)event);
}
if(eventinstanceofApplicationPreparedEvent){
onApplicationPreparedEvent(event);
}
}
privatevoidonApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEventevent){
ListpostProcessors=loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for(EnvironmentPostProcessorpostProcessor:postProcessors){
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
ListloadPostProcessors(){
returnSpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,
getClass().getClassLoader());
}
@Override
publicvoidpostProcessEnvironment(ConfigurableEnvironmentenvironment,
SpringApplicationapplication){
addPropertySources(environment,application.getResourceLoader());
configureIgnoreBeanInfo(environment);
bindToSpringApplication(environment,application);
}
privatevoidconfigureIgnoreBeanInfo(ConfigurableEnvironmentenvironment){
if(System.getProperty(
CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME)==null){
RelaxedPropertyResolverresolver=newRelaxedPropertyResolver(environment,
"spring.beaninfo.");
Booleanignore=resolver.getProperty("ignore",Boolean.class,Boolean.TRUE);
System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
ignore.toString());
}
}
privatevoidonApplicationPreparedEvent(ApplicationEventevent){
this.logger.replayTo(ConfigFileApplicationListener.class);
addPostProcessors(((ApplicationPreparedEvent)event).getApplicationContext());
}
/**
*Addconfigfilepropertysourcestothespecifiedenvironment.
*@paramenvironmenttheenvironmenttoaddsourceto
*@paramresourceLoadertheresourceloader
*@see#addPostProcessors(ConfigurableApplicationContext)
*/
protectedvoidaddPropertySources(ConfigurableEnvironmentenvironment,
ResourceLoaderresourceLoader){
RandomValuePropertySource.addToEnvironment(environment);
newLoader(environment,resourceLoader).load();
}
/**
*Bindtheenvironmenttothe{@linkSpringApplication}.
*@paramenvironmenttheenvironmenttobind
*@paramapplicationtheapplicationtobindto
*/
protectedvoidbindToSpringApplication(ConfigurableEnvironmentenvironment,
SpringApplicationapplication){
PropertiesConfigurationFactorybinder=newPropertiesConfigurationFactory(
application);
binder.setTargetName("spring.main");
binder.setConversionService(this.conversionService);
binder.setPropertySources(environment.getPropertySources());
try{
binder.bindPropertiesToTarget();
}
catch(BindExceptionex){
thrownewIllegalStateException("CannotbindtoSpringApplication",ex);
}
}
/**
*Addappropriatepost-processorstopost-configuretheproperty-sources.
*@paramcontextthecontexttoconfigure
*/
protectedvoidaddPostProcessors(ConfigurableApplicationContextcontext){
context.addBeanFactoryPostProcessor(
newPropertySourceOrderingPostProcessor(context));
}
publicvoidsetOrder(intorder){
this.order=order;
}
@Override
publicintgetOrder(){
returnthis.order;
}
/**
*Setthesearchlocationsthatwillbeconsideredasacomma-separatedlist.Each
*searchlocationshouldbeadirectorypath(endingin"/")anditwillbeprefixed
*bythefilenamesconstructedfrom{@link#setSearchNames(String)searchnames}and
*profiles(ifany)plusfileextensionssupportedbythepropertiesloaders.
*Locationsareconsideredintheorderspecified,withlateritemstakingprecedence
*(likeamapmerge).
*@paramlocationsthesearchlocations
*/
publicvoidsetSearchLocations(Stringlocations){
Assert.hasLength(locations,"Locationsmustnotbeempty");
this.searchLocations=locations;
}
/**
*Setsthenamesofthefilesthatshouldbeloaded(excludingfileextension)asa
*comma-separatedlist.
*@paramnamesthenamestoload
*/
publicvoidsetSearchNames(Stringnames){
Assert.hasLength(names,"Namesmustnotbeempty");
this.names=names;
}
/**
*{@linkBeanFactoryPostProcessor}tore-orderourpropertysourcesbelowany
*{@code@PropertySource}itemsaddedbythe{@linkConfigurationClassPostProcessor}.
*/
privateclassPropertySourceOrderingPostProcessor
implementsBeanFactoryPostProcessor,Ordered{
privateConfigurableApplicationContextcontext;
PropertySourceOrderingPostProcessor(ConfigurableApplicationContextcontext){
this.context=context;
}
@Override
publicintgetOrder(){
returnOrdered.HIGHEST_PRECEDENCE;
}
@Override
publicvoidpostProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory)
throwsBeansException{
reorderSources(this.context.getEnvironment());
}
privatevoidreorderSources(ConfigurableEnvironmentenvironment){
ConfigurationPropertySources
.finishAndRelocate(environment.getPropertySources());
PropertySource>defaultProperties=environment.getPropertySources()
.remove(DEFAULT_PROPERTIES);
if(defaultProperties!=null){
environment.getPropertySources().addLast(defaultProperties);
}
}
}
/**
*Loadscandidatepropertysourcesandconfigurestheactiveprofiles.
*/
privateclassLoader{
privatefinalLoglogger=ConfigFileApplicationListener.this.logger;
privatefinalConfigurableEnvironmentenvironment;
privatefinalResourceLoaderresourceLoader;
privatePropertySourcesLoaderpropertiesLoader;
privateQueueprofiles;
privateListprocessedProfiles;
privatebooleanactivatedProfiles;
Loader(ConfigurableEnvironmentenvironment,ResourceLoaderresourceLoader){
this.environment=environment;
this.resourceLoader=resourceLoader==null?newDefaultResourceLoader()
:resourceLoader;
}
publicvoidload(){
this.propertiesLoader=newPropertySourcesLoader();
this.activatedProfiles=false;
this.profiles=Collections.asLifoQueue(newLinkedList());
this.processedProfiles=newLinkedList();
//Pre-existingactiveprofilessetviaEnvironment.setActiveProfiles()
//areadditionalprofilesandconfigfilesareallowedtoaddmoreif
//theywantto,sodon'tcalladdActiveProfiles()here.
SetinitialActiveProfiles=initializeActiveProfiles();
this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
if(this.profiles.isEmpty()){
for(StringdefaultProfileName:this.environment.getDefaultProfiles()){
ProfiledefaultProfile=newProfile(defaultProfileName,true);
if(!this.profiles.contains(defaultProfile)){
this.profiles.add(defaultProfile);
}
}
}
//Thedefaultprofileforthesepurposesisrepresentedasnull.Weaddit
//lastsothatitisfirstoutofthequeue(activeprofileswillthen
//overrideanysettingsinthedefaultswhenthelistisreversedlater).
this.profiles.add(null);
while(!this.profiles.isEmpty()){
Profileprofile=this.profiles.poll();
for(Stringlocation:getSearchLocations()){
if(!location.endsWith("/")){
//locationisafilenamealready,sodon'tsearchformore
//filenames
load(location,null,profile);
}
else{
for(Stringname:getSearchNames()){
load(location,name,profile);
}
}
}
this.processedProfiles.add(profile);
}
addConfigurationProperties(this.propertiesLoader.getPropertySources());
}
privateSetinitializeActiveProfiles(){
if(!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)
&&!this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)){
returnCollections.emptySet();
}
//Anypre-existingactiveprofilessetviapropertysources(e.g.System
//properties)takeprecedenceoverthoseaddedinconfigfiles.
SpringProfilesspringProfiles=bindSpringProfiles(
this.environment.getPropertySources());
SetactiveProfiles=newLinkedHashSet(
springProfiles.getActiveProfiles());
activeProfiles.addAll(springProfiles.getIncludeProfiles());
maybeActivateProfiles(activeProfiles);
returnactiveProfiles;
}
/**
*Returntheactiveprofilesthathavenotbeenprocessedyet.Ifaprofileis
*enabledviaboth{@link#ACTIVE_PROFILES_PROPERTY}and
*{@linkConfigurableEnvironment#addActiveProfile(String)}itneedstobe
*filteredsothatthe{@link#ACTIVE_PROFILES_PROPERTY}valuetakesprecedence.
*
*Concretely,ifthe"cloud"profileisenabledviatheenvironment,itwilltake
*lessprecedencethatanyprofilesetviathe{@link#ACTIVE_PROFILES_PROPERTY}.
*@paraminitialActiveProfilestheprofilesthathavebeenenabledvia
*{@link#ACTIVE_PROFILES_PROPERTY}
*@returntheunprocessedactiveprofilesfromtheenvironmenttoenable
*/
privateListgetUnprocessedActiveProfiles(
SetinitialActiveProfiles){
ListunprocessedActiveProfiles=newArrayList();
for(StringprofileName:this.environment.getActiveProfiles()){
Profileprofile=newProfile(profileName);
if(!initialActiveProfiles.contains(profile)){
unprocessedActiveProfiles.add(profile);
}
}
//ReversethemsotheorderisthesameasfromgetProfilesForValue()
//(lastonewinswhenpropertiesareeventuallyresolved)
Collections.reverse(unprocessedActiveProfiles);
returnunprocessedActiveProfiles;
}
privatevoidload(Stringlocation,Stringname,Profileprofile){
Stringgroup="profile="+(profile==null?"":profile);
if(!StringUtils.hasText(name)){
//Trytoloaddirectlyfromthelocation
loadIntoGroup(group,location,profile);
}
else{
//Searchforafilewiththegivenname
for(Stringext:this.propertiesLoader.getAllFileExtensions()){
if(profile!=null){
//Trytheprofile-specificfile
loadIntoGroup(group,location+name+"-"+profile+"."+ext,
null);
for(ProfileprocessedProfile:this.processedProfiles){
if(processedProfile!=null){
loadIntoGroup(group,location+name+"-"
+processedProfile+"."+ext,profile);
}
}
//Sometimespeopleput"spring.profiles:dev"in
//application-dev.yml(gh-340).Arguablyweshouldtryanderror
//outonthat,butwecanbekindandloaditanyway.
loadIntoGroup(group,location+name+"-"+profile+"."+ext,
profile);
}
//Alsotrytheprofile-specificsection(ifany)ofthenormalfile
loadIntoGroup(group,location+name+"."+ext,profile);
}
}
}
privatePropertySource>loadIntoGroup(Stringidentifier,Stringlocation,
Profileprofile){
try{
returndoLoadIntoGroup(identifier,location,profile);
}
catch(Exceptionex){
thrownewIllegalStateException(
"Failedtoloadpropertysourcefromlocation'"+location+"'",
ex);
}
}
privatePropertySource>doLoadIntoGroup(Stringidentifier,Stringlocation,
Profileprofile)throwsIOException{
Resourceresource=this.resourceLoader.getResource(location);
PropertySource>propertySource=null;
StringBuildermsg=newStringBuilder();
if(resource!=null&&resource.exists()){
Stringname="applicationConfig:["+location+"]";
Stringgroup="applicationConfig:["+identifier+"]";
propertySource=this.propertiesLoader.load(resource,group,name,
(profile==null?null:profile.getName()));
if(propertySource!=null){
msg.append("Loaded");
handleProfileProperties(propertySource);
}
else{
msg.append("Skipped(empty)");
}
}
else{
msg.append("Skipped");
}
msg.append("configfile");
msg.append(getResourceDescription(location,resource));
if(profile!=null){
msg.append("forprofile").append(profile);
}
if(resource==null||!resource.exists()){
msg.append("resourcenotfound");
this.logger.trace(msg);
}
else{
this.logger.debug(msg);
}
returnpropertySource;
}
privateStringgetResourceDescription(Stringlocation,Resourceresource){
StringresourceDescription="'"+location+"'";
if(resource!=null){
try{
resourceDescription=String.format("'%s'(%s)",
resource.getURI().toASCIIString(),location);
}
catch(IOExceptionex){
//Usethelocationasthedescription
}
}
returnresourceDescription;
}
privatevoidhandleProfileProperties(PropertySource>propertySource){
SpringProfilesspringProfiles=bindSpringProfiles(propertySource);
maybeActivateProfiles(springProfiles.getActiveProfiles());
addProfiles(springProfiles.getIncludeProfiles());
}
privateSpringProfilesbindSpringProfiles(PropertySource>propertySource){
MutablePropertySourcespropertySources=newMutablePropertySources();
propertySources.addFirst(propertySource);
returnbindSpringProfiles(propertySources);
}
privateSpringProfilesbindSpringProfiles(PropertySourcespropertySources){
SpringProfilesspringProfiles=newSpringProfiles();
RelaxedDataBinderdataBinder=newRelaxedDataBinder(springProfiles,
"spring.profiles");
dataBinder.bind(newPropertySourcesPropertyValues(propertySources,false));
springProfiles.setActive(resolvePlaceholders(springProfiles.getActive()));
springProfiles.setInclude(resolvePlaceholders(springProfiles.getInclude()));
returnspringProfiles;
}
privateListresolvePlaceholders(Listvalues){
Listresolved=newArrayList();
for(Stringvalue:values){
resolved.add(this.environment.resolvePlaceholders(value));
}
returnresolved;
}
privatevoidmaybeActivateProfiles(Setprofiles){
if(this.activatedProfiles){
if(!profiles.isEmpty()){
this.logger.debug("Profilesalreadyactivated,'"+profiles
+"'willnotbeapplied");
}
return;
}
if(!profiles.isEmpty()){
addProfiles(profiles);
this.logger.debug("Activatedprofiles"
+StringUtils.collectionToCommaDelimitedString(profiles));
this.activatedProfiles=true;
removeUnprocessedDefaultProfiles();
}
}
privatevoidremoveUnprocessedDefaultProfiles(){
for(Iteratoriterator=this.profiles.iterator();iterator
.hasNext();){
if(iterator.next().isDefaultProfile()){
iterator.remove();
}
}
}
privatevoidaddProfiles(Setprofiles){
for(Profileprofile:profiles){
this.profiles.add(profile);
if(!environmentHasActiveProfile(profile.getName())){
//Ifit'salreadyacceptedweassumetheorderwasset
//intentionally
prependProfile(this.environment,profile);
}
}
}
privatebooleanenvironmentHasActiveProfile(Stringprofile){
for(StringactiveProfile:this.environment.getActiveProfiles()){
if(activeProfile.equals(profile)){
returntrue;
}
}
returnfalse;
}
privatevoidprependProfile(ConfigurableEnvironmentenvironment,
Profileprofile){
Setprofiles=newLinkedHashSet();
environment.getActiveProfiles();//ensuretheyareinitialized
//Butthisoneshouldgofirst(lastwinsinapropertykeyclash)
profiles.add(profile.getName());
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(profiles.toArray(newString[profiles.size()]));
}
privateSetgetSearchLocations(){
Setlocations=newLinkedHashSet();
//User-configuredsettingstakeprecedence,sowedothemfirst
if(this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)){
for(Stringpath:asResolvedSet(
this.environment.getProperty(CONFIG_LOCATION_PROPERTY),null)){
if(!path.contains("$")){
path=StringUtils.cleanPath(path);
if(!ResourceUtils.isUrl(path)){
path=ResourceUtils.FILE_URL_PREFIX+path;
}
}
locations.add(path);
}
}
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
DEFAULT_SEARCH_LOCATIONS));
returnlocations;
}
privateSetgetSearchNames(){
if(this.environment.containsProperty(CONFIG_NAME_PROPERTY)){
returnasResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
null);
}
returnasResolvedSet(ConfigFileApplicationListener.this.names,DEFAULT_NAMES);
}
privateSetasResolvedSet(Stringvalue,Stringfallback){
Listlist=Arrays.asList(StringUtils.trimArrayElements(
StringUtils.commaDelimitedListToStringArray(value!=null
?this.environment.resolvePlaceholders(value):fallback)));
Collections.reverse(list);
returnnewLinkedHashSet(list);
}
privatevoidaddConfigurationProperties(MutablePropertySourcessources){
List>reorderedSources=newArrayList>();
for(PropertySource>item:sources){
reorderedSources.add(item);
}
addConfigurationProperties(
newConfigurationPropertySources(reorderedSources));
}
privatevoidaddConfigurationProperties(
ConfigurationPropertySourcesconfigurationSources){
MutablePropertySourcesexistingSources=this.environment
.getPropertySources();
if(existingSources.contains(DEFAULT_PROPERTIES)){
existingSources.addBefore(DEFAULT_PROPERTIES,configurationSources);
}
else{
existingSources.addLast(configurationSources);
}
}
}
privatestaticclassProfile{
privatefinalStringname;
privatefinalbooleandefaultProfile;
Profile(Stringname){
this(name,false);
}
Profile(Stringname,booleandefaultProfile){
Assert.notNull(name,"Namemustnotbenull");
this.name=name;
this.defaultProfile=defaultProfile;
}
publicStringgetName(){
returnthis.name;
}
publicbooleanisDefaultProfile(){
returnthis.defaultProfile;
}
@Override
publicStringtoString(){
returnthis.name;
}
@Override
publicinthashCode(){
returnthis.name.hashCode();
}
@Override
publicbooleanequals(Objectobj){
if(obj==this){
returntrue;
}
if(obj==null||obj.getClass()!=getClass()){
returnfalse;
}
return((Profile)obj).name.equals(this.name);
}
}
/**
*Holdstheconfiguration{@linkPropertySource}sastheyareloadedcanrelocate
*themonceconfigurationclasseshavebeenprocessed.
*/
staticclassConfigurationPropertySources
extendsEnumerablePropertySource>>{
privatefinalCollection>sources;
privatefinalString[]names;
ConfigurationPropertySources(Collection>sources){
super(APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME,sources);
this.sources=sources;
Listnames=newArrayList();
for(PropertySource>source:sources){
if(sourceinstanceofEnumerablePropertySource){
names.addAll(Arrays.asList(
((EnumerablePropertySource>)source).getPropertyNames()));
}
}
this.names=names.toArray(newString[names.size()]);
}
@Override
publicObjectgetProperty(Stringname){
for(PropertySource>propertySource:this.sources){
Objectvalue=propertySource.getProperty(name);
if(value!=null){
returnvalue;
}
}
returnnull;
}
publicstaticvoidfinishAndRelocate(MutablePropertySourcespropertySources){
Stringname=APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME;
ConfigurationPropertySourcesremoved=(ConfigurationPropertySources)propertySources
.get(name);
if(removed!=null){
for(PropertySource>propertySource:removed.sources){
if(propertySourceinstanceofEnumerableCompositePropertySource){
EnumerableCompositePropertySourcecomposite=(EnumerableCompositePropertySource)propertySource;
for(PropertySource>nested:composite.getSource()){
propertySources.addAfter(name,nested);
name=nested.getName();
}
}
else{
propertySources.addAfter(name,propertySource);
}
}
propertySources.remove(APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME);
}
}
@Override
publicString[]getPropertyNames(){
returnthis.names;
}
}
/**
*Holderfor{@codespring.profiles}properties.
*/
staticfinalclassSpringProfiles{
privateListactive=newArrayList();
privateListinclude=newArrayList();
publicListgetActive(){
returnthis.active;
}
publicvoidsetActive(Listactive){
this.active=active;
}
publicListgetInclude(){
returnthis.include;
}
publicvoidsetInclude(Listinclude){
this.include=include;
}
SetgetActiveProfiles(){
returnasProfileSet(this.active);
}
SetgetIncludeProfiles(){
returnasProfileSet(this.include);
}
privateSetasProfileSet(ListprofileNames){
Listprofiles=newArrayList();
for(StringprofileName:profileNames){
profiles.add(newProfile(profileName));
}
Collections.reverse(profiles);
returnnewLinkedHashSet(profiles);
}
}
}
根据javadoc注释的说明,这个类会从指定的位置加载application.properties或者application.yml并将它们的属性读到Envrionment当中,其中这几个方法大家关注下:
@Override
publicvoidonApplicationEvent(ApplicationEventevent){
if(eventinstanceofApplicationEnvironmentPreparedEvent){
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent)event);
}
if(eventinstanceofApplicationPreparedEvent){
onApplicationPreparedEvent(event);
}
}
当springboot程序启动时一定会触发该事件监听,如果当前是ApplicationEnvironmentPreparedEvent事件就会调用onApplicationEnvironmentPreparedEvent方法,最终该方法会执行:
@Override
publicvoidpostProcessEnvironment(ConfigurableEnvironmentenvironment,
SpringApplicationapplication){
addPropertySources(environment,application.getResourceLoader());
configureIgnoreBeanInfo(environment);
bindToSpringApplication(environment,application);
}
其中bindToSpringApplication方法为:
/**
*Bindtheenvironmenttothe{@linkSpringApplication}.
*@paramenvironmenttheenvironmenttobind
*@paramapplicationtheapplicationtobindto
*/
protectedvoidbindToSpringApplication(ConfigurableEnvironmentenvironment,
SpringApplicationapplication){
PropertiesConfigurationFactorybinder=newPropertiesConfigurationFactory(
application);
binder.setTargetName("spring.main");
binder.setConversionService(this.conversionService);
binder.setPropertySources(environment.getPropertySources());
try{
binder.bindPropertiesToTarget();
}
catch(BindExceptionex){
thrownewIllegalStateException("CannotbindtoSpringApplication",ex);
}
}
很明显该方法是将Environment绑定到对应SpringApplication上,通过这个类就可以获取到我们更改过后的配置了
1.3、changes方法
privateMapchanges(Mapbefore,
Mapafter){
Mapresult=newHashMap();
for(Stringkey:before.keySet()){
if(!after.containsKey(key)){
result.put(key,null);
}
elseif(!equal(before.get(key),after.get(key))){
result.put(key,after.get(key));
}
}
for(Stringkey:after.keySet()){
if(!before.containsKey(key)){
result.put(key,after.get(key));
}
}
returnresult;
}
changes方法其实就是处理配置变更信息的,分以下几种情况:
1)如果刷新过后配置文件新增配置就添加到Map里
2) 如果有配置变更就添加变更后的配置
3)如果删除了原先的配置,就把原先的key对应的值设置为null
至此经过changes方法后,上下文环境已经拥有最新的配置了。
1.4、发布事件
当上述步骤都执行完毕后,紧接着会发布EnvrionmentChangeEvent事件,可是这个事件谁来监听呢?在这里我贴出官网的一段描述:
应用程序将收听EnvironmentChangeEvent,并以几种标准方式进行更改(用户可以以常规方式添加ApplicationListeners附加ApplicationListeners)。当观察到EnvironmentChangeEvent时,它将有一个已更改的键值列表,应用程序将使用以下内容:
1.重新绑定上下文中的任何@ConfigurationPropertiesbean
2.为logging.level.*中的任何属性设置记录器级别
根据官网描述我们知道将变更一下操作行为@ConfigurationProperties的bean与更改日志level,那么如何做到的呢?结合官网文档我们来关注以下两个类:
ConfigurationPropertiesRebinder:
/*
*Copyright2013-2014theoriginalauthororauthors.
*
*LicensedundertheApacheLicense,Version2.0(the"License");
*youmaynotusethisfileexceptincompliancewiththeLicense.
*YoumayobtainacopyoftheLicenseat
*
*http://www.apache.org/licenses/LICENSE-2.0
*
*Unlessrequiredbyapplicablelaworagreedtoinwriting,software
*distributedundertheLicenseisdistributedonan"ASIS"BASIS,
*WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
*SeetheLicenseforthespecificlanguagegoverningpermissionsand
*limitationsundertheLicense.
*/
packageorg.springframework.cloud.context.properties;
importjava.util.HashSet;
importjava.util.Map;
importjava.util.Set;
importjava.util.concurrent.ConcurrentHashMap;
importorg.springframework.aop.framework.Advised;
importorg.springframework.aop.support.AopUtils;
importorg.springframework.beans.BeansException;
importorg.springframework.boot.context.properties.ConfigurationProperties;
importorg.springframework.cloud.context.config.annotation.RefreshScope;
importorg.springframework.cloud.context.environment.EnvironmentChangeEvent;
importorg.springframework.context.ApplicationContext;
importorg.springframework.context.ApplicationContextAware;
importorg.springframework.context.ApplicationListener;
importorg.springframework.core.env.Environment;
importorg.springframework.jmx.export.annotation.ManagedAttribute;
importorg.springframework.jmx.export.annotation.ManagedOperation;
importorg.springframework.jmx.export.annotation.ManagedResource;
importorg.springframework.stereotype.Component;
/**
*Listensfor{@linkEnvironmentChangeEvent}andrebindsbeansthatwereboundtothe
*{@linkEnvironment}using{@linkConfigurationProperties
*@ConfigurationProperties
}.Whenthesebeansarere-boundand
*re-initializedthechangesareavailableimmediatelytoanycomponentthatisusingthe
*@ConfigurationProperties
bean.
*
*@seeRefreshScopeforadeeperandoptionallymorefocusedrefreshofbeancomponents
*
*@authorDaveSyer
*
*/
@Component
@ManagedResource
publicclassConfigurationPropertiesRebinder
implementsApplicationContextAware,ApplicationListener{
privateConfigurationPropertiesBeansbeans;
privateApplicationContextapplicationContext;
privateMaperrors=newConcurrentHashMap<>();
publicConfigurationPropertiesRebinder(ConfigurationPropertiesBeansbeans){
this.beans=beans;
}
@Override
publicvoidsetApplicationContext(ApplicationContextapplicationContext)
throwsBeansException{
this.applicationContext=applicationContext;
}
/**
*Amapofbeannametoerrorswheninstantiatingthebean.
*
*@returntheerrorsaccumulatedsincethelatestdestroy
*/
publicMapgetErrors(){
returnthis.errors;
}
@ManagedOperation
publicvoidrebind(){
this.errors.clear();
for(Stringname:this.beans.getBeanNames()){
rebind(name);
}
}
@ManagedOperation
publicbooleanrebind(Stringname){
if(!this.beans.getBeanNames().contains(name)){
returnfalse;
}
if(this.applicationContext!=null){
try{
Objectbean=this.applicationContext.getBean(name);
if(AopUtils.isAopProxy(bean)){
bean=getTargetObject(bean);
}
this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);
this.applicationContext.getAutowireCapableBeanFactory()
.initializeBean(bean,name);
returntrue;
}
catch(RuntimeExceptione){
this.errors.put(name,e);
throwe;
}
}
returnfalse;
}
@SuppressWarnings("unchecked")
privatestaticTgetTargetObject(Objectcandidate){
try{
if(AopUtils.isAopProxy(candidate)&&(candidateinstanceofAdvised)){
return(T)((Advised)candidate).getTargetSource().getTarget();
}
}
catch(Exceptionex){
thrownewIllegalStateException("Failedtounwrapproxiedobject",ex);
}
return(T)candidate;
}
@ManagedAttribute
publicSetgetBeanNames(){
returnnewHashSet(this.beans.getBeanNames());
}
@Override
publicvoidonApplicationEvent(EnvironmentChangeEventevent){
if(this.applicationContext.equals(event.getSource())
//Backwardscompatible
||event.getKeys().equals(event.getSource())){
rebind();
}
}
}
我们可以看到该类监听了ChangeEnvrionmentEvent事件,它最主要作用是拿到更新的配置以后,重新绑定@ConfigurationProperties标记的类使之能够读取最新的属性
LoggingRebinder:
/*
*Copyright2013-2014theoriginalauthororauthors.
*
*LicensedundertheApacheLicense,Version2.0(the"License");
*youmaynotusethisfileexceptincompliancewiththeLicense.
*YoumayobtainacopyoftheLicenseat
*
*http://www.apache.org/licenses/LICENSE-2.0
*
*Unlessrequiredbyapplicablelaworagreedtoinwriting,software
*distributedundertheLicenseisdistributedonan"ASIS"BASIS,
*WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
*SeetheLicenseforthespecificlanguagegoverningpermissionsand
*limitationsundertheLicense.
*/
packageorg.springframework.cloud.logging;
importjava.util.Map;
importjava.util.Map.Entry;
importorg.apache.commons.logging.Log;
importorg.apache.commons.logging.LogFactory;
importorg.springframework.boot.bind.RelaxedPropertyResolver;
importorg.springframework.boot.logging.LogLevel;
importorg.springframework.boot.logging.LoggingSystem;
importorg.springframework.cloud.context.environment.EnvironmentChangeEvent;
importorg.springframework.context.ApplicationListener;
importorg.springframework.context.EnvironmentAware;
importorg.springframework.core.env.Environment;
/**
*Listenerthatlooksfor{@linkEnvironmentChangeEvent}andrebindsloggerlevelsifany
*changed.
*
*@authorDaveSyer
*
*/
publicclassLoggingRebinder
implementsApplicationListener,EnvironmentAware{
privatefinalLoglogger=LogFactory.getLog(getClass());
privateEnvironmentenvironment;
@Override
publicvoidsetEnvironment(Environmentenvironment){
this.environment=environment;
}
@Override
publicvoidonApplicationEvent(EnvironmentChangeEventevent){
if(this.environment==null){
return;
}
LoggingSystemsystem=LoggingSystem.get(LoggingSystem.class.getClassLoader());
setLogLevels(system,this.environment);
}
protectedvoidsetLogLevels(LoggingSystemsystem,Environmentenvironment){
Maplevels=newRelaxedPropertyResolver(environment)
.getSubProperties("logging.level.");
for(Entryentry:levels.entrySet()){
setLogLevel(system,environment,entry.getKey(),entry.getValue().toString());
}
}
privatevoidsetLogLevel(LoggingSystemsystem,Environmentenvironment,Stringname,
Stringlevel){
try{
if(name.equalsIgnoreCase("root")){
name=null;
}
level=environment.resolvePlaceholders(level);
system.setLogLevel(name,LogLevel.valueOf(level.toUpperCase()));
}
catch(RuntimeExceptionex){
this.logger.error("Cannotsetlevel:"+level+"for'"+name+"'");
}
}
}
该类也是监听了ChangeEnvrionmentEvent事件,用于重新绑定日志级别
二、刷新范围
我们考虑如下场景,当我们变更数据库配置后,通过refresh刷新,虽然能获取到最新的配置,可是我们的DataSource对象早就被初始化好了,换句话说即便配置刷新了我们拿到的依然是配置刷新前的对象。怎么解决这个问题呢?
我们继续看ContextRefresher的refresh方法,最后有一处代码值得我们关注一下this.scope.refreshAll(),此处scope对象是RefreshScope类型,那么这个类有什么作用呢?那么我们先要关注一下@RefreshScope注解。在这里我在贴出官网一段解释:
当配置更改时,标有@RefreshScope的Spring@Bean将得到特殊处理。这解决了状态bean在初始化时只注入配置的问题。例如,如果通过Environment更改数据库URL时DataSource有开放连接,那么我们可能希望这些连接的持有人能够完成他们正在做的工作。然后下一次有人从游泳池借用一个连接,他得到一个新的URL
刷新范围bean是在使用时初始化的懒惰代理(即当调用一个方法时),并且作用域作为初始值的缓存。要强制bean重新初始化下一个方法调用,您只需要使其缓存条目无效。RefreshScope是上下文中的一个bean,它有一个公共方法refreshAll()来清除目标缓存中的范围内的所有bean。还有一个refresh(String)方法可以按名称刷新单个bean。此功能在/refresh端点(通过HTTP或JMX)中公开。
这里我贴出@RefreshScope源码:
/*
*Copyright2013-2014theoriginalauthororauthors.
*
*LicensedundertheApacheLicense,Version2.0(the"License");
*youmaynotusethisfileexceptincompliancewiththeLicense.
*YoumayobtainacopyoftheLicenseat
*
*http://www.apache.org/licenses/LICENSE-2.0
*
*Unlessrequiredbyapplicablelaworagreedtoinwriting,software
*distributedundertheLicenseisdistributedonan"ASIS"BASIS,
*WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
*SeetheLicenseforthespecificlanguagegoverningpermissionsand
*limitationsundertheLicense.
*/
packageorg.springframework.cloud.context.config.annotation;
importjava.lang.annotation.Documented;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
importorg.springframework.context.annotation.Scope;
importorg.springframework.context.annotation.ScopedProxyMode;
/**
*Convenienceannotationtoputa@Bean
definitionin
*{@linkorg.springframework.cloud.context.scope.refresh.RefreshScoperefreshscope}.
*Beansannotatedthiswaycanberefreshedatruntimeandanycomponentsthatareusing
*themwillgetanewinstanceonthenextmethodcall,fullyinitializedandinjected
*withalldependencies.
*
*@authorDaveSyer
*
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public@interfaceRefreshScope{
/**
*@seeScope#proxyMode()
*/
ScopedProxyModeproxyMode()defaultScopedProxyMode.TARGET_CLASS;
}
在这个注解上我们关注一下此处标记了@Scope("refresh"),我们知道Spring的Bean属性有个叫scope的,它定义了bean的作用范围,常见的有singleon,prototype,session等。此处新定义了一个范围叫做refresh,在此我贴出RefreshScope的源代码来分析一下:
/*
*Copyright2002-2009theoriginalauthororauthors.
*
*LicensedundertheApacheLicense,Version2.0(the"License");youmaynotusethisfileexceptincompliancewith
*theLicense.YoumayobtainacopyoftheLicenseat
*
*http://www.apache.org/licenses/LICENSE-2.0
*
*Unlessrequiredbyapplicablelaworagreedtoinwriting,softwaredistributedundertheLicenseisdistributedon
*an"ASIS"BASIS,WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.SeetheLicenseforthe
*specificlanguagegoverningpermissionsandlimitationsundertheLicense.
*/
packageorg.springframework.cloud.context.scope.refresh;
importjava.io.Serializable;
importorg.springframework.beans.BeansException;
importorg.springframework.beans.factory.config.BeanDefinition;
importorg.springframework.beans.factory.support.BeanDefinitionRegistry;
importorg.springframework.cloud.context.scope.GenericScope;
importorg.springframework.context.ApplicationContext;
importorg.springframework.context.ApplicationContextAware;
importorg.springframework.context.event.ContextRefreshedEvent;
importorg.springframework.context.event.EventListener;
importorg.springframework.core.Ordered;
importorg.springframework.jmx.export.annotation.ManagedOperation;
importorg.springframework.jmx.export.annotation.ManagedResource;
/**
*
*AScopeimplementationthatallowsforbeanstoberefresheddynamicallyatruntime
*(see{@link#refresh(String)}and{@link#refreshAll()}).Ifabeanisrefreshedthen
*thenexttimethebeanisaccessed(i.e.amethodisexecuted)anewinstanceis
*created.Alllifecyclemethodsareappliedtothebeaninstances,soanydestruction
*callbacksthatwereregisteredinthebeanfactoryarecalledwhenitisrefreshed,and
*thentheinitializationcallbacksareinvokedasnormalwhenthenewinstanceis
*created.Anewbeaninstanceiscreatedfromtheoriginalbeandefinition,soany
*externalizedcontent(propertyplaceholdersorexpressionsinstringliterals)is
*re-evaluatedwhenitiscreated.
*
*
*
*Notethatallbeansinthisscopeareonlyinitializedwhenfirstaccessed,so
*thescopeforceslazyinitializationsemantics.Theimplementationinvolvescreatinga
*proxyforeverybeaninthescope,sothereisaflag
*{@link#setProxyTargetClass(boolean)proxyTargetClass}whichcontrolstheproxy
*creation,defaultingtoJDKdynamicproxiesandthereforeonlyexposingtheinterfaces
*implementedbyabean.Ifcallersneedaccesstoothermethodsthentheflagneedsto
*beset(andCGLibpresentontheclasspath).Becausethisscopeautomaticallyproxies
*allitsbeans,thereisnoneedtoadd<aop:auto-proxy/>
toanybean
*definitions.
*
*
*
*Thescopedproxyapproachadoptedherehasasidebenefitthatbeaninstancesare
*automatically{@linkSerializable},andcanbesentacrossthewireaslongasthe
*receiverhasanidenticalapplicationcontextontheotherside.Toensurethatthetwo
*contextsagreethattheyareidenticaltheyhavetohavethesameserializationid.One
*willbegeneratedautomaticallybydefaultfromthebeannames,sotwocontextswith
*thesamebeannamesarebydefaultabletoexchangebeansbyname.Ifyouneedto
*overridethedefaultidthenprovideanexplicit{@link#setId(String)id}whenthe
*Scopeisdeclared.
*
*
*@authorDaveSyer
*
*@since3.1
*
*/
@ManagedResource
publicclassRefreshScopeextendsGenericScope
implementsApplicationContextAware,Ordered{
privateApplicationContextcontext;
privateBeanDefinitionRegistryregistry;
privatebooleaneager=true;
privateintorder=Ordered.LOWEST_PRECEDENCE-100;
/**
*Createascopeinstanceandgiveitthedefaultname:"refresh".
*/
publicRefreshScope(){
super.setName("refresh");
}
@Override
publicintgetOrder(){
returnthis.order;
}
publicvoidsetOrder(intorder){
this.order=order;
}
/**
*Flagtodeterminewhetherallbeansinrefreshscopeshouldbeinstantiatedeagerly
*onstartup.Defaulttrue.
*
*@parameagertheflagtoset
*/
publicvoidsetEager(booleaneager){
this.eager=eager;
}
@Override
publicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistryregistry)
throwsBeansException{
this.registry=registry;
super.postProcessBeanDefinitionRegistry(registry);
}
@EventListener
publicvoidstart(ContextRefreshedEventevent){
if(event.getApplicationContext()==this.context&&this.eager
&&this.registry!=null){
eagerlyInitialize();
}
}
privatevoideagerlyInitialize(){
for(Stringname:this.context.getBeanDefinitionNames()){
BeanDefinitiondefinition=this.registry.getBeanDefinition(name);
if(this.getName().equals(definition.getScope())&&!definition.isLazyInit()){
Objectbean=this.context.getBean(name);
if(bean!=null){
bean.getClass();
}
}
}
}
@ManagedOperation(description="Disposeofthecurrentinstanceofbeannameprovidedandforcearefreshonnextmethodexecution.")
publicbooleanrefresh(Stringname){
if(!name.startsWith(SCOPED_TARGET_PREFIX)){
//Userwantstorefreshthebeanwiththisnamebutthatisn'ttheoneinthe
//cache...
name=SCOPED_TARGET_PREFIX+name;
}
//Ensurelifecycleisfinishedifbeanwasdisposable
if(super.destroy(name)){
this.context.publishEvent(newRefreshScopeRefreshedEvent(name));
returntrue;
}
returnfalse;
}
@ManagedOperation(description="Disposeofthecurrentinstanceofallbeansinthisscopeandforcearefreshonnextmethodexecution.")
publicvoidrefreshAll(){
super.destroy();
this.context.publishEvent(newRefreshScopeRefreshedEvent());
}
@Override
publicvoidsetApplicationContext(ApplicationContextcontext)throwsBeansException{
this.context=context;
}
}
该类继承了GenericScope:
/*
*Copyright2002-2009theoriginalauthororauthors.
*
*LicensedundertheApacheLicense,Version2.0(the"License");youmaynotusethisfileexceptincompliancewith
*theLicense.YoumayobtainacopyoftheLicenseat
*
*http://www.apache.org/licenses/LICENSE-2.0
*
*Unlessrequiredbyapplicablelaworagreedtoinwriting,softwaredistributedundertheLicenseisdistributedon
*an"ASIS"BASIS,WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.SeetheLicenseforthe
*specificlanguagegoverningpermissionsandlimitationsundertheLicense.
*/
packageorg.springframework.cloud.context.scope;
importjava.lang.reflect.Method;
importjava.util.ArrayList;
importjava.util.Arrays;
importjava.util.Collection;
importjava.util.Collections;
importjava.util.LinkedHashSet;
importjava.util.List;
importjava.util.Map;
importjava.util.UUID;
importjava.util.concurrent.ConcurrentHashMap;
importjava.util.concurrent.ConcurrentMap;
importjava.util.concurrent.locks.Lock;
importjava.util.concurrent.locks.ReadWriteLock;
importjava.util.concurrent.locks.ReentrantReadWriteLock;
importorg.aopalliance.intercept.MethodInterceptor;
importorg.aopalliance.intercept.MethodInvocation;
importorg.apache.commons.logging.Log;
importorg.apache.commons.logging.LogFactory;
importorg.springframework.aop.framework.Advised;
importorg.springframework.aop.scope.ScopedObject;
importorg.springframework.aop.scope.ScopedProxyFactoryBean;
importorg.springframework.aop.support.AopUtils;
importorg.springframework.beans.BeansException;
importorg.springframework.beans.factory.BeanFactory;
importorg.springframework.beans.factory.DisposableBean;
importorg.springframework.beans.factory.ObjectFactory;
importorg.springframework.beans.factory.config.BeanDefinition;
importorg.springframework.beans.factory.config.BeanFactoryPostProcessor;
importorg.springframework.beans.factory.config.ConfigurableListableBeanFactory;
importorg.springframework.beans.factory.config.Scope;
importorg.springframework.beans.factory.support.BeanDefinitionRegistry;
importorg.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
importorg.springframework.beans.factory.support.DefaultListableBeanFactory;
importorg.springframework.beans.factory.support.RootBeanDefinition;
importorg.springframework.expression.Expression;
importorg.springframework.expression.ExpressionParser;
importorg.springframework.expression.ParseException;
importorg.springframework.expression.spel.standard.SpelExpressionParser;
importorg.springframework.expression.spel.support.StandardEvaluationContext;
importorg.springframework.util.ReflectionUtils;
importorg.springframework.util.StringUtils;
/**
*
*AgenericScopeimplementation.
*
*
*@authorDaveSyer
*
*@since3.1
*
*/
publicclassGenericScopeimplementsScope,BeanFactoryPostProcessor,
BeanDefinitionRegistryPostProcessor,DisposableBean{
privatestaticfinalLoglogger=LogFactory.getLog(GenericScope.class);
publicstaticfinalStringSCOPED_TARGET_PREFIX="scopedTarget.";
privateBeanLifecycleWrapperCachecache=newBeanLifecycleWrapperCache(
newStandardScopeCache());
privateStringname="generic";
privateConfigurableListableBeanFactorybeanFactory;
privateStandardEvaluationContextevaluationContext;
privateStringid;
privateMaperrors=newConcurrentHashMap<>();
privateConcurrentMaplocks=newConcurrentHashMap<>();
/**
*Manualoverridefortheserializationidthatwillbeusedtoidentifythebean
*factory.Thedefaultisauniquekeybasedonthebeannamesinthebeanfactory.
*
*@paramidtheidtoset
*/
publicvoidsetId(Stringid){
this.id=id;
}
/**
*Thenameofthisscope.Default"generic".
*
*@paramnamethenamevaluetoset
*/
publicvoidsetName(Stringname){
this.name=name;
}
/**
*Thecacheimplementationtouseforbeaninstancesinthisscope.
*
*@paramcachethecachetouse
*/
publicvoidsetScopeCache(ScopeCachecache){
this.cache=newBeanLifecycleWrapperCache(cache);
}
/**
*Amapofbeannametoerrorswheninstantiatingthebean.
*
*@returntheerrorsaccumulatedsincethelatestdestroy
*/
publicMapgetErrors(){
returnthis.errors;
}
@Override
publicvoiddestroy(){
Listerrors=newArrayList();
Collectionwrappers=this.cache.clear();
for(BeanLifecycleWrapperwrapper:wrappers){
try{
Locklock=locks.get(wrapper.getName()).writeLock();
lock.lock();
try{
wrapper.destroy();
}
finally{
lock.unlock();
}
}
catch(RuntimeExceptione){
errors.add(e);
}
}
if(!errors.isEmpty()){
throwwrapIfNecessary(errors.get(0));
}
this.errors.clear();
}
/**
*Destroythenamedbean(i.e.flushitfromthecachebydefault).
*
*@paramnamethebeannametoflush
*@returntrueifthebeanwasalreadycached,falseotherwise
*/
protectedbooleandestroy(Stringname){
BeanLifecycleWrapperwrapper=this.cache.remove(name);
if(wrapper!=null){
Locklock=locks.get(wrapper.getName()).writeLock();
lock.lock();
try{
wrapper.destroy();
}
finally{
lock.unlock();
}
this.errors.remove(name);
returntrue;
}
returnfalse;
}
@Override
publicObjectget(Stringname,ObjectFactory>objectFactory){
BeanLifecycleWrappervalue=this.cache.put(name,
newBeanLifecycleWrapper(name,objectFactory));
locks.putIfAbsent(name,newReentrantReadWriteLock());
try{
returnvalue.getBean();
}
catch(RuntimeExceptione){
this.errors.put(name,e);
throwe;
}
}
@Override
publicStringgetConversationId(){
returnthis.name;
}
@Override
publicvoidregisterDestructionCallback(Stringname,Runnablecallback){
BeanLifecycleWrappervalue=this.cache.get(name);
if(value==null){
return;
}
value.setDestroyCallback(callback);
}
@Override
publicObjectremove(Stringname){
BeanLifecycleWrappervalue=this.cache.remove(name);
if(value==null){
returnnull;
}
//Someonemighthaveaddedanotherobjectwiththesamekey,butwe
//keepthemethodcontractbyremovingthe
//valuewefoundanyway
returnvalue.getBean();
}
@Override
publicObjectresolveContextualObject(Stringkey){
Expressionexpression=parseExpression(key);
returnexpression.getValue(this.evaluationContext,this.beanFactory);
}
privateExpressionparseExpression(Stringinput){
if(StringUtils.hasText(input)){
ExpressionParserparser=newSpelExpressionParser();
try{
returnparser.parseExpression(input);
}
catch(ParseExceptione){
thrownewIllegalArgumentException("Cannotparseexpression:"+input,
e);
}
}
else{
returnnull;
}
}
@Override
publicvoidpostProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory)
throwsBeansException{
this.beanFactory=beanFactory;
beanFactory.registerScope(this.name,this);
setSerializationId(beanFactory);
}
@Override
publicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistryregistry)
throwsBeansException{
for(Stringname:registry.getBeanDefinitionNames()){
BeanDefinitiondefinition=registry.getBeanDefinition(name);
if(definitioninstanceofRootBeanDefinition){
RootBeanDefinitionroot=(RootBeanDefinition)definition;
if(root.getDecoratedDefinition()!=null&&root.hasBeanClass()
&&root.getBeanClass()==ScopedProxyFactoryBean.class){
if(getName().equals(root.getDecoratedDefinition(