SpringCloud에서 구성이 변경되면 http : // xxxx/refresh를 방문하여 서비스를 시작하지 않고 최신 구성을 얻을 수 있음을 알고 있습니다. 그래서 어떻게합니까? 데이터베이스 구성을 변경하고 새로 고침 후 최신 데이터 소스 객체를 얻을 수있는 방법은 무엇입니까? SpringCloud가 어떻게하는지 봅시다.
1. 환경 변화
1.1. Contextrefresher에 대해
액세스 /새로 고침되면 CompertendPoint 클래스에서 처리됩니다. 소스 코드를 살펴 보겠습니다.
/ * * 저작권 2013-2014 원래 저자 또는 저자. * * Apache 라이센스에 따라 라이센스가 부여 된 버전 2.0 ( "라이센스"); * 라이센스를 준수하는 것 외에는이 파일을 사용할 수 없습니다. * 귀하는 * * http://www.apache.org/license/license/license-2.0 *에서 라이센스 사본을 얻을 수 있습니다. 적용 가능한 법률에 의해 요구되거나 서면으로 동의하지 않는 한, 라이센스에 따라 배포 된 소프트웨어 *는 "기준"에 배포됩니다. * 라이센스에 따른 특정 언어 통치 권한 및 * 제한 사항에 대한 라이센스를 참조하십시오. */패키지 org.springframework.cloud.endpoint; import java.util.arrays; import java.util.collection; import java.util.set; import org.sprameframework.ack.actuate.endpoint.abstractendpoint; import org.springframewort.context.configurationProperties; org.springframework.cloud.context.refresh.contextrefresher; import org.springframework.jmx.export.annotation.managedOperation; import org.springframework.jmx.export.annotation.manageource;/** * @author dave syer * @author Venil Norona. */@configurationProperties (prefix = "endpoints.refresh", ingoreUlkNownFields = false) @ManageGesourCepublic ClospherenDPoint 확장 AbstractEndpoint <collection <string >> {private contextresher contextresher; Public RomformendPoint (ConteXtresher ContexTrefresher) {Super ( "Refresh"); this.contextrefresher = contextrefresher; } @ManageDoperation public String [] refresh () {set <string> keys = contextrefresher.refresh (); return keys.toArray (새 문자열 [keys.size ()]; } @override public collection <string> invoke () {return arrays.aslist (refresh ()); }}소스 코드를 통해 새로 고침 엔드 포인트에 액세스 할 때 ContexTrefresher의 새로 고침 메소드가 실제로 실행된다는 것을 알게되었습니다. 그런 다음 소스 코드를 계속 추적하고 새로 고침 메소드를 찾습니다.
public synchronized set <string> refresh () {map <string, object> prever = extract (this.context.getenvironment (). getPropertySources ()); AddConfigFilestoEnvironment (); set <string> keys = changes (prever, extract (this.context.getenvironment (). getPropertySources ())). Keyset (); this.context.publishevent (New EnvironmentChangeEvent (컨텍스트, 키)); this.scope.refreshall (); 리턴 키; }새로 고침 방법이 다음을 수행한다는 것을 알 수 있습니다.
1) 새로 고침 전에 모든 속성 소송을 받으십시오
2) AddConfigFilestoEnvironment 메소드를 호출하여 최신 구성을 얻으십시오
3) 구성 정보를 업데이트하려면 변경 방법을 호출하십시오.
4) EnvironmentchangeEnvent 이벤트를 게시하십시오
5) RESHREATHSCOPE의 Refreshall 메소드를 호출하여 범위를 새로 고치십시오.
2, 3 및 4 단계에 집중합시다
1.2. AddConfigFilestoEnvironment 방법
먼저이 방법이 구현되는 방법을 살펴 보겠습니다.
/ * 테스트 용 */ configurableApplicationContext addConfigFilestoEnvironment () {configurableApplicationContext 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 ()); 캡처 = builder.run (); if (Environment.getPropertySources (). contains (refresh_args_property_source)) {환경 .getPropertySources (). remain (reftish_args_property_source); } mutablePropertySources target = this.context.getenVironment () .getPropertySources (); 문자열 targetName = null; for (propertySource <?> 소스 : 환경 .getPropertySources ()) {String name = source.getName (); if (target.contains (name)) {targetname = name; } if (! this.standardSources.contains (name)) {if (target.contains (name)) {target.replace (이름, 소스); } else {if (targetName! = null) {target.addafter (targetName, source); } else {// targetName은 null이므로 목록의 시작 부분에 있습니다. AddFirst (source); TargetName = 이름; }}}}}} 마침내 {configurablePplicationContext Closeable = capture; while (closeable! = null) {try {closeable.close (); } catch (예외 e) {// 무시; } if (closeable.getParent () configurableApplicationContext의 인스턴스) {closeable = (configurableApplicationContext) closeable.getParent (); } else {break; }}} return Capture; }1)이 방법은 먼저 현재 환경을 복사합니다
2) SpringApplicationBuilder를 통해 간단한 SpringBoot 스타트 업 프로그램을 구축하고 시작하십시오.
builder.application (). setListeners (arrays.aslist (new bootstrapApplicationListener (), new ConfigFileApplicationListener ());
두 리스너가 여기에 추가됩니다 : BootstraPapplicationListener 및 ConfigFileApplicationListener. 이전 학습을 통해 BootstraPapplicationListener는 Bootstrap 프로그램의 핵심 청취자이며 ConfigFileApplicationListener도 매우 중요한 클래스입니다.
/ * * 저작권 2012-2017 원래 저자 또는 저자. * * Apache 라이센스에 따라 라이센스가 부여 된 버전 2.0 ( "라이센스"); * 라이센스를 준수하는 것 외에는이 파일을 사용할 수 없습니다. * 귀하는 * * http://www.apache.org/license/license/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.collections; import java.util.collection; lemport java.util.linkedhashset; 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.BeansException;import org.springframework.beans.CachedIntrospectionResults;import org.springframework.beans.beans.config.beanfactorypostprocessor; import org.springframework.bean.beans.config.config.configurablelistablebeanfactory; import org.springframework.boot.springAppramewort.boot.bind.bind.bind.bind.bind.bind.bind.bod.bind org.springframework.bind.bind.propertysourcespropertyvalues; import org.springframework.boot.bind.relaxeddatabinder; import org.springframework.boot.bind.relaxedpropertyresolver; import org.springframework.boot.context.event.applicationEnvironmentmentpreparedEvent; import org.springframework.boot.context.event.applicationpreparedevent; import org.springframework.env.env.enmecompositeproperce; import org.springframework.env.env.environmentpostprocessor; import org.springframework.boot.env.propertysourcesloader; import org.springframework.boot.logging.deferredlog; import org.springframework.context.applicationevent; import org.springframework.context.configurablepplicationcontext; import org.spramframework.context.annotation.configurationclasspostprocessor; import org.spramepramework.event.smartapplicationListener; import org.springframework.core.cored.cored; org.springframework.core.annotation.annotationawareordercomparator; import org.springframework.core.convert.conversionservice; import org.springframework.core.corevert.support.defaultConversionservice; import org.springframework.enve.env.configurable envonlonment; org.springframework.core.env.enumerablepropertysource; import org.springframework.core.env.mutablepropertysources; import org.sprameframework.core.env.propertysources; import org.springframework.env.env.propersorcesssourcess org.springframework.core.io.defaultresourceloader; import org.springframework.core.io.resourceloader; import org.spramframework.core.io.support.spring actoriorse는 import org.springframework.util.assert; import org.spriderts; org.springframework.util.stringutils; import org.springframework.validation.bindexception;/** * {@link 환경 전문가들에게 잘 알려진 파일 위치에서 * 속성을로드하여 컨텍스트 환경을 구성합니다. 기본적으로 속성은 다음 위치의 * 'application.properties'및/또는 'application.yml'파일에서로드됩니다. * {@link #setsearchlocations (string)} 및 {@link #setsearchnames (string)}을 사용하여 이름을 지정할 수 있습니다. * <p> * 추가 파일은 활성 프로파일을 기반으로로드됩니다. 예를 들어 '웹' * 프로필이 활성 인 경우 'Application-web.properties'및 'application-web.yml'이 * 고려됩니다. * <p> * 'spring.config.name'속성은로드 할 대체 이름을 지정하는 데 사용될 수 있으며 'spring.config.location'속성을 사용하여 대체 검색 * 위치 또는 특정 파일을 지정할 수 있습니다. * <p> * 구성 속성도 {@link springApplication}에 바인딩됩니다. 이로 인해 소스 * ( "Spring.Main.Sources" - CSV 목록)와 같이 {@Link SpringApplication} 속성을 동적으로 설정할 수 있습니다. 웹 환경 * ( "Spring.Main.Web_Environment = True") 또는 배너 * ( "Spring.Main.show_banner = FALLE")을 표시하는 플래그. * * @Author Dave Syer * @Author Phillip Webb * @Author Stephane Nicoll * @Author Andy Wilkinson * @Author Eddú Meléndez */Public ConfigFileApplicationListener Amments EnvironmentPoscessor, SmartApplicationListener, Ordertatic Final Stristies { "" "" "" "" "DEFALLOPERES"; // 순서는 최소에서 가장 구체적으로 (마지막으로 승리) 개인 정적 최종 문자열 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 includ_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.highest_precedence + 10; /*** 응용 프로그램 구성의 이름 {@Link PropertySource}. */ public static final String application_configuration_property_source_name = "ApplicationConfigurationProperties"; Private Final DeferredLog Logger = New DeferredLog (); 개인 문자열 SearchLocations; 개인 문자열 이름; 개인 int order = default_order; Private Final ConversionService Converservice = 새로운 DefaultConversionsErvice (); @override public boolean supportseventtype (class <? extends applicationevent> eventtype) {return applicationenvironmentpreparedevent.class.isassignablefrom (eventtype) || ApplicationPreparedEvent.class.isAssignableFrom (EventType); } @override public boolean supportssourcetype (class <?> aclass) {return true; } @override public void onapplicationEvent (ApplicationEvent 이벤트) {if (atplicationenvironmentpreparedEvent) {onapplicationEnvironmentPreparedEvent ((ApplicationEnvironmentPreparedEvent) 이벤트); } if (이벤트 인스턴스의 applicationpreparedEvent) {onapplicationPreparedEvent (이벤트); }} private void onapplicationEnvironmentPreparedEvent (ApplicationEnvironmentPreparedEvent 이벤트) {list <EnvironmentPostProcessor> postProcessors = loadPostProcessors (); 후 처리기 .add (this); AnnotationAwareOrderComparator.SORT (PostProcessors); for (EnvironmentPostProcessor postprocessor : postprocessors) {postprocessor.postprocessenvironment (event.getenvironment (), event.getSpringApplication ()); }} list <환경 처리 프로세서> loadPostProcessors () {return springFactorioryLoader.LoadFactories (EnvironmentPostProcessor.class, getClass (). getClassLoader ()); } @override public void postprocessenvironment (configurableEnvironment Environment, SpringApplication Application) {addPropertySources (Environment, Application.getResourceloader ()); ConfigureIgnoreBeanInfo (환경); bindtoSpringApplication (환경, 응용 프로그램); } private void configureIgnoreBeanInfo (configurableEnvironment 환경) {if (system.getProperty (cachedIntroSpectionResults.ignore_beanInfo_property_name) == null) {편안한 편안한 편안한 propertyresolver (환경, "); 부울 무시 = resolver.getProperty ( "무시", boolean.class, boolean.true); System.SetProperty (CachedIntrospectionResults.ignore_BeanInfo_property_name, ingore.toString ()); }} private void onapplicationPreparedEvent (ApplicationEvent event) {this.logger.replayto (configfileApplicationListener.class); AddPostProcessors ((((() ApplicationPreparedEvent)) 이벤트) .getApplicationContext ()); } /*** 지정된 환경에 구성 파일 속성 소스를 추가합니다. * @param 환경 * @param resourceloader 리소스 로더에 소스를 추가하는 환경 * @see #addpostprocessors (configurablepplicationcontext) */ 보호 adppropertysources (configurableenvironment renvironment resourceloader) {renducceloader resourceloader) {randomvaluepropertysource.addtoenvonnment (환경); New Loader (Environment, ResourcelOader) .load (); } /*** 환경을 {@link springApplication}에 바인딩합니다. * @param 환경 * @Param 응용 프로그램 */ 보호 void bindtoSpringApplication에 바인딩하기위한 응용 프로그램 (구성 가능성 환경, SpringApplication Application) {propertiesconfigurationFactory <SpringApplication> binder = new PropertiesConfigurationFactory <SpringApplication> (응용 프로그램); binder.settargetname ( "spring.main"); binder.setconversionservice (this.conversionservice); binder.setPropertySources (Environment.GetPropertySources ()); try {binder.bindpropertiestotarget (); } catch (bindexception ex) {새로운 불법 스테이트 exception을 던지십시오 ( "SpringApplication에 바인딩 할 수 없음", 예); }} /*** 속성 소스를 사후 구성하기 위해 적절한 후 처리기를 추가합니다. * @Param 컨텍스트 구성 */ 보호 void AddPostProcessors (configurableApplicationContext Context) {context.AddBeanFactoryPoscessor (New PropertySourceOrderingPostProcessor (Context)); } public void setorder (int order) {this.order = Order; } @override public int getOrder () {return this.order; } /*** 쉼표로 구분 된 목록으로 간주 될 검색 위치를 설정합니다. 각 * 검색 위치는 디렉토리 경로 ( "/"로 끝나는) 여야하며 {@link #setsearchNames (string) 검색 이름} 및 * 프로파일 (있는 경우)으로 구성된 파일 확장자가 지원하는 {@link #setsearchNames (string) names}에서 구성된 파일 이름으로 * 접두사 *로 표시됩니다. * 위치는 지정된 순서로 고려되며, 이후 항목은 우선 순위 * (맵 병합과 같은). * @param 위치 검색 위치 */ public void setsearchlocations (문자열 위치) {assert.haslength (위치, "위치는 비어 있지 않아야한다"); this.searchlocations = 위치; } /** *로드 해야하는 파일의 이름을 * 쉼표로 구분 된 목록으로로드해야합니다 (파일 확장 제외). * @param은로드 할 이름을 이름으로 지명 */ public void setsearchNames (문자열 이름) {assert.haslength (이름, "이름이 비어 있지 않아야합니다"); this.names = 이름; } /** * {@link beanfactorypostprocessor} {@link configurationClassPostProcessor}에 의해 추가 된 * {@code @propertysource} 항목 아래의 속성 소스를 재산으로 다시 주문합니다. */ private class propertySourceOrderingPostProcessor는 beanFactoryPostProcessor를 구현합니다. PropertySourceOrderingPostProcessor (configurableApplicationContext Context) {this.context = context; } @override public int getOrder () {return ordered.highest_precedence; } @override public void postprocessbeanfactory (configurableBleistablebeanfactory beanfactory) beansexception {Reordersources (this.context.getenvironment ()); } private void readersources (configurableEnvironment Environment) {configurationPropertySources .FinishAndOcate (Environment.GetPropertySources ()); PropertySource <?> defaultPropertySources = Environment.GetPropertySources () .remove (default_properties); if (defaultProperties! = null) {환경 .getPropertySources (). addLast (defaultProperties); }}} /*** 후보 속성 소스를로드하고 활성 프로파일을 구성합니다. */ 개인 클래스 로더 {private final log logger = configfileApplicationListener.this.logger; 개인 최종 구성 가능 환경 환경; 개인 최종 리소스 셀로더 Resourceloader; PRIBSE SOURCESLOADER PropertiesLoader; 개인 대기열 <profile> 프로파일; 비공개 목록 <profile> ProcessedProfiles; 개인 부울 활성화 프로파일; 로더 (구성 가능성 환경 환경, 리소스 셀 로더 리소스 셀로더) {this.environment = 환경; this.resourceloader = resourceloader == null? 새로운 defaultresourceloader () : resourceloader; } public void load () {this.propertiesloader = new PropertySourcesLoader (); this.activatedprofiles = false; this.profiles = collections.aslifoqueue (New LinkedList <profile> ()); this.processedProfiles = new LinkedList <profile> (); // 환경을 통해 설정된 활성 프로파일 (). SetActiveProfiles () // 추가 프로파일이며 구성 파일은 원하는 경우 더 많은 추가가 허용되므로 여기에서 addActiveProfiles ()를 호출하지 마십시오. set <profile> initialActiveProfiles = InitializeactiveProfiles (); this.profiles.addall (getUnprocessedActiveProfiles (initialActiveProfiles)); if (this.profiles.isempty ()) {for (string defaultProfilename : this.environment.getDefaultProfiles ()) {프로파일 DefaultProfile = 새 프로파일 (defaultProfilename, true); if (! this.profiles.contains (defaultProfile)) {this.profiles.add (defaultProfile); }}} // 이러한 목적의 기본 프로파일은 NULL로 표시됩니다. 우리는 그것을 큐에서 먼저 나가도록 마지막으로 추가합니다 (활성 프로파일은 // 나중에 목록이 반전 될 때 기본값의 설정을 무시합니다). this.profiles.add (null); while (! this.profiles.isempty ()) {프로파일 프로파일 = this.profiles.poll (); for (문자열 위치 : getSearchLocations ()) {if (! location.endswith ( "/")) {// 위치는 이미 파일 이름이므로 더 많은 // filenames load (location, null, profile)를 검색하지 마십시오. } else {for (문자열 이름 : getSearchNames ()) {load (위치, 이름, 프로파일); }}} this.processedProfiles.add (프로파일); } addConfigurationProperties (this.propertiesLoader.GetPropertySources ()); } private set <privice> 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 ConfigurableEnvironment #addActiveProfile (String)을 통해 * 활성화 된 경우 {@link #Active_Profiles_Property} 값이 정확한 경우 * 필터링되어야합니다. * <p> * 구체적으로, "클라우드"프로파일이 환경을 통해 활성화되면 {@link #Active_profiles_Property}를 통해 모든 프로파일이 설정된 것보다 우선 순위가 낮습니다. * @param initialActiveProfiles * {@link #Active_profiles_Property} * @@Return 환경에서 비공식 활성 프로파일을 활성화하기 위해 */ private list <foly> getUnProcessedActiveProfiles (set <profile> initialActiveProfiles) {list <프로파일> profcedationActiveProfiles = 새로운 jatraylist> (); for (string profilename : this.environment.getActiveProfiles ()) {프로파일 프로파일 = 새 프로파일 (profilename); if (! initialActiveProfiles.contains (profile)) {exprocessedActiveProfiles.Add (프로파일); }} // 순서가 getProfilesforValue ()의 것과 동일하게 반전되도록 반전하십시오 () // (마지막으로 속성이 해결 될 때 마지막으로 승리) collections.Reverse (vercessedActiveProfiles); 가공되지 않은 경우 반환; } private void load (문자열 위치, 문자열 이름, 프로파일 프로파일) {String group = "profile =" + (profile == null? ": 프로파일); if (! stringUtils.hastext (name)) {// LoadinTogroup (그룹, 위치, 프로파일)에서 직접로드하려고합니다. } else {// 주어진 이름이있는 파일을 검색합니다 (문자열 ext : this.propertiesloader.getallFileExtensions ()) {if (profile! = null) {// 프로필 단위로 loadIntOgroup (group, location + name + "-" + 프로파일 + "." + ext, null); for (profile processedProfile : this.processedProfiles) {if (processedProfile! = null) {loadIntOgroup (그룹, 위치 + 이름 + "-" + processedProfile + "." + ext, 프로파일); }} // 때때로 사람들은 // application-dev.yml (GH-340)에 "spring.profiles : dev"를 넣습니다. 아마도 우리는 그것에 대해 오류를 시도하고 오류를 시도해야하지만, 우리는 친절하고 어쨌든 그것을로드 할 수 있습니다. loadIntOgroup (Group, location + name + "-" + profile + "." + ext, profile); } // 또한 일반 파일의 프로파일-특이 적 섹션 (있는 경우 loadIntOgroup) (그룹, 위치 + 이름 + "." + ext, 프로파일); }}} Private PropertySource <?> loadIntOgroup (문자열 식별자, 문자열 위치, 프로파일 프로파일) {try {return doloadinTogroup (식별자, 위치, 프로파일); } catch (예외) {Throw New New ElegalStateException ( "위치에서 속성 소스를로드하지 못했습니다 '" + location + "", ex); }} 사유지 소스 <?> doloadinTogroup (문자열 식별자, 문자열 위치, 프로파일 프로파일)은 ioException {resource resource = this.ResourcelOader.getResource (location); PropertySource <?> propertySource = null; StringBuilder msg = new StringBuilder (); if (resource! = null && resource.exists ()) {String name = "ApplicationConfig : [" + location + "]"; String group = "ApplicationConfig : [" + Identifier + "]"; PropertySource = this.propertiesloader.load (자원, 그룹, 이름, (profile == null? null : profile.getName ())); if (propertySource! = null) {msg.append ( "loaded"); 핸들 프로필 프 로피트 (PropertySource); } else {msg.append ( "건너 뛰기 (빈)"); }} else {msg.append ( "건너 뛰기"); } msg.append ( "구성 파일"); msg.append (getResourcedEscription (위치, 자원)); if (profile! = null) {msg.append ( "for profile") .append (프로파일); } if (resource == null ||! resource.exists ()) {msg.append ( "resource 찾기"); this.logger.trace (msg); } else {this.logger.debug (msg); } Return PropertySource; } private String getResourcedEscription (문자열 위치, 리소스 리소스) {String ResourcedEscription = " ' + location +" "; if (resource! = null) {try {resourcedescription = string.format ( " '%s'(%s)", resource.geturi (). toasciistring (), location); } catch (ioException ex) {// 위치를 설명으로 사용}} return resourcedEscription; } private void handsprofileproperties (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 (PropertionSources); } private springProfiles bindspringProfiles (PropertionSources PropertySources) {SpringProfiles SpringProfiles = 새로운 SpringProfiles (); ReslingDatabinder Databinder = New RestonDatabinder (SpringProfiles, "Spring.profiles"); Databinder.Bind (New PropertySourcesPropertyValues (PropertySources, False)); SpringProfiles.setActive (Resolveplyplockenders (SpringProfiles.getActive ())); SpringProfiles.setInclude (Resolveplyplodholders (SpringProfiles.getInclude ())); SpringProfiles를 반환합니다. } private list <string> Resolveplyplockenders (list <string> value) {list <string> resolved = new arraylist <string> (); for (문자열 값 : 값) {resolved.add (this.environment.resolveplypholders (value)); } 반환 해결; } private void private void maybeactivateProfiles (set <profile> profiles) {if (this.ActivateProfiles) {if (! profiles.isempty ()) {this.logger.debug ( "이미 활성화 된, '" + 프로파일 + "'는 적용되지 않습니다"); } 반품; } if (! profiles.isempty ()) {addProfiles (profiles); this.logger.debug ( "활성화 된 프로파일" + stringUtils.collectionTocommadelimitedString (profiles)); this.activatedprofiles = true; removeUnProcessedDefaultProfiles (); }} private void void removeProcessedDefaultProfiles () {for (iterator <profile> iterator = this.profiles.iterat 또는 (); iterator .hasnext ();) {if (iterator.next (). isdefaultProfile ()) {iterator.remove (); }}} private void addProfiles (set <profile> 프로파일) {for (프로파일 : 프로파일) {this.profiles.add (프로파일); if (! EnvironmentHasactiveProfile (profile.getName ())) {// 이미 허용 된 경우 순서가 설정되었다고 가정합니다. }}} private boolean 환경 HasactiveProfile (String Profile) {for (String activeProfile : this.environment.getActiveProfiles ()) {if (activeProfile.equals (profect)) {return true; }} 거짓을 반환합니다. } private void prependProfile (configurableEnvironment 환경, 프로파일 프로파일) {set <string> profiles = new LinkedHashset <string> (); 환경 .getActiveProfiles (); // 초기화되었는지 확인 // 그러나 이것은 먼저 가야합니다 (속성 키 클래쉬에서 마지막 승리) profiles.add (profile.getName ()); profiles.addall (arrays.aslist (환경 .getActiveProfiles ()); Environment.setActiveProfiles (profiles.toArray (새 문자열 [profiles.size ()]); } private set <string> getSearchLocations () {set <string> locations = new LinkedHashset <string> (); // 사용자가 구성된 설정이 우선합니다. (this.environment.containsProperty (config_location_property)) {for (문자열 경로 : asresolvedset (this.environment.getProperty (config_location_property), null) {if (! $ ") {wath.contains ("$ ")). StringUtils.cleanPath (Path); if (! resourceutils.isurl (path)) {path = resourceutils.file_url_prefix + path; }} locations.add (경로); }} locations.addall (asresolvedset (configfileApplicationListener.this.SearchLocations, default_search_locations)); 반환 위치; } private set <string> getSearchNames () {if (this.environment.containsProperty (config_name_property)) {return asresolvedset (this.environment.getProperty (config_name_property), null); } return asresolvedset (configfileApplicationListener.this.Names, default_names); } private set <string> asresolvedset (문자열 값, 문자열 폴백) {list <string> list = arrays.aslist (stringUtils.trimarrayElements (stringUtils.commadelimitedListToStringArray (value! = null? this.environment.resolveplaceholders (value))); Collections.Reverse (목록); 새로운 LinkedHashset <string> (목록)을 반환합니다. } private void addConfigurationProperties (mutablePropertySources 소스) {list <propertySource <? >> readordSources = new ArrayList <propertySource <? >> (); for (propertySource <?> 항목 : 소스) {reorderedSources.add (항목); } addConfigurationProperties (새로운 configurationPropertySources (재정렬);} private void addConfigurationSoperties (configurationPropertySources soliturationSources) {mutablePropertySources ResentingSources = this.envornment. {EndessOURCES. Assert.Name = DefaultProfile (return this.name); hashcode () {return.name.hashcode (); {@link propertysourtysources가 열거 된 propertysource <? >>> propertysource <? >> 개인 이름; ConfigurationPropertySources (Collection <PropertySource <? >> 소스) {super (application_configuration_property_source_name); 이름. ADDALL ((EnumerablePropertySource <?>) 소스). getPropertyNames ())}}}; propertysource.getProperty (value! = null) {return null}}}} {string name = application_copertysproperces .get (name); if (removed! = null) {for (propertySource <?> repsource : rEmoved.Sources) {if (enumerableCompositEpropertySource) {enumerableCompositSourceSite = (EnumerAbleComperTysource); (PropertySource getPropertyNames () {return this.names}} * {@code spring.profiles} holder {private list <string> new arraylist <string> (); setActive (list <string> active = active <string> getinclude (retrance this.include) {this.include =} set getactiveProfiles Asprofileset (this.include) <string <string> profiles = new arting <profiles <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 method
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. Publish events
当上述步骤都执行完毕后,紧接着会发布EnvrionmentChangeEvent事件,可是这个事件谁来监听呢?在这里我贴出官网的一段描述:
应用程序将收听EnvironmentChangeEvent,并以几种标准方式进行更改(用户可以以常规方式添加ApplicationListeners附加ApplicationListeners)。当观察到EnvironmentChangeEvent时,它将有一个已更改的键值列表,应用程序将使用以下内容:
1.重新绑定上下文中的任何@ConfigurationProperties bean
2.为logging.level.*中的任何属性设置记录器级别
根据官网描述我们知道将变更一下操作行为@ConfigurationProperties的bean与更改日志level,那么如何做到的呢?结合官网文档我们来关注以下两个类:
ConfigurationPropertiesRebinder:
/* * Copyright 2013-2014 the original author or authors. * * Apache 라이센스에 따라 라이센스가 부여 된 버전 2.0 ( "라이센스"); * 라이센스를 준수하는 것 외에는이 파일을 사용할 수 없습니다. * 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. * 라이센스에 따른 특정 언어 통치 권한 및 * 제한 사항에 대한 라이센스를 참조하십시오. */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(); this.applicationContext.getAutowireCapableBeanFactory(); .initializeBean(bean, name); 진실을 반환하십시오. } catch (RuntimeException e) { this.errors.put(name, e); throw e; }} 거짓을 반환합니다. } @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. * * Apache 라이센스에 따라 라이센스가 부여 된 버전 2.0 ( "라이센스"); * 라이센스를 준수하는 것 외에는이 파일을 사용할 수 없습니다. * 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. * 라이센스에 따른 특정 언어 통치 권한 및 * 제한 사항에 대한 라이센스를 참조하십시오. */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()); 개인 환경 환경; @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事件,用于重新绑定日志级别
二、刷新范围
We consider the following scenario: When we change the database configuration and refresh, although we can obtain the latest configuration, our DataSource object has long been initialized. In other words, even if the configuration is refreshed, what we still get is the object before the configuration refresh. 이 문제를 해결하는 방법?
我们继续看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. * * Apache 라이센스에 따라 라이센스가 부여 된 버전 2.0 ( "라이센스"); * 라이센스를 준수하는 것 외에는이 파일을 사용할 수 없습니다. * 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. * 라이센스에 따른 특정 언어 통치 권한 및 * 제한 사항에 대한 라이센스를 참조하십시오. */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.ScopeedProxyMode;/** * 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. * * Apache 라이센스에 따라 라이센스가 부여 된 버전 2.0 ( "라이센스"); 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 identity 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)); 진실을 반환하십시오. } 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. * * Apache 라이센스에 따라 라이센스가 부여 된 버전 2.0 ( "라이센스"); 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.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.aopalliance.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); 진실을 반환하십시오. } 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 instance of 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()); 반환 결과; } @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; } 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配置刷新原理解析,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!