SpringCloudでは、構成が変更されたときに、http:// xxxx/refreshにアクセスしてサービスを開始せずに最新の構成を取得できることがわかっています。では、どうやってそれをするのでしょうか?データベースの構成を変更して更新した後、最新のデータソースオブジェクトを取得するにはどうすればよいですか? SpringCloudがどのように行うか見てみましょう。
1。環境の変化
1.1。 contextrefresherについて
アクセス /更新すると、ReshendPointクラスによって処理されます。ソースコードを見てみましょう:
/ * * Copyright 2013-2014元の著者または著者。 * * Apacheライセンス、バージョン2.0(「ライセンス」)に基づいてライセンスされています。 *ライセンスに準拠している場合を除き、このファイルを使用することはできません。 *ライセンスのコピーを取得することができます * * http://www.apache.org/licenses/license-2.0 * *該当する法律で要求されていない場合、または書面で合意しない限り、ライセンスに基づいて配布されるソフトウェア *は、保証または条件なしで「現状」に分配されます。 *権限を管理する特定の言語のライセンスと、ライセンスに基づく制限を参照してください。 */package org.springframework.cloud.endpoint; Import java.util.arrays; Import java.util.collection; import java.util.set; Import org.springframework.boot.actuate.endpoint.abstractendpoint; import org.springframework.boot.context.context.context.properties.configuterties org.springframework.cloud.context.refresh.contextrefresher; Import org.springframework.jmx.export.annotation.managePramework.jmx.export.annotation.ManageResource;/** @author daveer * @authorhha(@authorhha(@authorhha) = "endpoints.refresh"、Ingroreunknownfields = false)@managedresourcepublic class refleshendpoint extends abstractendpoint <collection <string >> {private contextrefresher contextrefresher; public RefreshendPoint(contextrefresher contextrefresher){super( "refresh"); this.contextrefresher = contextrefresher; } @managedoperation public string [] refresh(){set <string> keys = contextrefresher.refresh(); return keys.toarray(new String [keys.size()]); } @override public collection <string> invoke(){return arrays.aslist(refresh()); }}ソースコードを使用して、更新エンドポイントにアクセスすると、ContexTrefresherの更新方法が実際に実行されることを学びました。次に、ソースコードを追跡し続け、その更新方法を見つけ続けます。
public synchronized set <string> refresh(){map <string、object> before =抽出(this.context.getEnvironment()。getPropertySources()); addconfigfileStoenvironment(); <string> keys = changes(extract(this.context.getEnvironment()。getPropertySources()))。keyset(); this.context.publishevent(new EnvironmentChangeEvent(Context、Keys)); this.scope.refreshall();キーを返します。 }更新方法が次のことを行うことがわかります。
1)更新する前にすべてのPropertySourcesを取得します
2)最新の構成を取得するには、addconfigfilestoenvironmentメソッドを呼び出します
3)変更方法を呼び出して、構成情報を更新する
4)EnvironmentChangeEnventイベントを公開します
5)refreshscopeのrefreshallメソッドを呼び出して範囲を更新します
2、3、および4つのステップに焦点を当てましょう
1.2。 addconfigfileStoenvironmentメソッド
まず、この方法がどのように実装されているかを見てみましょう。
/ *テスト用 */ configureableapplicationContext addconfigfilestoenvironment(){configureableapplicationContext capture = null; try {StandardEnvironment Environment = copyEnvironment(this.context.getEnvironment()); SpringApplicationBuilder Builder = new SpringApplicationBuilder(empty.class).bannermode(mode.off).web(false).environment(環境); //環境に影響を与えるリスナーのみ(例:副作用があるためロギング//リスナーを除く)Builder.Application().SetListeners(arrays.Aslist(new BootstrapApplicationListener()、new configFileApplicationListener()); Capture = builder.run(); if(Environment.getPropertySources()。 } MutablePropertySources Target = this.context.getEnvironment().getPropertySources();文字列ターゲット名= null; for(propertySource <?> source:Environment.getPropertySources()){string name = 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){stargen.addafter(targetname、source); } else {// TargetNameはnullだったので、リストターゲットの開始時にいます。addfirst(source); TargetName = name; }}}}}} fullly {configureableapplicationContext closeable = capture; while(closeable!= null){try {closeable.close(); } catch(例外e){//無視; } if(closeable.getParent()instance of ConfigureAbleapplicationContext){closeable =(configureableapplicationContext)closeable.getParent(); } else {break; }}} return Capture; }1)この方法は、最初に現在の環境をコピーします
2)SpringApplicationBuilderを介して簡単なスプリングブートスタートアッププログラムを構築して開始します
builder.application()。setListeners(arrays.aslist(new bootstrapapplicationListener()、new configfileapplicationlistener()));
ここに2人のリスナーが追加されます:bootstrapapplicationListenerとconfigFileAppLicationListener。以前の学習を通じて、BootstrapApplicationListenerがBootstrapプログラムのコアリスナーであり、configFileApplicationListenerも非常に重要なクラスであることがわかります。
/ * *著作権2012-2017元の著者または著者。 * * Apacheライセンス、バージョン2.0(「ライセンス」)に基づいてライセンスされています。 *ライセンスに準拠している場合を除き、このファイルを使用することはできません。 *ライセンスのコピーを取得することができます * * http://www.apache.org/licenses/license-2.0 * *該当する法律で要求されていない場合、または書面で合意しない限り、ライセンスに基づいて配布されるソフトウェア *は、保証または条件なしで「現状」に分配されます。 *権限を管理する特定の言語のライセンスと、ライセンスに基づく制限を参照してください。 */package org.springframework.boot.context.config; Import java.io.ioexception; Import java.util.arraylist; Import java.util.arrays; Import java.util.collection; import java.util.celections; import java.util.itil.util.util.util.util.util.util.util.util.util.util.util.util.itil. java.util.linkedlist; Import java.util.list; import java.util.queue; import java.util.set; import org.apache.commons.logging.log; import org.springframework.beans.beansexcection; Import org.springframework.springframework.beans.cechedintrecults; org.springframework.beans.config.beanfactorypostprocessor; Import org.springframework.beans.factory.config.configurablelistablebeanfactory; Import org.springframework.boot.springApplication; Import org.springframework.botturt.buttusterf.bintuctusted.buttuctusted.buttusterfturtusted.buttusted.buttusterf.bintuctusted.buttusterf.buttusterf.buttusterf.buttuste org.springframework.boot.bind.propertysourcespropertyvalues; Import org.springframework.boot.bind.relaxeddatabinder; Import org.springframework.boot.bind.RelaxedPropertyResolver; Import org.springframework.boot.context.event.applicationenvironmentpreparedevent; Import org.springframework.boot.context.event.applicationpreparedevent; Import org.springframework.boot.env.enumerablecompositeportysource; Import; org.springframework.env.environmentpostprocessor; Import org.springframework.boot.env.propertysourcesloader; Import org.springframework.boot.logging.logging.deferredlog; Import org.springframework.context.context.plicentext.ebentiontext org.springframework.context.ConfigurableApplicationContext;import org.springframework.context.annotation.ConfigurationClassPostProcessor;import org.springframework.context.event.SmartApplicationListener;import org.springframework.core.Ordered;import springframework.core.annotation.annotationAwareOrderComparator; Import org.springframework.core.convert.conversionservice; import org.springframework.core.convert.support.defaultconversionservice; springframework.env.enumerablepropertysource; Import org.springframework.core.env.mutablepropertysources; Import org.springframework.core.env.propertysources; Import org.springframework.core.core.core.core.core.core.core.core.core.core.core.core.core.core.core.core.core.core.core.core.core.core.core.core.core; org.springframework.core.io.defaultresourceloader; Import org.springframework.core.io.resourceloader; Import org.springframework.core.io.support.springfactoriesloader; import org.springframework.util.util.spridfr.surframework org.springframework.util.stringutils; import org.springframework.validation.bindexception;/** * {@link環境postprocessor}は、よく知られているファイルの場所からプロパティをロードしてコンテキスト環境を構成します。デフォルトでは、次の場所の * 'Application.Properties'および/または 'application.yml'ファイルから * 'Application.Properties'および/または 'application:</li> * <li>ファイル:./ </li> * <li> ClassPath:config/</li> * <li>ファイル:./ * {@link #setsearchlocations(string)}および{@link #setearchnames(string)}を使用して指定。 * <p> *追加ファイルは、アクティブプロファイルに基づいてロードされます。たとえば、「Web」 *プロファイルがアクティブである場合、Application-Web.Properties」および「Application-Web.yml」が考慮されます。 * <p> * 'spring.config.name'プロパティを使用して、ロードする代替名を指定できます *および 'spring.config.location'プロパティを使用して、代替検索 *の場所または特定のファイルを指定できます。 * <p> *構成プロパティは{@link springApplication}にもバインドされています。これにより、ソース *( "Spring.main.Sources" -CSVリスト)のように、{@link springApplication}プロパティを動的に設定することができます。 * * @Author Dave Syer * @Author Phillip Webb * @Author Stephane Nicoll * @Author Andy Wilkinson * @authoreddúmeléndez */public class configfileapplicationlistenerは環境ポストプロセッサ、SmartApplicationListener、Ordered {default_properties "; //注文は最小から最も特定の(最後の1つの勝利)プライベート静的な最終文字列default_search_locations = "classpath:/、classpath:/、config/、file:./、file:./ config/";プライベート静的最終文字列default_names = "application"; /***「アクティブプロファイル」プロパティ名。 */ public static final string active_profiles_property = "spring.profiles.active"; /***「プロファイルを含む」プロパティ名。 */ public static final string include_profiles_property = "spring.profiles.include"; /***「構成名」プロパティ名。 */ public static final string config_name_property = "spring.config.name"; /***「構成場所」プロパティ名。 */ public static final string config_location_property = "spring.config.location"; /***プロセッサのデフォルトの順序。 */ public static final int default_order = ordered.heist_precedence + 10; /***アプリケーション構成の名前{@Link PropertySource}。 */ public static final string application_configuration_property_source_name = "ApplicationConfigurationProperties";プライベート最終deferredLog logger = new DeferredLog();プライベート文字列SearchLocations;プライベート文字列名; private int order = default_order; Private final ConversionService conversionService = new DefaultConversionsionservice(); @Override public boolean supportseventType(class <?extends applicationEvent> eventType){return applicationEnvironmentPreparedEvent.class.isassignable(eventType)|| ApplicationPreparedEvent.class.isassignableFrom(eventType); } @Override public boolean supportssourcetype(class <?> aclass){return true; } @Override public void onApplicationEvent(applicationEvent event){if(applicationEnvironmentPreparedEvent){onApplicationEnvironmentPrepareDevent((ApproachEnvironment -PreparedEvent)イベント); } if(event instanceof applicationPreparedEvent){onapplicationPreparedEvent(event); }} private void onapplicationEnvironmentPreparedEvent(applicationEnvironment -PreparedEvent Event){list <environmentPostProcessor> postprocessors = loadPostProcessors(); postprocessors.add(this); AnnotationAwareOrderComparator.Sort(PostProcessors); for(EnvironmentPostProcessor PostProcessor:PostProcessors){postprocessor.postprocessenvironment(event.getEnvironment()、event.getSpringApplication()); }} list <EnvironmentPostProcessor> loadPostProcessors(){return SpringFactoriesLoader.LoadFactories(EnvironmentPostProcessor.class、getClass()。getClassLoader()); } @Override public void postprocessenvironment(configureableenvironment環境、springApplication Application){addPropertySources(環境、Application.getResourcelOader()); configingorebeaninfo(環境); bindtospringApplication(環境、アプリケーション); } private void configureignorebeaninfo(configureableenvironment環境){if(system.getProperty(cachedintrospectionResults.ignore_beaninfo_property_name)== null){raxedPropertyResolver Resolver = new raxedportyResolver(envery ention ";") boolean agnore = sloldver.getProperty( "Ignore"、boolean.class、boolean.true); System.setProperty(cachedintrospectionResults.ignore_beaninfo_property_name、nagrore.toString()); }} private void onapplicationpreparedevent(applicationEvent event){this.logger.replayto(configfileapplicationlistener.class); AddPostProcessors(((ApproachPreadeEvent)Event).GetApplicationContext()); } /***指定された環境に構成ファイルプロパティソースを追加します。 * @param環境 * @param ResourceoAder The Resource Loader * @see #AddPostProcessors(configureableAbleapplicationContext) */ Protected Void AddPropertySours(ConfigureableEnvironment環境、ReasonCeurceloAder resurceoader){randomvalueportysource.addtonvironment(環境); new Loader(環境、ResourceLoader).load(); } /***環境を{@link springApplication}にバインドします。 * @param環境バインドする環境 * @paramアプリケーション */保護されたvindtospringapplication(configureableenvironment環境、springApplicationアプリケーション){propertiesConfigurationFactory = binder = new PropertiesConfigurationFactory <SpringApplication>(アプリケーション); binder.settargetname( "spring.main"); binder.setConversionservice(this.conversionservice); binder.setPropertySources(Environment.getPropertySources()); try {binder.bindpropertiestotarget(); } catch(bindexception ex){新しいIllegalStateException( "SpringApplicationに結合できない"、ex); }} /***適切なポストプロセッサを追加して、プロパティソースを構成します。 * @paramコンテキスト構成 */保護されたvoid addpostprocessors(configureableapplicationContext context){context.addbeanfactorypostprocessor(new propertySourceOrderingPostProcessor(context)); } public void setorder(int order){this.order = order; } @Override public int getOrder(){return this.order; } /***コンマ分離リストと見なされる検索場所を設定します。各 *検索場所は、ディレクトリパス(「/」で終了)である必要があり、{@link #setearchNames(string)search名}と *プロファイル(存在する場合)と *プロパティローダーでサポートされているファイル拡張機能で構築されたファイル名がプレフィックスされます。 *場所は指定された順序で考慮され、後のアイテムが優先されます *(マップマージのように)。 * @param locations検索場所 */ public void setSearchLocations(string locations){assert.haslength(locations、 "場所は空にしてはならない"); this.searchlocations = locations; } /** *は、[ファイル拡張子を除く)ロードする必要のあるファイルの名前を * comma分離リストとして設定します。 * @paramの名前はロードする名前に名前 */ public void setSearchNames(string names){assert.haslength(names、 "names be vate"); this.names = names; } /** * {@link beanfactorypostprocessor} {@link configurationClassPostProcessor}によって追加された * {@code @propertySource}アイテムの下にプロパティソースを再注文します。 */ private class propertySourceOrderPostProcessorを実装するbeanfactorypostprocessor、sordered {private configurableapplicationContext Context; propertySourceOrderingPostProcessor(configureableapplicationContext Context){this.context = context; } @Override public int getOrder(){return ordered.highed_precedence; } @Override public void postprocessbeanfactory(configureablElistablebeanFactory BeanFactory)BeanSexception {reorderSources(this.context.getEnvironment()); } private void reorderSources(configureableenvironment環境){configurationPropertySources .finishandReLocate(Environment.getPropertySources()); PropertySource <?> defaultPropertySources = Environment.getPropertySources().Remove(default_properties); if(defaultProperties!= null){Environment.getPropertySources()。addLast(defaultProperties); }}} /***候補のプロパティソースをロードし、アクティブプロファイルを構成します。 */プライベートクラスローダー{プライベートファイナルログロガー= configfileapplicationlistener.this.logger;プライベート最終構成環境環境。プライベートファイナルリソースリソースResourceLeceloAder; Private PropertySourcesLoader PropertiesLoader;プライベートキュー<profile>プロファイル;プライベートリスト<profile> ProcessedProfiles;プライベートブールアクティブ化プロファイル。 Loader(ConfigureableEnvironment Environment、ResourceloAder resourceLeceloader){this.environment =環境; this.resourceloader = resourceloader == null?新しいDefaultroSourceloAder():ResourceLoader; } public void load(){this.propertiesloader = new PropertySourcesLoader(); this.activatedprofiles = false; this.profiles = collections.aslifoqueue(new linkedlist <profile>()); this.processedProfiles = new LinkedList <profile>(); //環境を介して設定された既存のアクティブプロファイル。SetActiveProfiles()//は追加のプロファイルであり、構成ファイルは//必要な場合に追加することができます。 set <profile> initialactiveProfiles = initializeactiveProfiles(); this.profiles.addall(getunprocessedactiveprofiles(initialactiveprofiles)); if(this.profiles.isempty()){for(string defaultprofilename:this.environment.getDefaultProfiles()){profile defaultProfile = new Profile(defaultProfileName、true); if(!this.profiles.contains(defaultprofile)){this.profiles.add(defaultProfile); }}} //これらの目的のデフォルトプロファイルは、nullとして表されます。最後に//最後にキューから外れているように追加します(アクティブプロファイルは//リストが後で逆になったときにデフォルトの設定をオーバーライドします)。 this.profiles.add(null); while(!this.profiles.isempty()){プロファイルプロファイル= this.profiles.poll(); for(string location:getSearchLocations()){if(!location.endswith( "/")){//場所は既にファイル名です。 } else {for(string name:getsearchNames()){load(location、name、forporie); }}} this.processedprofiles.add(expre); } addConfigurationProperties(this.propertiesloader.getPropertySources()); } private set <profile> initializeactiveProfiles(){if(!this.environment.containsproperty(active_profiles_property)&&!this.environment.containsproperty(include_profiles_property)){return collections.emptyset(); } //プロパティソースを介して設定された既存のアクティブプロファイル(システム//プロパティなど)は、構成ファイルに追加されたものよりも優先されます。 springprofiles springprofiles = bindspringprofiles(this.environment.getPropertySources()); set <profile> ActiveProfiles = new LinkedHashset <profile>(springProfiles.getActiveProfiles()); ActiveProfiles.Addall(SpringProfiles.getIncludeProfiles()); MayBeactivateProfiles(ActiveProfiles); ActiveProfilesを返します。 } /***まだ処理されていないアクティブプロファイルを返します。プロファイルが{@link #active_profiles_property}と * {@link configureableenvironment#addactiveProfile(string)}の両方を介して *有効になっている場合、{@link #active_profiles_property}値が先行するように *フィルタリングする必要があります。 * <p> *具体的には、「クラウド」プロファイルが環境を介して有効になっている場合、{@link #active_profiles_property}を介して設定されたプロファイルがあまり優先されません。 * @param initialActiveProfiles * {@link #active_profiles_property}を介して有効になったプロファイル * @ @環境から未処理のアクティブプロファイルを有効にする */プライベートリスト<profile> getunProfies(set <<profile> initialactiveprofies = <profirist = unprefirsive> unprocessedactiveprof> unprofishprofis for(string profileName:this.environment.getActiveProfiles()){profile Profile = new Profile(profileName); if(!intialityactiveProfiles.contains(profile)){unprocessedactiveProfiles.add(profile); }} //それらを逆にするため、注文はgetProfilesforValue()//(最後の1つが最終的に解決されると勝ちます)collections.Reverse(UnprocessedactiveProfiles)から同じになります。 unprocessedactiveprofilesを返します。 } private void load(string location、string name、profile profile){string group = "profile =" +(profile == null? "":profile); if(!stringutils.hastext(name)){// loadintogroup(group、location、profile)の場所から直接ロードしてみてください。 } else {//指定された名前のファイルを検索します(string ext:this.propertiesloader.getallfileextensions()){if(profile!= null){//プロファイル固有のファイルloadintogroup(グループ、場所 + name + " - " +プロファイル + "。" + ext、null); for(プロファイルProcessedProfile:this.processedProfiles){if(processedProfile!= null){loadintogroup(group、location + name + " - " + processedProfile + "。" + ext、profile); }} //時々、人々は「spring.profiles:dev」を// application-dev.yml(gh-340)に入れます。間違いなく、それを試してエラーを試してみる必要がありますが、とにかく親切でロードすることができます。 loadIntogroup(グループ、場所 + name + " - " +プロファイル + "。" + ext、profile); } //また、通常のファイルloadintogroup(グループ、場所 + name + "。" + ext、profile)のプロファイル固有のセクション(もしあれば)を試してください。 }}} private propertySource <?> loadIntogroup(string識別子、文字列位置、プロファイルプロファイル){try {return doloadintogroup(識別子、場所、プロファイル); } catch(Exception ex){新しいIllegalStateExceptionをスロー( "" + location + "'"、exからプロパティソースのロードに失敗しました); }} private propertySource <? propertySource <?> propertySource = null; stringbuilder msg = new StringBuilder(); if(resource!= null && resource.exists()){string name = "applicationConfig:[" + location + "]"; string group = "applicationconfig:[" +識別子 + "]"; propertySource = this.propertiesloader.load(resource、group、name、(profile == null?null:profile.getname())); if(propertySource!= null){msg.append( "loaded"); handleprofileproperties(propertySource); } else {msg.append( "skippip(empty)"); }} else {msg.append( "スキップ"); } msg.append( "config file"); msg.append(getResourcedescription(場所、リソース)); if(profile!= null){msg.append( "for profile").append(profile); } if(resource == null ||!resource.exists()){msg.append( "resource not not ind"); this.logger.trace(msg); } else {this.logger.debug(msg); } propertySourceを返します。 } private string getResourcedEscription(String location、Resource Resource){String Resourcedescription = "'" + location + "'"; if(resource!= null){try {resourcedescription = string.format( "'%s'(s)"、resource.geturi()。toasciistring()、location); } catch(ioException ex){//場所を説明として使用}} reasuredescriptionを返します。 } private void handleProfileProperties(propertySource <?> propertySource){SpringProfiles springProfiles = bindSpringProfiles(PropertySource); MayBeactivateProfiles(springprofiles.getActiveProfiles()); addprofiles(springprofiles.getincludeprofiles()); } private springprofiles bindspringprofiles(propertySource <?> propertySource){MutablePropertySources PropertySources = new MutablePropertySources(); propertySources.addfirst(propertySource); BindSpringProfiles(PropertySources)を返します。 } private springprofiles bindSpringProfiles(propertySources propertySources){SpringProfiles springProfiles = new SpringProfiles(); rackeddatabinder databinder = new rackeddatabinder(springprofiles、 "spring.profiles"); databinder.bind(new propertySourcesPropertyValues(propertySources、false)); springprofiles.setactive(ResolvePlaceHolders(springprofiles.getActive())); springprofiles.setInclude(ResolvePlaceHolders(springprofiles.getInclude())); SpringProfilesを返します。 }プライベートリスト<String> ResolvePlaceHolders(List <String>値){List <String> Resolved = new ArrayList <String>(); for(string value:values){resolved.add(this.environment.ResolvePlaceHolders(value)); } return Resolved; } private void maybeactivateprofiles(set <profile> profiles){if(this.activateProfiles){if(!profiles.isempty()){this.logger.debug( "既にアクティブ化されている '" +プロファイル + "'は適用されません"); } 戻る; } if(!profiles.isempty()){addprofiles(profiles); this.logger.debug( "activated profiles" + stringutils.collectiontocommadelimitedString(プロファイル)); this.activatedprofiles = true; removeUnProcessedDefaultProfiles(); }} private void removeunProcessedDefaultProfiles(){for(iterator <profile> iterator = this.profiles.iteratまたは(); iterator .hasnext();){if(iterator.next()。isdefaultProfile()){iterator.Remove(); }}} private void addprofiles(set <profile> profiles){for(profile profile:profiles){this.profiles.add(profile); if(!EnvironmentHasactiveProfile(profile.getName())){//既に受け入れられている場合、注文が設定されていると仮定します//意図的にprependprofile(this.environment、profile); }}}} private boolean EnvironmenthasactiveProfile(string profile){for(string ActiveProfile:this.environment.getActiveProfiles()){if(ActiveProfile.equals(profile)){return true; }} falseを返します。 } private void prependprofile(configureableenvironment環境、プロファイルプロファイル){set <string> profiles = new linkedhashset <string>(); Environment.GetActiveProfiles(); //それらが初期化されていることを確認します//これは最初に移動する必要があります(プロパティキークラッシュで最後の勝利)profiles.add(profile.getName()); profiles.addall(arrays.aslist(Environment.getActiveProfiles())); Environment.SetActiveProfiles(profiles.toarray(new String [profiles.size()])); } private set <string> getSearchLocations(){set <string> locations = new LinkedHashset <String>(); //ユーザーコンフィグ付き設定が優先されるので、最初に(this.environment.containsproperty(config_location_property)){for(string path:asrosolvedset(this.environment.getProperty(config_location_property)、null)){( stringutils.cleanpath(パス); if(!resourceutils.isurl(path)){path = resourceutils.file_url_prefix + path; }} locations.add(path); }} locations.addall(asResolvedset(configfileapplicationlistener.this.searchlocations、default_search_locations));戻り場所。 } private set <string> getSearchNames(){if(this.environment.containsproperty(config_name_property)){return asrosolvedset(this.environment.getProperty(config_name_property)、null); } return asResolvedset(configfileapplicationlistener.this.names、default_names); } private set <string> asResolvedset(string value、string fallback){list <string> list = arrays.aslist(stringutils.trimarrayelements(stringutils.commadelimitedListtoStringArray(value!= null?this.environment.ResolveplaceHolders(value)); collections.reverse(list);新しいlinkedhashset <string>(list)を返します。 } private void addConfigurationProperties(MutablePropertySources Sources){List <PropertySource <? for(propertySource <?> item:sources){ReorderedSources.add(item); } addConfigurationProperties(new ConfigurationPropertySources(ReorderedSources);} private void addConfigurationProperties(ConfigurationPropertySources ConfigurationSources){MutablePropertySources expingsources = this.Environment .GetPropertySours();存在するnull "); this.name = name; this.defaultProfile = defaultProfile;} public string getName(){return this.name;} public boolean isdefaultProfile(){return this.defaultProfile;} @Override public String toString(){return int int inthashcode() this.name.hashcode();ロードされたプロパティソース}は、構成クラスが処理された後、列挙されます。 super(application_configuration_property_source_name、sources = source> new arraylist <?); SOURCE) null;} public void finantsurcate(sultable_configuration_property_source_name =(configurationpropertysources)。 {if(enumerablecompositeportysourceのInstance){enumerablecompositepropertySource composite =(enumerablecompositepropertysource)propertySource; Nest.getName();プロパティ *プライベートリスト= new Arraylist <string>; void setinclude(<string> include) arraylist <profile>(string profileNames){new Profile(profileName))根据javadoc注释的说明,这个类会从指定的位置加载application.properties或者application.yml并将它们的属性读到Envrionment当中,其中这几个方法大家关注下:
@Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } }当springboot程序启动时一定会触发该事件监听,如果当前是ApplicationEnvironmentPreparedEvent事件就会调用onApplicationEnvironmentPreparedEvent方法,最终该方法会执行:
@Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader()); configureIgnoreBeanInfo(environment); bindToSpringApplication(environment, application); }其中bindToSpringApplication方法为:
/** * Bind the environment to the {@link SpringApplication}. * @param environment the environment to bind * @param application the application to bind to */ protected void bindToSpringApplication(ConfigurableEnvironment environment, SpringApplication application) { PropertiesConfigurationFactory<SpringApplication> binder = new PropertiesConfigurationFactory<SpringApplication>( application); binder.setTargetName("spring.main"); binder.setConversionService(this.conversionService); binder.setPropertySources(environment.getPropertySources()); try { binder.bindPropertiesToTarget(); } catch (BindException ex) { throw new IllegalStateException("Cannot bind to SpringApplication", ex); } }很明显该方法是将Environment绑定到对应SpringApplication上,通过这个类就可以获取到我们更改过后的配置了
1.3、changes方法
private Map<String, Object> changes(Map<String, Object> before, Map<String, Object> after) { Map<String, Object> result = new HashMap<String, Object>(); for (String key : before.keySet()) { if (!after.containsKey(key)) { result.put(key, null); } else if (!equal(before.get(key), after.get(key))) { result.put(key, after.get(key)); } } for (String key : after.keySet()) { if (!before.containsKey(key)) { result.put(key, after.get(key)); } } return result; }changes方法其实就是处理配置变更信息的,分以下几种情况:
1)如果刷新过后配置文件新增配置就添加到Map里
2) 如果有配置变更就添加变更后的配置
3) 如果删除了原先的配置,就把原先的key对应的值设置为null
至此经过changes方法后,上下文环境已经拥有最新的配置了。
1.4、发布事件
当上述步骤都执行完毕后,紧接着会发布EnvrionmentChangeEvent事件,可是这个事件谁来监听呢?在这里我贴出官网的一段描述:
应用程序将收听EnvironmentChangeEvent,并以几种标准方式进行更改(用户可以以常规方式添加ApplicationListeners附加ApplicationListeners)。当观察到EnvironmentChangeEvent时,它将有一个已更改的键值列表,应用程序将使用以下内容:
1.重新绑定上下文中的任何@ConfigurationProperties bean
2.为logging.level.*中的任何属性设置记录器级别
根据官网描述我们知道将变更一下操作行为@ConfigurationProperties的bean与更改日志level,那么如何做到的呢?结合官网文档我们来关注以下两个类:
ConfigurationPropertiesRebinder:
/* * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.cloud.context.properties;import java.util.HashSet;import java.util.Map;import java.util.Set;import java.util.concurrent.ConcurrentHashMap;import org.springframework.aop.framework.Advised;import org.springframework.aop.support.AopUtils;import org.springframework.beans.BeansException;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.cloud.context.config.annotation.RefreshScope;import org.springframework.cloud.context.environment.EnvironmentChangeEvent;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.context.ApplicationListener;import org.springframework.core.env.Environment;import org.springframework.jmx.export.annotation.ManagedAttribute;import org.springframework.jmx.export.annotation.ManagedOperation;import org.springframework.jmx.export.annotation.ManagedResource;import org.springframework.stereotype.Component;/** * Listens for {@link EnvironmentChangeEvent} and rebinds beans that were bound to the * {@link Environment} using {@link ConfigurationProperties * <code>@ConfigurationProperties</code>}. When these beans are re-bound and * re-initialized the changes are available immediately to any component that is using the * <code>@ConfigurationProperties</code> bean. * * @see RefreshScope for a deeper and optionally more focused refresh of bean components * * @author Dave Syer * */@Component@ManagedResourcepublic class ConfigurationPropertiesRebinder implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> { private ConfigurationPropertiesBeans beans; private ApplicationContext applicationContext; private Map<String, Exception> errors = new ConcurrentHashMap<>(); public ConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) { this.beans = beans; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * A map of bean name to errors when instantiating the bean. * * @return the errors accumulated since the latest destroy */ public Map<String, Exception> getErrors() { return this.errors; } @ManagedOperation public void rebind() { this.errors.clear(); for (String name : this.beans.getBeanNames()) { rebind(name); } } @ManagedOperation public boolean rebind(String name) { if (!this.beans.getBeanNames().contains(name)) { return false; } if (this.applicationContext != null) { try { Object bean = this.applicationContext.getBean(name); if (AopUtils.isAopProxy(bean)) { bean = getTargetObject(bean); } this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean); this.applicationContext.getAutowireCapableBeanFactory() .initializeBean(bean, name); return true; } catch (RuntimeException e) { this.errors.put(name, e); throw e; } } return false; } @SuppressWarnings("unchecked") private static <T> T getTargetObject(Object candidate) { try { if (AopUtils.isAopProxy(candidate) && (candidate instanceof Advised)) { return (T) ((Advised) candidate).getTargetSource().getTarget(); } } catch (Exception ex) { throw new IllegalStateException("Failed to unwrap proxied object", ex); } return (T) candidate; } @ManagedAttribute public Set<String> getBeanNames() { return new HashSet<String>(this.beans.getBeanNames()); } @Override public void onApplicationEvent(EnvironmentChangeEvent event) { if (this.applicationContext.equals(event.getSource()) // Backwards compatible || event.getKeys().equals(event.getSource())) { rebind(); } }}我们可以看到该类监听了ChangeEnvrionmentEvent事件,它最主要作用是拿到更新的配置以后,重新绑定@ConfigurationProperties标记的类使之能够读取最新的属性
LoggingRebinder:
/* * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.cloud.logging;import java.util.Map;import java.util.Map.Entry;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.boot.bind.RelaxedPropertyResolver;import org.springframework.boot.logging.LogLevel;import org.springframework.boot.logging.LoggingSystem;import org.springframework.cloud.context.environment.EnvironmentChangeEvent;import org.springframework.context.ApplicationListener;import org.springframework.context.EnvironmentAware;import org.springframework.core.env.Environment;/** * Listener that looks for {@link EnvironmentChangeEvent} and rebinds logger levels if any * changed. * * @author Dave Syer * */public class LoggingRebinder implements ApplicationListener<EnvironmentChangeEvent>, EnvironmentAware { private final Log logger = LogFactory.getLog(getClass()); private Environment environment; @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void onApplicationEvent(EnvironmentChangeEvent event) { if (this.environment == null) { return; } LoggingSystem system = LoggingSystem.get(LoggingSystem.class.getClassLoader()); setLogLevels(system, this.environment); } protected void setLogLevels(LoggingSystem system, Environment environment) { Map<String, Object> levels = new RelaxedPropertyResolver(environment) .getSubProperties("logging.level."); for (Entry<String, Object> entry : levels.entrySet()) { setLogLevel(system, environment, entry.getKey(), entry.getValue().toString()); } } private void setLogLevel(LoggingSystem system, Environment environment, String name, String level) { try { if (name.equalsIgnoreCase("root")) { name = null; } level = environment.resolvePlaceholders(level); system.setLogLevel(name, LogLevel.valueOf(level.toUpperCase())); } catch (RuntimeException ex) { this.logger.error("Cannot set level: " + 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源码:
/* * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.cloud.context.config.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import org.springframework.context.annotation.Scope;import org.springframework.context.annotation.ScopedProxyMode;/** * Convenience annotation to put a <code>@Bean</code> definition in * {@link org.springframework.cloud.context.scope.refresh.RefreshScope refresh scope}. * Beans annotated this way can be refreshed at runtime and any components that are using * them will get a new instance on the next method call, fully initialized and injected * with all dependencies. * * @author Dave Syer * */@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Scope("refresh")@Documentedpublic @interface RefreshScope { /** * @see Scope#proxyMode() */ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;}在这个注解上我们关注一下此处标记了@Scope("refresh"),我们知道Spring的Bean属性有个叫scope的,它定义了bean的作用范围,常见的有singleon,prototype,session等。此处新定义了一个范围叫做refresh,在此我贴出RefreshScope的源代码来分析一下:
/* * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */package org.springframework.cloud.context.scope.refresh;import java.io.Serializable;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanDefinition;import org.springframework.beans.factory.support.BeanDefinitionRegistry;import org.springframework.cloud.context.scope.GenericScope;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.context.event.ContextRefreshedEvent;import org.springframework.context.event.EventListener;import org.springframework.core.Ordered;import org.springframework.jmx.export.annotation.ManagedOperation;import org.springframework.jmx.export.annotation.ManagedResource;/** * <p> * A Scope implementation that allows for beans to be refreshed dynamically at runtime * (see {@link #refresh(String)} and {@link #refreshAll()}). If a bean is refreshed then * the next time the bean is accessed (ie a method is executed) a new instance is * created. All lifecycle methods are applied to the bean instances, so any destruction * callbacks that were registered in the bean factory are called when it is refreshed, and * then the initialization callbacks are invoked as normal when the new instance is * created. A new bean instance is created from the original bean definition, so any * externalized content (property placeholders or expressions in string literals) is * re-evaluated when it is created. * </p> * * <p> * Note that all beans in this scope are <em>only</em> initialized when first accessed, so * the scope forces lazy initialization semantics. The implementation involves creating a * proxy for every bean in the scope, so there is a flag * {@link #setProxyTargetClass(boolean) proxyTargetClass} which controls the proxy * creation, defaulting to JDK dynamic proxies and therefore only exposing the interfaces * implemented by a bean. If callers need access to other methods then the flag needs to * be set (and CGLib present on the classpath). Because this scope automatically proxies * all its beans, there is no need to add <code><aop:auto-proxy/></code> to any bean * definitions. * </p> * * <p> * The scoped proxy approach adopted here has a side benefit that bean instances are * automatically {@link Serializable}, and can be sent across the wire as long as the * receiver has an identical application context on the other side. To ensure that the two * contexts agree that they are identical they have to have the same serialization id. One * will be generated automatically by default from the bean names, so two contexts with * the same bean names are by default able to exchange beans by name. If you need to * override the default id then provide an explicit {@link #setId(String) id} when the * Scope is declared. * </p> * * @author Dave Syer * * @since 3.1 * */@ManagedResourcepublic class RefreshScope extends GenericScope implements ApplicationContextAware, Ordered { private ApplicationContext context; private BeanDefinitionRegistry registry; private boolean eager = true; private int order = Ordered.LOWEST_PRECEDENCE - 100; /** * Create a scope instance and give it the default name: "refresh". */ public RefreshScope() { super.setName("refresh"); } @Override public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } /** * Flag to determine whether all beans in refresh scope should be instantiated eagerly * on startup. Default true. * * @param eager the flag to set */ public void setEager(boolean eager) { this.eager = eager; } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { this.registry = registry; super.postProcessBeanDefinitionRegistry(registry); } @EventListener public void start(ContextRefreshedEvent event) { if (event.getApplicationContext() == this.context && this.eager && this.registry != null) { eagerlyInitialize(); } } private void eagerlyInitialize() { for (String name : this.context.getBeanDefinitionNames()) { BeanDefinition definition = this.registry.getBeanDefinition(name); if (this.getName().equals(definition.getScope()) && !definition.isLazyInit()) { Object bean = this.context.getBean(name); if (bean != null) { bean.getClass(); } } } } @ManagedOperation(description = "Dispose of the current instance of bean name provided and force a refresh on next method execution.") public boolean refresh(String name) { if (!name.startsWith(SCOPED_TARGET_PREFIX)) { // User wants to refresh the bean with this name but that isn't the one in the // cache... name = SCOPED_TARGET_PREFIX + name; } // Ensure lifecycle is finished if bean was disposable if (super.destroy(name)) { this.context.publishEvent(new RefreshScopeRefreshedEvent(name)); return true; } return false; } @ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.") public void refreshAll() { super.destroy(); this.context.publishEvent(new RefreshScopeRefreshedEvent()); } @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; }}该类继承了GenericScope:
/* * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */package org.springframework.cloud.context.scope;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;import java.util.Collections;import java.util.LinkedHashSet;import java.util.List;import java.util.Map;import java.util.UUID;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ConcurrentMap;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.aop.framework.Advised;import org.springframework.aop.scope.ScopedObject;import org.springframework.aop.scope.ScopedProxyFactoryBean;import org.springframework.aop.support.AopUtils;import org.springframework.beans.BeansException;import org.springframework.beans.factory.BeanFactory;import org.springframework.beans.factory.DisposableBean;import org.springframework.beans.factory.ObjectFactory;import org.springframework.beans.factory.config.BeanDefinition;import org.springframework.beans.factory.config.BeanFactoryPostProcessor;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;import org.springframework.beans.factory.config.Scope;import org.springframework.beans.factory.support.BeanDefinitionRegistry;import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;import org.springframework.beans.factory.support.DefaultListableBeanFactory;import org.springframework.beans.factory.support.RootBeanDefinition;import org.springframework.expression.Expression;import org.springframework.expression.ExpressionParser;import org.springframework.expression.ParseException;import org.springframework.expression.spel.standard.SpelExpressionParser;import org.springframework.expression.spel.support.StandardEvaluationContext;import org.springframework.util.ReflectionUtils;import org.springframework.util.StringUtils;/** * <p> * A generic Scope implementation. * </p> * * @author Dave Syer * * @since 3.1 * */public class GenericScope implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean { private static final Log logger = LogFactory.getLog(GenericScope.class); public static final String SCOPED_TARGET_PREFIX = "scopedTarget."; private BeanLifecycleWrapperCache cache = new BeanLifecycleWrapperCache( new StandardScopeCache()); private String name = "generic"; private ConfigurableListableBeanFactory beanFactory; private StandardEvaluationContext evaluationContext; private String id; private Map<String, Exception> errors = new ConcurrentHashMap<>(); private ConcurrentMap<String, ReadWriteLock> locks = new ConcurrentHashMap<>(); /** * Manual override for the serialization id that will be used to identify the bean * factory. The default is a unique key based on the bean names in the bean factory. * * @param id the id to set */ public void setId(String id) { this.id = id; } /** * The name of this scope. Default "generic". * * @param name the name value to set */ public void setName(String name) { this.name = name; } /** * The cache implementation to use for bean instances in this scope. * * @param cache the cache to use */ public void setScopeCache(ScopeCache cache) { this.cache = new BeanLifecycleWrapperCache(cache); } /** * A map of bean name to errors when instantiating the bean. * * @return the errors accumulated since the latest destroy */ public Map<String, Exception> getErrors() { return this.errors; } @Override public void destroy() { List<Throwable> errors = new ArrayList<Throwable>(); Collection<BeanLifecycleWrapper> wrappers = this.cache.clear(); for (BeanLifecycleWrapper wrapper : wrappers) { try { Lock lock = locks.get(wrapper.getName()).writeLock(); lock.lock(); try { wrapper.destroy(); } finally { lock.unlock(); } } catch (RuntimeException e) { errors.add(e); } } if (!errors.isEmpty()) { throw wrapIfNecessary(errors.get(0)); } this.errors.clear(); } /** * Destroy the named bean (ie flush it from the cache by default). * * @param name the bean name to flush * @return true if the bean was already cached, false otherwise */ protected boolean destroy(String name) { BeanLifecycleWrapper wrapper = this.cache.remove(name); if (wrapper != null) { Lock lock = locks.get(wrapper.getName()).writeLock(); lock.lock(); try { wrapper.destroy(); } finally { lock.unlock(); } this.errors.remove(name); return true; } return false; } @Override public Object get(String name, ObjectFactory<?> objectFactory) { BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } } @Override public String getConversationId() { return this.name; } @Override public void registerDestructionCallback(String name, Runnable callback) { BeanLifecycleWrapper value = this.cache.get(name); if (value == null) { return; } value.setDestroyCallback(callback); } @Override public Object remove(String name) { BeanLifecycleWrapper value = this.cache.remove(name); if (value == null) { return null; } // Someone might have added another object with the same key, but we // keep the method contract by removing the // value we found anyway return value.getBean(); } @Override public Object resolveContextualObject(String key) { Expression expression = parseExpression(key); return expression.getValue(this.evaluationContext, this.beanFactory); } private Expression parseExpression(String input) { if (StringUtils.hasText(input)) { ExpressionParser parser = new SpelExpressionParser(); try { return parser.parseExpression(input); } catch (ParseException e) { throw new IllegalArgumentException("Cannot parse expression: " + input, e); } } else { return null; } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; beanFactory.registerScope(this.name, this); setSerializationId(beanFactory); } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { for (String name : registry.getBeanDefinitionNames()) { BeanDefinition definition = registry.getBeanDefinition(name); if (definition instanceof RootBeanDefinition) { RootBeanDefinition root = (RootBeanDefinition) definition; if (root.getDecoratedDefinition() != null && root.hasBeanClass() && root.getBeanClass() == ScopedProxyFactoryBean.class) { if (getName().equals(root.getDecoratedDefinition().getBeanDefinition() .getScope())) { root.setBeanClass(LockedScopedProxyFactoryBean.class); } } } } } /** * If the bean factory is a DefaultListableBeanFactory then it can serialize scoped * beans and deserialize them in another context (even in another JVM), as long as the * ids of the bean factories match. This method sets up the serialization id to be * either the id provided to the scope instance, or if that is null, a hash of all the * bean names. * * @param beanFactory the bean factory to configure */ private void setSerializationId(ConfigurableListableBeanFactory beanFactory) { if (beanFactory instanceof DefaultListableBeanFactory) { String id = this.id; if (id == null) { List<String> list = new ArrayList<>( Arrays.asList(beanFactory.getBeanDefinitionNames())); Collections.sort(list); String names = list.toString(); logger.debug("Generating bean factory id from names: " + names); id = UUID.nameUUIDFromBytes(names.getBytes()).toString(); } logger.info("BeanFactory id=" + id); ((DefaultListableBeanFactory) beanFactory).setSerializationId(id); } else { logger.warn( "BeanFactory was not a DefaultListableBeanFactory, scoped proxy beans " + "cannot be serialized."); } } static RuntimeException wrapIfNecessary(Throwable throwable) { if (throwable instanceof RuntimeException) { return (RuntimeException) throwable; } if (throwable instanceof Error) { throw (Error) throwable; } return new IllegalStateException(throwable); } protected String getName() { return this.name; } private static class BeanLifecycleWrapperCache { private final ScopeCache cache; public BeanLifecycleWrapperCache(ScopeCache cache) { this.cache = cache; } public BeanLifecycleWrapper remove(String name) { return (BeanLifecycleWrapper) this.cache.remove(name); } public Collection<BeanLifecycleWrapper> clear() { Collection<Object> values = this.cache.clear(); Collection<BeanLifecycleWrapper> wrappers = new LinkedHashSet<BeanLifecycleWrapper>(); for (Object object : values) { wrappers.add((BeanLifecycleWrapper) object); } return wrappers; } public BeanLifecycleWrapper get(String name) { return (BeanLifecycleWrapper) this.cache.get(name); } public BeanLifecycleWrapper put(String name, BeanLifecycleWrapper value) { return (BeanLifecycleWrapper) this.cache.put(name, value); } } /** * Wrapper for a bean instance and any destruction callback (DisposableBean etc.) that * is registered for it. Also decorates the bean to optionally guard it from * concurrent access (for instance). * * @author Dave Syer * */ private static class BeanLifecycleWrapper { private Object bean; private Runnable callback; private final String name; private final ObjectFactory<?> objectFactory; public BeanLifecycleWrapper(String name, ObjectFactory<?> objectFactory) { this.name = name; this.objectFactory = objectFactory; } public String getName() { return this.name; } public void setDestroyCallback(Runnable callback) { this.callback = callback; } public Object getBean() { if (this.bean == null) { synchronized (this.name) { if (this.bean == null) { this.bean = this.objectFactory.getObject(); } } } return this.bean; } public void destroy() { if (this.callback == null) { return; } synchronized (this.name) { Runnable callback = this.callback; if (callback != null) { callback.run(); } this.callback = null; this.bean = null; } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } BeanLifecycleWrapper other = (BeanLifecycleWrapper) obj; if (this.name == null) { if (other.name != null) { return false; } } else if (!this.name.equals(other.name)) { return false; } return true; } } @SuppressWarnings("serial") public class LockedScopedProxyFactoryBean extends ScopedProxyFactoryBean implements MethodInterceptor { private String targetBeanName; @Override public void setBeanFactory(BeanFactory beanFactory) { super.setBeanFactory(beanFactory); Object proxy = getObject(); if (proxy instanceof Advised) { Advised advised = (Advised) proxy; advised.addAdvice(0, this); } } @Override public void setTargetBeanName(String targetBeanName) { super.setTargetBeanName(targetBeanName); this.targetBeanName = targetBeanName; } @Override public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); if (AopUtils.isEqualsMethod(method) || AopUtils.isToStringMethod(method) || AopUtils.isHashCodeMethod(method) || isScopedObjectGetTargetObject(method)) { return invocation.proceed(); } Object proxy = getObject(); Lock lock = locks.get(this.targetBeanName).readLock(); lock.lock(); try { if (proxy instanceof Advised) { Advised advised = (Advised) proxy; ReflectionUtils.makeAccessible(method); return ReflectionUtils.invokeMethod(method, advised.getTargetSource().getTarget(), invocation.getArguments()); } return invocation.proceed(); } finally { lock.unlock(); } } private boolean isScopedObjectGetTargetObject(Method method) { return method.getDeclaringClass().equals(ScopedObject.class) && method.getName().equals("getTargetObject") && method.getParameterTypes().length == 0; } }}这里面我们先看一下RefreshScope的构造函数:
/** * Create a scope instance and give it the default name: "refresh". */ public RefreshScope() { super.setName("refresh"); }这里面创建了一个名字为refresh的scope。
紧接着在它的父类里我们可以看一下这个方法:
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; beanFactory.registerScope(this.name, this); setSerializationId(beanFactory); }此方法中使用BeanFactory注册了一个refresh的范围,使得scope为refresh的bean生效。@RefreshScope标注的类还有一个特点:会使用代理对象并进行延迟加载。我们来看一下postProcessBeanDefinitionRegistry方法
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { for (String name : registry.getBeanDefinitionNames()) { BeanDefinition definition = registry.getBeanDefinition(name); if (definition instanceof RootBeanDefinition) { RootBeanDefinition root = (RootBeanDefinition) definition; if (root.getDecoratedDefinition() != null && root.hasBeanClass() && root.getBeanClass() == ScopedProxyFactoryBean.class) { if (getName().equals(root.getDecoratedDefinition().getBeanDefinition() .getScope())) { root.setBeanClass(LockedScopedProxyFactoryBean.class); } } } } }该方法遍历所有的bean定义如果当前的bean的scope为refresh,那么就把当前的bean设置为LockedScopedProxyFactoryBean的代理对象。
RefreshScope还会监听一个ContextRefreshedEvent,该事件会在ApplicationContext初始化或者refreshed时触发,我们来看一下代码:
@EventListener public void start(ContextRefreshedEvent event) { if (event.getApplicationContext() == this.context && this.eager && this.registry != null) { eagerlyInitialize(); } } private void eagerlyInitialize() { for (String name : this.context.getBeanDefinitionNames()) { BeanDefinition definition = this.registry.getBeanDefinition(name); if (this.getName().equals(definition.getScope()) && !definition.isLazyInit()) { Object bean = this.context.getBean(name); if (bean != null) { bean.getClass(); } } } }注意此处获取refreshscope的bean,其中getBean是一个复杂而又繁琐的过程,此处我们先不在这里讨论,只不过经过这个方法以后,其通过代理机制会在GernericScope的BeanLifecycleWrapperCache缓存里把这个@RefreshScope标记的bean添加进去。
最后我们回过头来看一看RefreshScope的refreshAll方法:
@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.") public void refreshAll() { super.destroy(); this.context.publishEvent(new RefreshScopeRefreshedEvent()); }//.......GernericScope的destroy方法@Override public void destroy() { List<Throwable> errors = new ArrayList<Throwable>(); Collection<BeanLifecycleWrapper> wrappers = this.cache.clear(); for (BeanLifecycleWrapper wrapper : wrappers) { try { Lock lock = locks.get(wrapper.getName()).writeLock(); lock.lock(); try { wrapper.destroy(); } finally { lock.unlock(); } } catch (RuntimeException e) { errors.add(e); } } if (!errors.isEmpty()) { throw wrapIfNecessary(errors.get(0)); } this.errors.clear(); }这里的代码逻辑很简单清除与释放缓存里被@RefreshScope标记的bean 。
当我们要获取对象时,我们可以关注如下方法:
@Override public Object get(String name, ObjectFactory<?> objectFactory) { BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } } //..... BeanLifecycleWrapper的方法public Object getBean() { if (this.bean == null) { synchronized (this.name) { if (this.bean == null) { this.bean = this.objectFactory.getObject(); } } } return this.bean; }BeanLifecycleWrapper这个是@RefreshScope标记bean的一个包装类,会被存储到缓存里,在这里取不到值的话就会从objectFactory里去拿
三、示例与总结
3.1、示例
创建AppConfig类代码如下:
package com.bdqn.lyrk.refresh.scope.server;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.cloud.context.config.annotation.RefreshScope;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration@EnableConfigurationProperties(StudentConfig.class)public class AppConfig { @RefreshScope @Bean public Student student(StudentConfig config) { Student student = new Student(); student.setName(config.getName()); return student; }}在这里,将Student设置为@RefreshScope 那么刷新以后会获取最新的Bean
スタートアップクラス:
package com.bdqn.lyrk.refresh.scope.server;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@SpringBootApplication@RestControllerpublic class RefreshScopeApplication { @Autowired private Student student; @GetMapping public String student() { return student.getName(); } public static void main(String[] args) throws InterruptedException { SpringApplication.run(RefreshScopeApplication.class, args); }}application.yml文件:
spring: application: name: refresh-scope-serverendpoints: refresh: sensitive: falseserver: port: 8089student: name: admin
这里把refresh端点开放出来,然后变更配置后就可以获取最新的对象了
3.2、总结
1) 当配置更新并通过refresh端点刷新后,会执行ContextRefresher的refresh方法,该方法会记录当前的Environment,而后构建一个简易的SpringApplicationBuilder并执行其run方法,此时ConfigFileApplicationListener会读取我们修改过后的配置并绑定到SpringApplication对象上,最后进行changes操作来变更已有的PropertySource
2) @RefreshScope最好配合@Bean使用,当且仅当变更配置后,需要重新获取最新的bean时使用。加上该注解的Bean会被代理并且延迟加载,所有的scope属性为Refresh的bean会被包装成BeanLifecycleWrapper存入缓存(ConcurrentHashMap)中,所有的读取,修改,删除都是基于该缓存的。
要約します
以上所述是小编给大家介绍的SpringCloud配置刷新原理解析,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!