Мы знаем, что в Springcloud, когда изменяется конфигурация, мы можем получить последнюю конфигурацию, не запустив службу, посетив http: // xxxx/rebresh. Так как же это делать? Как мы можем получить новейший объект источника данных после изменения конфигурации базы данных и обновления? Посмотрим, как это делает Springcloud.
1. Изменения окружающей среды
1.1. О Contextrefresher
Когда мы получаем доступ /обновлять, он будет обработан классом ForrehendPoint. Давайте посмотрим на исходный код:
/ * * 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; импорт org.spramework.boot.properties.confirties. org.springframework.cloud.context.refresh.contextrefresher; import org.springframework.jmx.export.annotation.managedoperation; импорт org.springframework.jmx.export.annotation.managedresource;/** * @author dave syer * @author */@ConfigurationProperties (prefix = "endpoints.refresh", ignoreunknownfields = false) @manageresourcepublic class reveshendpoint extrableendpoint <collection <string >> {private contextrefresher contextrefreher; public refreshendpoint (contextrefresher contextrefresher) {super ("rebresh"); this.contextrefresher = contextrefresher; } @ManagedOperation public String [] refresh () {set <stry> keys = contextrefresher.refresh (); return keys.toarray (new String [keys.size ()]); } @Override public Collection <String> invoke () {return arrays.aslist (refresh ()); }}Через исходный код мы узнали, что при доступе к конечной точке обновления метод обновления Contextrefresher фактически выполняется. Затем мы продолжаем проследить исходный код и находим его метод обновления:
public Synchronized set <stry> refresh () {map <string, object> перед = extract (this.context.getenvironment (). getPropertySources ()); AddConfigFilestoEnvironment (); SET <String> keys = изменения (до, extract (this.context.getEnvironment (). GetPropertySources ())). KeySet (); this.context.publishevent (new EnvironmentChangeEvent (Context, Keys)); this.scope.refreshall (); вернуть ключи; }Мы видим, что метод обновления выполняет следующее:
1) Получите все недвижимость перед обновлением
2) Вызов AddConfigfilestoEnvironvironment Method, чтобы получить последнюю конфигурацию
3) Вызовите метод изменений, чтобы обновить информацию о конфигурации
4) Публикуйте событие EnvironmantEnvent
5) Вызовите метод обновления обновления, чтобы обновить диапазон
Давайте сосредоточимся на 2, 3 и 4 шагах
1.2. AddConfigFilestoEnvironment Метод
Давайте сначала рассмотрим, как реализован этот метод:
/ * Для тестирования */ configurableApplicationContext AddConfigFilestoEnvironment () {configurableApplicationContext capture = null; try {StandardEnvironment Environment = copyEnvironment (this.context.getEnvironment ()); SpringApplicationBuilder Builder = New SpringApplicationBuilder (umpty.class) .bannermode (mode.off) .web (false) .Environment (среда); // просто слушатели, которые влияют на окружающую среду (например, исключая журнал // слушатель, потому что у него есть побочные эффекты) Builder.Application () .SetListeners (Arrays.aslist (новый BootstrappplicationListener (), новый ConfigFileApplicationListener ())); capture = builder.run (); if (Environment.getPropertySources (). Содержит (refresh_args_property_source)) {Environment.getPropertySources (). Remove (refresh_args_property_source); } MitablePropertySources target = this.context.getEnvironment () .getPropertySources (); String TargetName = null; для (свойства property <?> Источник: 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) {target.Addafter (targetName, source); } else {// TargetName был нулевым, поэтому мы находимся в начале списка Target.AddFirst (Source); TargetName = name; }}}} / while (qualbable! = null) {try {queplable.close (); } catch (Exception e) {// Игнорировать; } if (stockable.getParent () экземпляр configurableApplicationContext) {stockable = (configurableApplicationContext) useable.getParent (); } else {break; }}} return Capture; }1) Этот метод сначала копирует текущую среду
2) Создайте простую программу запуска Springboot через SpringApplicationBuilder и Start
builder.application (). SetListeners (Arrays.aslist (новый BootstrapapplicationListener (), новый ConfigFileApplicalationListener ()));
Здесь будут добавлены два слушателя: BootstrapapplicationListener и ConfigFileApplicationListener. Благодаря предыдущему обучению мы знаем, что BootstrapapplicationListener является основным слушателем программы Bootstrap, а ConfigFileApplicationListener также является очень важным классом:
/ * * Copyright 2012-2017 Первоначальный автор или авторы. * * Лицензирован по лицензии Apache, версия 2.0 («Лицензия»); * Вы не можете использовать этот файл, кроме как в соответствии с лицензией. * Вы можете получить копию лицензии по адресу * * http://www.apache.org/licenses/license-2.0 * *, если не требуется применимый закон или не согласен в письменной форме, программное обеспечение *, распределено по лицензии, распределяется на «как это», * без гарантий или условий любого рода, либо экспрессии, либо подразумевается. * См. Лицензию для конкретного языка, регулирующих разрешения и * ограничения по лицензии. */package org.springframework.boot.context.config; import java.io.ioexception; импорт java.util.arraylist; import java.util.arrays; импорт java.util.collection; import java.util.collections; импорт java.util.itertor; import java.Util.linklections; импорт java.util.iterator; importhashletections; импорт java.util.iterator; импорт. 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; импорт org.spramework.beans.cachedIntroStropsults; org.springframework.beans.factory.config.beanfactorypostprocessor; import org.springframework.beans.factory.config.configurablelistablebeanfactory; import org.springframework.boot.springpplication; импорт org.spramework.boot.bind.bind.bind.propertory org.springframework.boot.bind.propertysourcespropertyvalues; импорт org.springframework.boot.bind.relaxedDatabinder; импорт org.springframework.boot.bind.relaxedPropertyresolver; import org.springframework.boot.context.event.applicationEnvironmentPrepareDevent; импорт org.springframework.boot.context.event.applicationPreparedEvent; импорт org.springframework.boot.env.enumerablecompositepropertysource; import org.springframework.boot.env.environmentpostprocessor; import org.springframework.boot.env.propertysourcesloader; импорт org.springframework.boot.logging.deferredlog; import org.spramework.context. org.springframework.context.configurableapplicationcontext; import org.springframework.context.annotation.configurationclasspostprocessor; import org.springframework.context.event.smartapplicationlistener; импорт org.spramework.core. org.springframework.core.annotation.annotationawareordercomparator; импорт org.springframework.core.convert.conversionsersers; импорт org.springframework.core.convert.support.defconcersionsionservice; импорт org.spramework.core.core.configueldervice; импорт. org.springframework.core.env.enumerablepropertySource; импорт org.springframework.core.env.mutablepropertysources; импорт org.springframework.core.env.propertysources; импорт org.spramework.core.env.propertysources; org.springframework.core.io.DefaultResourceLoader;import org.springframework.core.io.ResourceLoader;import org.springframework.core.io.support.SpringFactoriesLoader;import org.springframework.util.Assert;import org.springframework.util.ResourceUtils;import org.springframework.util.stringutils; import org.springframework.validation.bindexception;/** * {@link EnvironmentPostProcessor}, который настраивает контекстную среду путем загрузки * свойств из известных местоположений файлов. По умолчанию свойства будут загружены из * '' Application.properties 'и/или' Application.yml 'файлы в следующих местоположениях: * <ul> * <li> classpath: </li> * <li> Файл: ./ </li> * <li> classpath: config/</li> * <li> file: ./ config/: </li> * <li> file: ./1 </<li> * <p> и имена могут быть указаны с использованием * {@link #setsearchlocations (string)} и {@link #setsearchnames (String)}. * <p> * Дополнительные файлы также будут загружены на основе активных профилей. Например, если профиль «Web» * активен. * <p> * Свойство «spring.config.name» можно использовать для указания альтернативного имени для загрузки *, а свойство «spring.config.location» можно использовать для указания альтернативного поиска * местоположения или конкретных файлов. * <p> * Свойства конфигурации также связаны с {@link Springapplication}. Это позволяет * динамически установить свойства @link SpringApplication}, как и источники * ("spring.main.sources" - список CSV) флаг, чтобы указать веб -среду * ("Spring.main.Web_environment = true") или флаг, чтобы выключить баннер * ("spring.main.show_banner = false"). * * @author Dave Syer * @author Phillip Webb * @author Stephane Nicoll * @author Andy Wilkinson * @author Eddú Meléndez */public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered { private static final String DEFAULT_PROPERTIES = "defaultProperties"; // Примечание. Порядок от наименьшего до наиболее конкретного (последний выигрыш) частная статическая конечная строка default_search_locations = "classpath:/, classpath:/config/, file: ./, file: ./ config/"; частная статическая конечная строка default_names = "Приложение"; /*** Имя свойства "Active Prifiles". */ public static final String active_profiles_property = "spring.profiles.active"; /*** Имя свойства «включает профили». */ public static final String incluct_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 = заказан. highest_precedence + 10; /*** Имя конфигурации приложения {@link PropertySource}. */ public static final String Application_configuration_property_source_name = "ApplicationConfigurationProperties"; Частный окончательный deferredlog logger = new DeferredLog (); Приватные строки поиска; частные имена строк; private int order = default_order; частное окончательное преобразование конвейса конвейонащика = новый DefaultConversionService (); @Override public boolean supportseventType (class <? Extends ApplicationEvent> eventType) {return ApplicationEnvirommentPrepareDevent.class.isassignablefrom (EventType) || ApplicationPrepReedEvent.class.isassignableFrom (EventType); } @Override public boolean supportssourcetype (class <?> Aclass) {return true; } @Override public void onApplicationEvent (ApplicationEvent Event) {if (Event exanceOf ApplicationEnvirommentPrepareDevent) {onApplicationEnvironmentPrearedEvent (((ApplicationEnvirOUnmentPRearedEvent) событие); } if (exantef exaturalof ApplicationPrearedEvent) {onApplicationPrepareDevent (event); }} private void onApplicationEnvironmentPrepareDevent (ApplicationEnvironmentPrearedEvent Event) {List <EnvironmentPostProcessor> PostProcessors = LoadPostProcessors (); PostProcessors.Add (это); AnnotationAwareRoderComparator.sort (постпроцессоры); для (EnvironmentPostProcessor PostProcessor: PostProcessors) {PostProcessor.PostProcessEnvironment (Event.getEnvironment (), Event.getSpringApplication ()); }} Список <EnvironmentPostProcessor> LoadPostProcessors () {return SpringFactoryLoctory.LoadFactories (EnvironmentPostProcessor.Class, getClass (). GetClassLoader ()); } @Override public void postProcessEnvironment (среда настраиваемой среды, приложение SpringApplication) {addPropertySources (Environment, Application.getResourceloader ()); configureIgnoreBeanInfo (среда); BindToSpringApplication (среда, приложение); } private void configureIgnoreBeanInfo (ConfineMavenEnvironment Environment) {if (System.getProperty (CachedIntrospectionResults.Ignore_beaninfo_property_name) == null) {relexedpropertyresolver resolver = new RelectedPropertyResolver (Environment, "Spring.Beaninfo."); Boolean Ignore = Resolver.getProperty («Игнорировать», boolean.class, boolean.true); System.SetProperty (CachedIntrospectionResults.Ignore_beaninfo_property_name, ignore.toString ()); }} private void onApplicationPreparEdevent (applyevent event) {this.logger.replayto (configfileapplicationlistener.class); AddPostProcessors ((((ApplicationPrepareDevent) Event) .getApplicationContext ()); } /*** Добавить источники свойств файла конфигурации в указанную среду. * @param среда, среда, чтобы добавить источник в * @param resourceloader the Resource Loader * @see #addpostprocessors (configurableapplicalationcontext) */ protected void addpropertysources (configatedEnvironment Environment, ResourceLoader ResourceloAder) {RandomValuePropertySource.AddtoEnvironmentry (Environmation); новый погрузчик (среда, ресурс -нагрузка) .load (); } /*** Приведите среду с {@link SpringApplication}. * @param среда среда для привязки * @param Приложение для привязки к */ защищенному void bindtospringapplication (настраиваемое среда для среды, приложение Spressapplication) {PropertiesConfigurationFactory <pressapplication> binder = new PropertiesConfigurationFactory <SpressApplication> (приложение); Binder.SetTargetName ("Spring.Main"); binder.setConversionservice (this.conversionservice); Binder.SetPropertySources (Environment.getPropertySources ()); try {binder.bindpropertiestoTarget (); } catch (bindException ex) {бросить новое allodalStateException («не может привязать к SpringApplication», Ex); }} /*** Добавьте соответствующие пост-обработки для пост-конфигурации имущественных источников. * @param context Контекст для настройки */ protected void AddPostProcessors (configurableApplicalationcontext context) {context.addbeanfactorypostprocessor (new PropertySourceordingPostProcessor (context)); } public void setOrder (int order) {this.order = order; } @Override public int getOrder () {return this.order; } /*** Установите места поиска, которые будут рассматриваться как разделенный запятой список. Каждое местоположение поиска должно быть пути каталога (заканчивающийся в «/»), и он будет предварительно профиксирован * по именам файлов, построенных из {@link #setsearchnames (String) имена поиска} и * профили (если таковые имеются) плюс расширения файлов, поддерживаемые загрузчиками свойств. * Места рассматриваются в указанном порядке, причем более поздние элементы имеют приоритет * (как слияние карты). * @param локации локации поиска */ public void setSearchlocations (строки местоположения) {assert.haslength (местоположения, "местоположения не должны быть пустыми"); this.searchlocations = местоположения; } /** * Устанавливает имена файлов, которые должны быть загружены (исключая расширение файла) в виде *, разделенного с запятой списком. * @param Наносит имена для загрузки */ public void setSearchNames (String names) {assert.haslength (имена, "имена не должны быть пустыми"); this.names = names; } /** * {@link beanfactorypostprocessor} для повторного порядка наших источников свойств ниже любого * {@code @propertysource} элементы, добавленные {@link configuration classpostprocessor}. */ Private Class PropertySourceOrdingPostProcessor реализует BeanFactoryPostProcessor, заказанный {private ConfigurableApplicationContext Context; Свойства PropertySourceOrdingPostProcessor (configurableApplicationContext context) {this.context = context; } @Override public int getOrder () {return Ordered.highest_precedence; } @Override public void postprocessbeanfactory (configurablelistablebeanfactory beanfactory) бросает Beansexception {reordersources (this.context.getenvironment ()); } private void reordersources (конфигурируемая среда проживания) {ConfigurationPropertySources .finishandReLocate (Environment.getPropertySources ()); PropertySource <?> DefaultPropertySources = Environment.GetPropertySources () .Remove (default_properties); if (defaultProperties! = null) {Environment.getPropertySources (). Addlast (defaultProperties); }}} /*** Загружает источники свойств кандидата и настраивает активные профили. */ private Class Loader {private final log logger = configFileApplicationlistener.this.logger; частная конечная конфигурированная среда окружающей среды; Частный окончательный ресурс -загрузчик ResourceLoader; Private Propertysourcesloader PropertiesLoader; Частная очередь <Профиль> Профили; частный список <friels> procepledProfiles; Частный логический активированный профиль; Loader (конфигурируемая среда проживания среды, ресурс -ресурс -загрузатель) {this.Environment = среда; this.resourceloader = resourceloader == null? Новый Defaultresourceloader (): ResourceLoader; } public void Load () {this.propertiesloader = new PropertySourcesloader (); this.activatedprofiles = false; this.profiles = collections.aslifoqueue (new LinkedList <frople> ()); это. // Предыдущие активные профили, установленные через Environment.SetactivePiles () // Дополнительные профили, и файлы конфигурации могут добавить больше, если // они хотят, поэтому не вызовите AddatectiveProfiles () здесь. Установить <fifile> initialActiveProfiles = initializeactiveProfiles (); this.profiles.addall (getUnProcatedActiveProfiles (initiveActiveProfiles)); if (this.profiles.isempty ()) {for (String defaultProfilEname: this.Environment.getDefaultprofiles ()) {profile defaultProfile = новый профиль (defaultProfilEname, true); if (! this.profiles.contains (defaultprofile)) {this.profiles.add (defaultprofile); }}} // Профиль по умолчанию для этих целей представлен как нулевый. Мы добавляем его // последнее так, чтобы он был сначала вне очереди (активные профили будут затем // переопределять любые настройки в дефолте, когда список будет изменен позже). this.profiles.add (null); while (! this.profiles.isempty ()) {профиль профиль = this.profiles.poll (); Для (строка местоположения: getearchlocations ()) {if (! location.endswith ("/")) {// местоположение уже имен файла, так что не ищите больше // filenames load (местоположение, null, профиль); } else {for (string name: getSearchNames ()) {загрузка (местоположение, имя, профиль); }}} this.prococedProfiles.add (профиль); } addConfigurationProperties (this.propertiesloader.getPropertySources ()); } private set <frovise> initializeactiveprofiles () {if (! this.environment.containsproperty (active_profiles_property) &&! this.environment.containsproperty (include_profiles_property)) {return collection.emptyset (); } // Любые ранее существовавшие активные профили, установленные с помощью источников свойств (например, системы //) имеют приоритет по сравнению с теми, которые добавлены в файлы конфигурации. Springprofiles Springprofiles = bindspringprofiles (this.environment.getPropertySources ()); SET <FORLITE> activeProfiles = new LinkedHashset <friple> (SpringProfiles.getActiveProfiles ()); ActiveProfiles.addall (SpringProfiles.getIncludeProfiles ()); MayBeActivateProfiles (ActiveProfiles); вернуть ActiveProfiles; } /*** Возвращает активные профили, которые еще не обработались. Если профиль включен * с помощью {@link #active_profiles_property} и * {@link conifinevelyenvironment #ardatactprofile (string)}, он должен быть * отфильтрован, чтобы {@link #active_profiles_property} имело предложение. * <p> * конкретно, если профиль «облака» включен через среду, потребуется * меньше приоритета, который любой профиль устанавливает через {@link #Active_profiles_property}. * @param initiveActiveProfiles Профили, которые были включены через * {@link #Active_profiles_property} * @return. Непомозванные активные профили из среды, чтобы включить */ private <prifile> getUnprocavestactaintActiveProfiles (SET <FOLINTE> initiveActiveProfiles) {Profile> uppustActivePactilePales = new ARRAMELILESLILEST <PLOIN> ();); для (String profilename: this.environment.getactiveprofiles ()) {профиль профиля = новый профиль (Profilename); if (! initiveActiveprofiles.contains (profile)) {inporcessectivePactyProfiles.Add (профиль); }} // Обратить их в ответ, чтобы порядок был таким же, как и из getProfilesForValue () // (последний выигрывает, когда свойства в конечном итоге разрешены). вернуть необработанные антактные профили; } private void загрузка (расположение строки, имя строки, профиль профиля) {String Group = "profile =" + (profile == null? "": profile); if (! stringutils.hastext (name)) {// Попробуйте загружать непосредственно с Location LoadIntogroup (группа, местоположение, профиль); } else {// Поиск файла с данным именем для (string ext: this.propertiesloader.getallfileextensions ()) {if (profile! = null) {// Попробуйте специфический для профиля файл LoadIntogroup (группа, местоположение + name + "-" + profile + ".". ext, null); для (профиль обработка profile: this.procavedProfiles) {if (processedProfile! = null) {loadIntogroup (группа, местоположение + name + "-" + procepledprofile + "." + ext, profile); }} // Иногда люди ставят "Spring.profiles: dev" в // application-dev.yml (gh-340). Возможно, мы должны попытаться ошибиться // в этом, но мы можем быть добрыми и загрузить его в любом случае. LoadIntogroup (группа, местоположение + name + "-" + profile + "." + ext, profile); } // Также попробуйте раздел, специфичный для профиля (если есть) обычного файла loadintogroup (группа, местоположение + name + "." + Ext, profile); }}} private propertysource <?> loadintogroup (идентификатор строки, расположение строки, профиль профиля) {try {return doloadintogroup (идентификатор, местоположение, профиль); } catch (Exception ex) {бросить новое allosalstateException («Не удалось загрузить источник свойства из местоположения» « + местоположение +» ', ex); }} Private PropertySource <?> DoloadIntogroup (String Identifier, STRING LOCE, профиль профиля) Throws 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: [" + идентификатор + "]"; PropertySource = this.propertiesloader.load (ресурс, группа, имя, (профиль == null? null: profile.getName ())); if (PropertySource! = null) {msg.append ("загружен"); Руководитель профилепроперси (свойства); } else {msg.append ("Spected (yate)"); }} else {msg.append ("spected"); } msg.append ("config file"); msg.append (getResourceDescription (местоположение, ресурс)); if (profile! = null) {msg.append ("для профиля") .append (профиль); } if (resource == null ||! resource.exists ()) {msg.append ("ресурс не найден"); this.logger.trace (msg); } else {this.logger.debug (msg); } return PropertySource; } private String getResourceDescription (строка местоположение, ресурс ресурса) {string resourceScription = "'" + location + "'"; if (resource! = null) {try {resourceDescription = string.format ("'%s' (%s)", resource.geturi (). toasciistring (), location); } catch (ioException ex) {// Использовать местоположение в качестве описания}} return rescredescription; } private void handlepropRoperties (свойство antry <?> PropertySource) {SpringProfiles SpringProfiles = bindspringProfiles (PropertySource); MayBeActivateProfiles (SpringProfiles.getActiveProfiles ()); addProfiles (springprofiles.getincludeprofiles ()); } private SpringProfiles BindsPringProfiles (PropertySource <?> PropertySource) {mitablePropertySources PropertySources = new MitablePropertySources (); PropertySources.AddFirst (PropertySource); return BindspringProfiles (PropertySources); } private Springprofiles BindspringProfiles (свойства свойства недвижимости) {Springprofiles Springprofiles = new Springprofiles (); DelexedDatabinder DataBinder = новый REXOLDATABINDER (SpringProfiles, "Spring.Profiles"); DataBinder.Bind (новые свойства PropertySourcesPropertyValues (PropertySources, false)); SpringProfiles.Setactive (ResolvePlace Holders (SpringProfiles.getActive ())); SpringProfiles.SetInclude (ResolvePlaceholders (SpringProfiles.getInclude ())); вернуть Springprofiles; } частный список <string> ResiplePlaceholders (list <string> values) {list <string> resolved = new ArrayList <string> (); for (строковое значение: values) {Resolved.Add (this.Environment.ResolvePlaceholders (значение)); } return разрешил; } private void maybeActivateProfiles (set <friod> Profiles) {if (this.ActivateProfiles) {if (! Profiles.isempty ()) {this.logger.debug («Профили уже активированы,« + профили + "'не будут применены"); } возвращаться; } if (! Profiles.isempty ()) {addProfiles (Profiles); this.logger.debug ("активированные профили" + stringutils.collectiontocommadelimitedstring (профили)); this.activatedprofiles = true; Удалить UnprocatedDefaultProfiles (); }} private void removeUnprocavesedDefaultProfiles () {for (iterator <friod> iterator = this.profiles.iterat или (); iterator .hasnext ();) {if (iterator.next (). isdefaulprofile ()) {iterator.remove (); }}} private void AddProfiles (set <friod> Profiles) {for (профиль профиля: профили) {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 (Среда настраиваемой среды профиля, профиль) {set <string> profiles = new LinkedHashset <string> (); Environment.getactiveProfiles (); // Убедитесь, что они инициализированы //, но это должно перейти первым (последнее победы в профилях ключа Key). 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: asresolvedset (this.environment.getproperty (config_location_property), null) {if (! 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 asresolvedset (this.environment.getProperty (config_name_property), null); } вернуть asresolvedset (configfileApplicationlistener.this.names, default_names); } private set <string> asresolvedset (string value, string raffbur) {list <string> list = arrays.aslist (stringutils.trimarrayelements (stringUtils.commadelimitedlisttoStringarray (значение! = null? this.environment.sroplaceholders (значение): Fallback))); Collections.reverse (список); вернуть новый LinkedHashset <String> (список); } private void addConfigurationProperties (MitablePropertySources Sources) {List <PropertySource <? >> переупорядоченные положения = new ArrayList <PropertySource <? >> (); для (свойства property <?> пункт: источники) {ReorderedSources.Add (item); } addConfigurationProperties (New ConfigurationPropertySources (повторно зарядали);} private void AddConfigurationProperties (configurationPropertySourcesourationsources) {intablePropertySources receorceources = это. Extanicsources.addbefore (Default_properties, Configurationsources); «Имя не должно быть нулевым»); this.name.hashcode (); Свойства, как они загружаются, можно было переехать. Super (Application_configuration_property_source_name, источники); Source) .getPropertyNames ()); } public static void finishing и (Propertysource Encementof enerumerableCompositePropertySource) {enumerableCompositepropertysource composite = (enumerablecompositepropertysource). } else {stocdaysources.addafter (name, propertysource); Springprofiles {Private List <string> Active = New ArrayList <string>; this.include = включить; profilenames) {profiles.add (новый профиль (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); 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. * * Лицензирован по лицензии 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事件,用于重新绑定日志级别
二、刷新范围
我们考虑如下场景,当我们变更数据库配置后,通过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. * * Лицензирован по лицензии 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)); 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. * * Лицензирован по лицензии 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); 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 instance of 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()); 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; } вернуть 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 instance of 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 instance of 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's destroy method @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 method 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配置刷新原理解析,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!