Nous savons que dans SpringCloud, lorsque la configuration change, nous pouvons obtenir la dernière configuration sans démarrer le service en visitant http: // xxxx / rafraîchir. Alors, comment ça fait? Comment pouvons-nous obtenir le dernier objet de source de données après avoir modifié la configuration de la base de données et actualiser? Voyons comment Springcloud le fait.
1. Changements environnementaux
1.1. À propos de Contextrefresher
Lorsque nous accéderons / actualions, il sera traité par la classe RafhendPoint. Regardons le code source:
/ * * Copyright 2013-2014 L'auteur ou les auteurs originaux. * * Licencié sous la licence Apache, version 2.0 (la "licence"); * Vous ne pouvez pas utiliser ce fichier sauf conforme à la licence. * Vous pouvez obtenir une copie de la licence à * * http://www.apache.org/licenses/license-2.0 * * sauf si la loi applicable ou convenu par écrit, le logiciel * distribué sous la licence est distribué sur une base "en tant que", * sans garantie ou conditions de toute nature, exprimée ou impliquée. * Voir la licence pour la langue spécifique régissant les autorisations et les * limitations sous la licence. * / package org.springframework.cloud.endpoint; import java.util.arrays; import java.util.collection; import java.util.set; import org.springframework.boot.context.properties.configurationproperties; org.springframework.cloud.context.refresh.contextrefresher; import org.springframework.jmx.export.annotation.managedOperation; import org.springframework.jmx.export.annotation.managedResource; / ** * @Author Dave Syer * @Author Venil Noronha; * / @ ConfigurationProperties (prefix = "endpoint.refresh", ignoreunknownFields = false) @managedResourcePublic class RefreshendPoint étend AbstrAndPoint <collection <string >> {private Contextrefresher ContextreFresher; Public RefreshendPoint (ContextreFresher ContextreFresher) {Super ("Refresh"); this.contextreFresher = ContextreFresher; } @ManagedOperation Public String [] Refresh () {set <string> keys = contextrefresher.refresh (); return keys.toArray (new String [keys.size ()]); } @Override public Collection <string> invoke () {return arrays.aslist (refresh ()); }}Grâce au code source, nous avons appris que lors de l'accès au point de terminaison de rafraîchissement, la méthode de rafraîchissement de Contextrefresher est réellement exécutée. Ensuite, nous continuons à tracer le code source et à trouver sa méthode de rafraîchissement:
Set synchronisé public <string> refresh () {map <string, object> avant = extract (this.context.getenvironment (). getPropertySources ()); addConfigFileStoenvironment (); Set <string> keys = changes (avant, extraire (this.context.getEnvironment (). GetPropertySources ())). KeySet (); this.Context.publisheVent (new SentimeChangeEvent (contexte, clés)); this.scope.refreshall (); clés de retour; }Nous pouvons voir que la méthode de rafraîchissement fait ce qui suit:
1) Obtenez toutes les propriétés avant de rafraîchir
2) Appelez AddConfigfileStoenvironment Méthode pour obtenir la dernière configuration
3) Appelez la méthode des modifications pour mettre à jour les informations de configuration
4) Publier un événement EnvironmentChangeenvent
5) Appelez la méthode de rafraîchissement de rafraîchissement pour actualiser la plage
Concentrons-nous sur 2, 3 et 4 étapes
1.2. Méthode AddConfigfileStoenvironment
Voyons d'abord comment cette méthode est mise en œuvre:
/ * pour les tests * / configurableApplicationContext addConfigFileStoenvironment () {configurableApplicationContext capture = null; Try {StandorEnvironment Environment = Copyenvironment (this.context.getEnvironment ()); SpringApplicationBuilder Builder = new SpringApplicationBuilder (vide.class) .bannermode (mode.off) .web (false) .environment (environnement); // uniquement les auditeurs qui affectent l'environnement (par exemple, excluant l'écouteur de logging // car il a des effets secondaires) builder.application () .setListeners (arrays.aslist (new bootstrapApplicationListener (), new ConfigFileApplicationListener ())); capture = builder.run (); if (Environment.getPropertySources (). contient (refresh_args_property_source)) {Environment.getPropertySources (). retire (refresh_args_property_source); } Mutablepropertysources cible = this.context.getenvironment () .getPropertySources (); String TargetName = null; for (propriétéSource <?> Source: Environment.getPropertySources ()) {String name = source.getName (); if (cible.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 était null, donc nous sommes au début de la liste cible.addFirst (source); TargetName = name; }}}}}} Enfin {configurableApplicationContext clarableable = capture; while (clôtuable! = null) {try {closable.close (); } catch (exception e) {// ignore; } if (closable.getParent () instance de configurableApplicationContext) {closable = (configurableApplicationContext) clarable.getParent (); } else {break; }}} return capture; }1) Cette méthode copie d'abord l'environnement actuel
2) Construisez un programme de démarrage Springboot simple via SpringApplicationBuilder et démarrez
builder.application (). setListeners (arrays.aslist (new bootstrapApplicationListener (), new ConfigFileApplicationListener ()));
Deux auditeurs seront ajoutés ici: bootstrapApplicationListener et configFileApplicationListener. Grâce à l'apprentissage précédent, nous savons que BootstrapApplicationListener est le principal auditeur du programme Bootstrap, et ConfigFileApplicationListener est également une classe très importante:
/ * * Copyright 2012-2017 L'auteur ou les auteurs originaux. * * Licencié sous la licence Apache, version 2.0 (la "licence"); * Vous ne pouvez pas utiliser ce fichier sauf conforme à la licence. * Vous pouvez obtenir une copie de la licence à * * http://www.apache.org/licenses/license-2.0 * * sauf si la loi applicable ou convenu par écrit, le logiciel * distribué sous la licence est distribué sur une base "en tant que", * sans garantie ou conditions de toute nature, exprimée ou impliquée. * Voir la licence pour la langue spécifique régissant les autorisations et les * limitations sous la licence. * / package org.springframework.boot.context.config; importer java.io.ioexception; import java.util.arraylist; import java.util.arrays; import java.util.collection; import 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.factory.config.beanfactorypostProcessor; import org.springframework.beans.factory.config.configurableListableBeanfactory; import org.springframework.boot.springapplication; import; org.springframework.boot.bind.propertiesConfigurationFactory; import org.springframework.boot.bind.propertysourcespropertyvalues; import org.springframework.boot.bind.relatedDatabinder; import org.springframework.boot.bind.RelatedPropertyRover; Importoter; org.springframework.boot.context.event.applicationenvironmentPrepareDevent; import org.springframework.boot.context.event.applicationPrepareDevent; import org.springframework.boot.env.enumerablepositeproperpertysource; org.springframework.boot.env.environmentPostProcessor; import org.springframework.boot.env.propertysourcesloader; import org.springframework.context.applicationevent; importer; org.springframework.context.configurableApplicationContext; import org.springframework.context.annotation.configurationclasspostprocessor; import org.springframework.context.event.smartApplicationListener; import org.springframework.Core.Rectioned; import; org.springframework.core.annotation.annotationAwareOrderComparator; import org.springframework.core.convert.conversionService; import org.springframe org.springframework.core.env.enumeablepropertysource; import org.springframework.core.env.mutablepropertysources; import org.springframework.core.env.propertysources; org.springframework.core.io.defaultreSourceLoader; import org.springframework.core.io.resourceloader; import org.springframework.core.io.support.springfactoriesloller; org.springframework.util.resourceutils; import org.springframework.util.stringutils; import org.springframework.validation.bindexception; / ** * {@Link EnvironmentPostProcessor} qui configure l'environnement de contexte par chargement * Propriétés à partir de locations de fichiers bien connues. Par défaut, les propriétés seront chargées à partir des fichiers * 'application.properties' et / ou 'application.yml' dans les emplacements suivants: * <ul> * <li> classpath: </li> * <li> Fichier: ./ </li> * <li> ClassPath: config / </li> <li> Fichier: ./ Config /: </li> * <li> Fichier :. Les noms peuvent être spécifiés à l'aide de * {@Link #SetSearchLocations (String)} et {@Link #SetSearchNames (String)}. * <p> * Les fichiers supplémentaires seront également chargés en fonction des profils actifs. Par exemple, si un profil 'web' * est actif 'application-web.properties' et 'application-web.yml' sera * considéré. * <p> * La propriété 'Spring.config.name' peut être utilisée pour spécifier un nom alternatif à charger * et la propriété 'Spring.config.location' peut être utilisée pour spécifier des emplacements de recherche alternatifs * ou des fichiers spécifiques. * <p> * Les propriétés de configuration sont également liées au {@Link SpringApplication}. Cela permet * de définir dynamiquement les {@link springApplication}, comme les sources * ("spring.main.sources" - une liste CSV) l'indicateur pour indiquer un environnement Web * ("Spring.main.web_environment = true") ou le drapeau pour désactiver la bannière * ("Spring.main.show_banner = false"). * * @Author Dave Syer * @author Phillip Webb * @author Stephane Nicoll * @author Andy Wilkinson * @Author Eddú Meléndez * / Classe publique ConfigFileApplicationListener implémente EnvironmentPostProcessor, SmartApplicationSner, ordonné {private static string string default_properties = "DefaultProperties"; // Remarque L'ordre est du moins au moins au plus spécifique (le dernier gagne) une chaîne finale statique privée default_search_locations = "classpath: /, classpath: / config /, fichier: ./, fichier: ./ config /"; chaîne finale statique privée default_names = "application"; / ** * Le nom de la propriété "Profils actifs". * / public static final string active_profiles_property = "spring.profiles.active"; / ** * Le nom de la propriété "inclut les profils". * / chaîne finale statique publique include_profiles_property = "printemps.profiles.include"; / ** * Le nom de la propriété "Nom de configuration". * / public static final string config_name_property = "spring.config.name"; / ** * Le nom de la propriété "Config Location". * / public static final string config_location_property = "spring.config.location"; / ** * L'ordre par défaut pour le processeur. * / public static final int Default_Order = ordonné.Highest_precence + 10; / ** * Nom de la configuration de l'application {@Link PropertySource}. * / public static final String application_configuration_property_source_name = "ApplicationConfigurationProperties"; Private Final DeferredLog Logger = new DeferredLog (); Locations de recherche de chaînes privées; noms de chaînes privées; private int order = default_order; ConversionService final privé ConversionService = new defaultConversionService (); @Override public boolean supportSeventType (classe <? Étend applicationEvent> EventType) {return applicationNironmentPrepareEvent.class.isassignableFrom (EventType) || ApplicationPrepareDevent.class.issignableFrom (EventType); } @Override public boolean supportSSourceType (class <?> Aclass) {return true; } @Override public void onApplicationEvent (événement applicationEvent) {if (event instanceof ApplicationEnvironmentPrepareDevent) {onApplicationNenvironmentPrepareDevent ((applicationEnvironmentPrepareEvent) Event); } if (event instanceof applicationPrepareDevent) {onApplicationPrepareDevent (événement); }} private void onApplicationNenvironmentPrepareDevent (applicationEnvironmentPrepareDeEvent Event) {list <EnvironmentPostProcessor> PostProcessors = LoadPostProcessors (); postprocessors.add (this); AnnotationAwareOrderComparator.Sort (postprocesseurs); pour (EnvironmentPostProcessor Postprocessor: PostProcessors) {postprocessor.postProcessEnvironment (event.getEnvironment (), event.getspringApplication ()); }} List <NIVERTIESPOSTPROCESSOR> LOADPOSTPROCESSORSELS () {return SpringFactoriesLoader.LoadFactories (EnvironmentPostProcessor.Class, getClass (). GetClassLoader ()); } @Override public void PostProcessEnvironment (Environnement ConfigurableEnvironment, application SpringApplication) {addPropertySources (Environment, application.getResourceLoader ()); configureignoreBeanInfo (environnement); BindTospringApplication (environnement, application); } private void configureignoreBeanInfo (Environnement ConfigurableenVironment) {if (System.getProperty (CacheDintOrsOsResults.Ignore_BeanInfo_Property_Name) == NULL) {RelaxEdPropertyResolver Resolver = new SellmEdPropertyResolver (Environment, "Spring.BeanInfo"); "); Boolean ignore = résolver.getProperty ("ignorer", boolean.class, boolean.true); System.SetProperty (CacheDintOsrospectionResults.ignore_Beaninfo_Property_name, ignore.toString ()); }} private void onApplicationPrepareDevent (événement applicationEvent) {this.logger.replayto (configFileApplicationListener.class); AddPostProcessors (((applicationPrepareDevent) Event) .getApplicationContext ()); } / ** * Ajouter des sources de propriétés de fichiers de configuration à l'environnement spécifié. * @Param Environment l'environnement pour ajouter une source à * @Param ResourceLoader le chargeur de ressources * @see #AddPostProcessors (ConfigurableApplicationContext) * / Protected void addPropertySources (configurableenvironment, ResourceLoader ResourceLoader); Nouveau chargeur (Environment, ResourceLoader) .load (); } / ** * lier l'environnement au {@link springApplication}. * @param environnement l'environnement pour lier * @param application l'application pour se lier à * / protégée void bindtospringApplication (Environnement Configurableenvironment, application SpringApplication) {PropertiesConfigurationFactory <SpringApplication> Binder = new PropertiesConfigurationFactory <SpringApplication> (application); Binder.SetTtargetName ("printemps.main"); Binder.setConversionService (this.ConversionService); Binder.SetPropertySources (Environment.GetPropertySources ()); essayez {binder.bindProperStiStotArget (); } catch (bindException ex) {throw new illégalStateException ("ne peut pas se lier à SpringApplication", ex); }} / ** * Ajouter des post-processeurs appropriés à la post-configuration des sources de propriété. * @Param Context Le contexte pour configurer * / protégé void addPostProcessors (ConfigurableApplicationContext context) {context.AddBeAnfactoryPostProcessor (new PropertySourceDorsingPostProcessor (context)); } public void setOrder (int order) {this.order = ordre; } @Override public int getOrder () {return this.order; } / ** * Définissez les emplacements de recherche qui seront considérés comme une liste séparée par des virgules. Chaque * emplacement de recherche doit être un chemin de répertoire (se terminant par "/") et il sera préfixé * par les noms de fichiers construits à partir des noms de recherche {@Link #SetSearchNames (String)} et * Profils (le cas échéant) plus les extensions de fichiers prises en charge par les chargeurs de propriétés. * Les emplacements sont pris en compte dans l'ordre spécifié, les éléments ultérieurs prenant la priorité * (comme une fusion de carte). * @Param Locations Les emplacements de recherche * / public void setSearchLocations (emplacements de chaîne) {assert.hasLength (emplacements, "les emplacements ne doivent pas être vides"); this.searchLocations = emplacements; } / ** * Définit les noms des fichiers qui doivent être chargés (exclusion de l'extension de fichier) en tant que liste * séparée par des virgules. * @param noms les noms à charger * / public void setSearchNames (noms de chaîne) {assert.hasLength (noms, "les noms ne doivent pas être vides"); this.Names = noms; } / ** * {@link beanfactoryPostProcessor} Pour réorganiser nos sources de propriété sous n'importe quel * {@code @propertysource} ajouté par le {@Link ConfigurationClassPostProcessor}. * / Classe privée PropertySourceOrderingPostProcessor implémente beanfactoryPostProcessor, ordonné {private configurableApplicationContex context; PropriétéSourceOrderingPostProcessor (ConfigurableApplicationContext context) {this.context = context; } @Override public int getOrder () {return ordonné.Highest_precence; } @Override public void PostProcessBeanFactory (configurableListableBeanFactory beanfactory) lève BeanSexception {reordersources (this.context.getenvironment ()); } private void reordersources (configurableenvironment environnement) {ConfigurationPropertySources .FinishAndReLocate (Environment.GetPropertySources ()); PropertySource <?> DefaultPropertySources = Environment.GetPropertySources () .Remove (Default_Properties); if (defaultProperties! = null) {Environment.getPropertySources (). AddLast (defaultProperties); }}} / ** * Charge les sources de propriété candidate et configure les profils actifs. * / Private Class chargeer {private final logger = configFileApplicationListener.this.logger; environnement privé configurable environment; ResourceLoader final privé ResourceLoader; propriété privésourcesloader Propertiesloader; Profils <pilaté> de file d'attente privée; Liste privée <pilé> ProcedProfiles; Private Boolean ActivatedProfiles; Loader (Environnement ConfigurableNenvironment, ResourceLoader ResourceLoader) {this.environment = environnement; this.resourceLoader = ResourceLoader == null? new DefaultreSourceLoader (): ResourceLoader; } public void charge () {this.propertiesloader = new PropertySourcesLoader (); this.activatedProfiles = false; this.profiles = collections.aslifoqueue (new LinkedList <pilaf> ()); this.ProcesdProfiles = new LinkedList <pilaf> (); // Profils actifs pré-existants définis via Environment.SetActiveProfiles () // sont des profils supplémentaires et les fichiers de configuration sont autorisés à ajouter plus si // ils le souhaitent, alors n'appelez pas addActiveProfiles () ici. Set <pila> InitialActiveProfiles = initialiseActiveProfiles (); this.profiles.addall (getUnprocessEdActiveProfiles (initialActiveProfiles)); if (this.profiles.isempty ()) {for (String defaultProfileName: this.environment.getDefaultProfiles ()) {Profil DefaultProfile = new Profil (DefaultProfileName, true); if (! this.profiles.contains (defaultProfile)) {this.profiles.add (defaultProfile); }}} // Le profil par défaut à ces fins est représenté comme null. Nous l'ajoutons // en dernier pour qu'il soit d'abord hors de la file d'attente (les profils actifs seront alors // remplaceront tous les paramètres dans les valeurs par défaut lorsque la liste sera inversée plus tard). this.profiles.add (null); while (! this.profiles.isempty ()) {profil profil = this.profiles.poll (); for (String Location: getSearchLocations ()) {if (! location.endswith ("/")) {// emplacement est déjà un nom de fichier, alors ne recherchez pas plus // les noms de fichiers chargement (emplacement, null, profil); } else {for (String name: getSearchNames ()) {Load (emplacement, nom, profil); }}} this.procesdProfiles.add (profil); } addConfigurationProperties (this.propertiesloader.getPropertySources ()); } set privé <pila> initializeActiveProfiles () {if (! this.environment.containsproperty (active_profiles_property) &&! this.environment.contivainsproperty (include_profiles_property)) {return CollectionS.emptySet (); } // Tout profil actif pré-existant défini via des sources de propriétés (par exemple System // Properties), prévoyez celles ajoutées dans les fichiers de configuration. SpringProfiles SpringProfiles = bindspringProfiles (this.environment.getPropertySources ()); Set <pilaf> activeProfiles = new LinkedHashSet <pilaf> (springProfiles.getActiveProfiles ()); activeProfiles.Addall (SpringProfiles.getCluceProfiles ()); MayBeactivateProfiles (ActiveProfiles); retour activeProfiles; } / ** * Renvoyez les profils actifs qui n'ont pas encore été traités. Si un profil est * activé via les deux {@link #active_profiles_property} et * {@link configurableenvironment # addactiveProfile (string)}, il doit être * filtré de sorte que la valeur {@link #active_profiles_property} soit précède. * <p> * Concretely, si le profil "cloud" est activé via l'environnement, il faudra * moins de primaire que tout profil définit via le {@link #active_profiles_property}. * @param initialactiveProfiles les profils qui ont activé via * {@link #active_profiles_property} * @return les profils actifs non traités de l'environnement pour activer * / private list <pila> getUsUnprocessEdActiveProfiles = news <pilafred> initialActiveProfiles);); for (String ProfileName: this.environment.getActiveProfiles ()) {Profil Profil = new Profil (ProfileName); if (! InitialActiveProfiles.Contains (Profil)) {UNTOCCESSEDACTPROFILE.ADD (Profil); }} // inversez-les afin que l'ordre soit le même que de getProfilesForValue () // (le dernier gagne lorsque les propriétés sont finalement résolues) Collection.Reverse (UNCOCESSEDACTACTPORFILES); retour non traitedactiveProfiles; } Private void Load (emplacement de chaîne, nom de chaîne, profil de profil) {String Group = "Profil =" + (profil == null? "": Profil); if (! StringUtils.hastext (name)) {// Essayez de charger directement à partir de l'emplacement LoadIntOgroup (groupe, emplacement, profil); } else {// Recherchez un fichier avec le nom donné pour (String ext: this.propertiesLoader.getAllFileExtensions ()) {if (Profil! = NULL) {// Essayez le fichier spécifique du profil LoadIntOgroup (groupe, emplacement + nom + "-" + Profil + "." + ext, null); pour (Profile ProcessEdProfile: this.ProcesdProfiles) {if (ProcessEdProfile! = null) {LoadIntoGroup (groupe, emplacement + name + "-" + procedProfile + "." + ext, profil); }} // Parfois, les gens mettent "Spring.Profiles: Dev" dans // Application-Dev.yml (GH-340). On peut dire que nous devrions essayer et errer // à ce sujet, mais nous pouvons être gentils et le charger de toute façon. LoadIntoGroup (groupe, emplacement + nom + "-" + profil + "." + ext, profil); } // Essayez également la section spécifique au profil (le cas échéant) du fichier normal LoadIntoGroup (groupe, emplacement + nom + "." + Ext, profil); }}} propriété privésource <?> LoadIntoGroup (identifiant de chaîne, emplacement de chaîne, profil de profil) {try {return doloadintogroup (identificateur, emplacement, profil); } catch (exception ex) {lancez new illégalStateException ("Échec la source de la propriété de l'emplacement '" + emplacement + "'", ex); }} Private PropertySource <?> DoloAdIntoGroup (String Identifier, String Location, Profile Profile) lève IOException {Resource Resource = this.resourceLoader.getResource (emplacement); PropertySource <?> PropertySource = null; StringBuilder msg = new StringBuilder (); if (ressource! = null && ressource.exists ()) {string name = "applicationConfig: [" + location + "]"; String Group = "ApplicationConfig: [" + Identifier + "]"; propriétéSource = this.propertiesloader.load (ressource, groupe, nom, (profil == null? null: profil.getName ())); if (propriétéSource! = null) {msg.append ("chargé"); HandleProfileProperties (PropertySource); } else {msg.append ("Skipped (vide)"); }} else {msg.append ("sauté"); } msg.append ("Fichier de configuration"); MSG.APPEND (GetResourceDeScription (emplacement, ressource)); if (profil! = null) {msg.append ("pour profil") .append (profil); } if (ressource == null ||! Resource.exists ()) {msg.append ("Resource introuvable"); this.logger.trace (msg); } else {this.logger.debug (msg); } return PropertySource; } String privé getResourceDescription (emplacement de chaîne, ressource de ressource) {String ResourceDescription = "'" + emplacement + "'"; if (ressource! = null) {try {ResourceDescription = string.format ("'% s' (% s)", ressource.geturi (). toasciistring (), emplacement); } catch (ioException ex) {// Utilisez l'emplacement comme description}} return ResourceDedescription; } private void handleProfileProperties (PropertySource <?> PropertySource) {SpringProfiles springProfiles = bindspringProfiles (propriétéSource); MayBeActivateProfiles (SpringProfiles.getActiveProfiles ()); addProfiles (springProfiles.getcluceProfiles ()); } private SpringProfiles bindspringProfiles (PropertySource <?> PropertySource) {mutablepropertySources PropertySources = new MutablePropertySources (); PropertySources.AddFirst (PropertySource); retour bindspringProfiles (propriétéSources); } private SpringProfiles bindspringProfiles (PropertySources PropertySources) {SpringProfiles SpringProfiles = new SpringProfiles (); RelaxEdDatabinder databinder = new ReladedDatabinder (SpringProfiles, "Spring.profiles"); databinder.bind (new PropertySourcesPropertyValues (PropertySources, false)); SpringProfiles.SetActive (ResolvePlaceHolders (SpringProfiles.getActive ())); SpringProfiles.Setinclude (ResolvePlaceHolders (SpringProfiles.getinclude ())); Retour SpringProfiles; } private list <string> résolvePlaceHolders (list <string> valeurs) {list <string> résolu = new ArrayList <string> (); for (String Value: Values) {résolved.add (this.environment.ResolElPlaceHolders (Value)); } retour résolu; } private void MayBeActivateProfiles (set <pila> Profils) {if (this.activateProfiles) {if (! Profils.isempty ()) {this.logger.debug ("Profils déjà activés, '" + Profils + "' ne sera pas appliqué"); } retour; } if (! Profils.iSempty ()) {addProfiles (Profils); this.logger.debug ("Profils activés" + stringUtils.collectionToComAdElimitedString (Profils)); this.activedProfiles = true; Supprime leprocessedDefaultProfiles (); }} private void removeUnprocessEdDefaultProfiles () {for (iterator <pila> iterator = this.profiles.iterat ou (); iterator .hasnext ();) {if (iterator.next (). isDefaultProfile ()) {iterator.remove (); }}} private void addProfiles (set <pila> Profils) {for (profil Profil: Profils) {this.profiles.add (profil); if (! EnvironmentHasActiveProfile (Profil.GetName ())) {// Si elle est déjà acceptée, nous supposons que l'ordre a été défini // informatique prendProfile (this.environment, profil); }}} private boolean EnvironmentHasActiveProfile (String Profile) {for (String ActiveProfile: this.environment.getActiveProfiles ()) {if (activeProfile.equals (profil)) {return true; }} return false; } private void prendProfile (Environnement ConfigurableNenvironment, profil de profil) {set <string> Profils = new LinkedHashSet <string> (); Environment.getActiveProfiles (); // Assurez-vous qu'ils sont initialisés // mais celui-ci devrait passer en premier (dernière victoire dans un CLASH CLAY CLASH) Profils.add (Profil.GetName ()); Profils.Addall (arrays.aslist (Environment.GetActiveProfiles ())); Environment.SetActiveProfiles (Profils.toArray (new String [Profils.Size ()])); } set privé <string> getSearchLocations () {set <string> locations = new LinkedHashSet <string> (); // Les paramètres configurés par l'utilisateur ont la priorité, nous les faisons donc d'abord if (this.environment.containsproperty (config_location_property)) {for (String Path: asresolvedset (this.environment.getproperty (config_location_property), null)) {if (path.contains; if (! ResourceUtils.isurl (path)) {path = ressourceutils.file_url_prefix + path; }} locations.add (path); }} locations.addall (AsresolvedSet (configFileApplicationListener.This.SearchLocations, default_search_locations)); emplacements de retour; } set privé <string> getSearchNames () {if (this.environment.containsproperty (config_name_property)) {return asresolvedSet (this.environment.getProperty (config_name_property), null); } return asResolvedSet (configFileApplicationListener.this.names, default_name); } set privé <string> asresolvedSet (String Value, String Fallback) {list <string> list = arrays.aslist (stringUtils.trimArrayElements (stringUtils.commadelimitedListToStringArray (valeur! = null? this.environment.ResolvePlaceHolders (value): Fallback))); Collection.Reverse (liste); return new LinkedHashSet <string> (list); } private void addConfigurationProperties (MutablePropertySources Sources) {list <propriétéSource <? >> réorganiséSources = new ArrayList <PropertySource <? >> (); pour (propriétéSource <?> item: Sources) {reorderedSources.add (item); } addConfigurationProperties (nouvelle configurationPropertySources (réorganisé);} private void addConfigurationProperties (ConfigurationPropertySources Configurations existingsources.addBefore (Default_properties, configurations Courtises);} else {existant ne pas être nul "); this.name = name; this.defaultProfile = defaultProfile;} public String getName () {return this.name;} public boolean isdefaultprofile () {return this.defaultprofile;} @Override public string toString () {return this.name;} @Override public int hashcode () {retour this.name.hashcode ();} @Override public boolean equals (objet obj) {if (obj == this) {return true;} if (obj == null || PropertySource} s comme ils sont chargés de la déménagement une fois que les classes de configuration ont été traitées. super (application_configuration_property_source_name, sources); Source) .getPropertyNames ()));}} this.Names = names.toArray (new String [noms de la chaîne) {pour (PropertySource <? } public static void finalAndReLocate (mutablepropertysources PropertySources) {String name = application_configuration_property_source_name; (PropertySource Instance of EnumerableCositEpropertySource) {enUmerableCositEpropertySource Composite = (enUmerableCositEpropertySource) PropertySource; {PropertySources.Addafter (nom, propriété);}} propriétésources.remove (application_configuration_property_source_name); {Liste privée <String> active = New ArrayList <string> (); } Set <pilaf> getActiveProfiles () {return asprofilesEt (this.active);} set <pila profiles.add(new Profile(profileName)); } Collections.reverse(profiles); return new LinkedHashSet<Profile>(profiles); } }}根据javadoc注释的说明,这个类会从指定的位置加载application.properties或者application.yml并将它们的属性读到Envrionment当中,其中这几个方法大家关注下:
@Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); }}当springboot程序启动时一定会触发该事件监听,如果当前是ApplicationEnvironmentPreparedEvent事件就会调用onApplicationEnvironmentPreparedEvent方法,最终该方法会执行:
@Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader()); configureIgnoreBeanInfo(environment); bindToSpringApplication(environment, application); }其中bindToSpringApplication方法为:
/** * Bind the environment to the {@link SpringApplication}. * @param environment the environment to bind * @param application the application to bind to */ protected void bindToSpringApplication(ConfigurableEnvironment environment, SpringApplication application) { PropertiesConfigurationFactory<SpringApplication> binder = new PropertiesConfigurationFactory<SpringApplication>( application); binder.setTargetName("spring.main"); binder.setConversionService(this.conversionService); binder.setPropertySources(environment.getPropertySources()); try { binder.bindPropertiesToTarget(); } catch (BindException ex) { throw new IllegalStateException("Cannot bind to SpringApplication", ex); }}很明显该方法是将Environment绑定到对应SpringApplication上,通过这个类就可以获取到我们更改过后的配置了
1.3、changes方法
private Map<String, Object> changes(Map<String, Object> before, Map<String, Object> after) { Map<String, Object> result = new HashMap<String, Object>(); for (String key : before.keySet()) { if (!after.containsKey(key)) { result.put(key, null); } else if (!equal(before.get(key), after.get(key))) { result.put(key, after.get(key)); } } for (String key : after.keySet()) { if (!before.containsKey(key)) { result.put(key, after.get(key)); } } return result; }changes方法其实就是处理配置变更信息的,分以下几种情况:
1)如果刷新过后配置文件新增配置就添加到Map里
2) 如果有配置变更就添加变更后的配置
3) 如果删除了原先的配置,就把原先的key对应的值设置为null
至此经过changes方法后,上下文环境已经拥有最新的配置了。
1.4、发布事件
当上述步骤都执行完毕后,紧接着会发布EnvrionmentChangeEvent事件,可是这个事件谁来监听呢?在这里我贴出官网的一段描述:
应用程序将收听EnvironmentChangeEvent,并以几种标准方式进行更改(用户可以以常规方式添加ApplicationListeners附加ApplicationListeners)。当观察到EnvironmentChangeEvent时,它将有一个已更改的键值列表,应用程序将使用以下内容:
1.重新绑定上下文中的任何@ConfigurationProperties bean
2.为logging.level.*中的任何属性设置记录器级别
根据官网描述我们知道将变更一下操作行为@ConfigurationProperties的bean与更改日志level,那么如何做到的呢?结合官网文档我们来关注以下两个类:
ConfigurationPropertiesRebinder:
/* * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * Vous ne pouvez pas utiliser ce fichier sauf conforme à la licence. * 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. * Voir la licence pour la langue spécifique régissant les autorisations et les * limitations sous la licence. */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; application application privée 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. * * Licensed under the Apache License, Version 2.0 (the "License"); * Vous ne pouvez pas utiliser ce fichier sauf conforme à la licence. * 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. * Voir la licence pour la langue spécifique régissant les autorisations et les * limitations sous la licence. */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()); environnement privé; @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void onApplicationEvent(EnvironmentChangeEvent event) { if (this.environment == null) { return; } LoggingSystem system = LoggingSystem.get(LoggingSystem.class.getClassLoader()); setLogLevels(system, this.environment); } protected void setLogLevels(LoggingSystem system, Environment environment) { Map<String, Object> levels = new RelaxedPropertyResolver(environment) .getSubProperties("logging.level."); for (Entry<String, Object> entry : levels.entrySet()) { setLogLevel(system, environment, entry.getKey(), entry.getValue().toString()); } } private void setLogLevel(LoggingSystem system, Environment environment, String name, String level) { try { if (name.equalsIgnoreCase("root")) { name = null; } level = environment.resolvePlaceholders(level); system.setLogLevel(name, LogLevel.valueOf(level.toUpperCase())); } catch (RuntimeException ex) { this.logger.error("Cannot set level: " + level + " for '" + name + "'"); }}}该类也是监听了ChangeEnvrionmentEvent事件,用于重新绑定日志级别
二、刷新范围
我们考虑如下场景,当我们变更数据库配置后,通过refresh刷新,虽然能获取到最新的配置,可是我们的DataSource对象早就被初始化好了,换句话说即便配置刷新了我们拿到的依然是配置刷新前的对象。怎么解决这个问题呢?
我们继续看ContextRefresher的refresh方法,最后有一处代码值得我们关注一下this.scope.refreshAll(),此处scope对象是RefreshScope类型,那么这个类有什么作用呢?那么我们先要关注一下@RefreshScope注解。在这里我在贴出官网一段解释:
当配置更改时,标有@RefreshScope的Spring @Bean将得到特殊处理。这解决了状态bean在初始化时只注入配置的问题。例如,如果通过Environment更改数据库URL时DataSource有开放连接,那么我们可能希望这些连接的持有人能够完成他们正在做的工作。然后下一次有人从游泳池借用一个连接,他得到一个新的URL
刷新范围bean是在使用时初始化的懒惰代理(即当调用一个方法时),并且作用域作为初始值的缓存。要强制bean重新初始化下一个方法调用,您只需要使其缓存条目无效。RefreshScope是上下文中的一个bean,它有一个公共方法refreshAll()来清除目标缓存中的范围内的所有bean。还有一个refresh(String)方法可以按名称刷新单个bean。此功能在/refresh端点(通过HTTP或JMX)中公开。
这里我贴出@RefreshScope源码:
/* * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * Vous ne pouvez pas utiliser ce fichier sauf conforme à la licence. * 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. * Voir la licence pour la langue spécifique régissant les autorisations et les * limitations sous la licence. */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. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */package org.springframework.cloud.context.scope.refresh;import java.io.Serializable;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanDefinition;import org.springframework.beans.factory.support.BeanDefinitionRegistry;import org.springframework.cloud.context.scope.GenericScope;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.context.event.ContextRefreshedEvent;import org.springframework.context.event.EventListener;import org.springframework.core.Ordered;import org.springframework.jmx.export.annotation.ManagedOperation;import org.springframework.jmx.export.annotation.ManagedResource;/** * <p> * A Scope implementation that allows for beans to be refreshed dynamically at runtime * (see {@link #refresh(String)} and {@link #refreshAll()}). If a bean is refreshed then * the next time the bean is accessed (ie a method is executed) a new instance is * created. All lifecycle methods are applied to the bean instances, so any destruction * callbacks that were registered in the bean factory are called when it is refreshed, and * then the initialization callbacks are invoked as normal when the new instance is * created. A new bean instance is created from the original bean definition, so any * externalized content (property placeholders or expressions in string literals) is * re-evaluated when it is created. * </p> * <p> * Note that all beans in this scope are <em>only</em> initialized when first accessed, so * the scope forces lazy initialization semantics. The implementation involves creating a * proxy for every bean in the scope, so there is a flag * {@link #setProxyTargetClass(boolean) proxyTargetClass} which controls the proxy * creation, defaulting to JDK dynamic proxies and therefore only exposing the interfaces * implemented by a bean. If callers need access to other methods then the flag needs to * be set (and CGLib present on the classpath). Because this scope automatically proxies * all its beans, there is no need to add <code><aop:auto-proxy/></code> to any bean * definitions. * </p> * <p> * The scoped proxy approach adopted here has a side benefit that bean instances are * automatically {@link Serializable}, and can be sent across the wire as long as the * receiver has an identical application context on the other side. To ensure that the two * contexts agree that they are 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 + nom; } // Ensure lifecycle is finished if bean was disposable if (super.destroy(name)) { this.context.publishEvent(new RefreshScopeRefreshedEvent(name)); return true; } return false; } @ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.") public void refreshAll() { super.destroy(); this.context.publishEvent(new RefreshScopeRefreshedEvent()); } @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; }}该类继承了GenericScope:
/* * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */package org.springframework.cloud.context.scope;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;import java.util.Collections;import java.util.LinkedHashSet;import java.util.List;import java.util.Map;import java.util.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 mise en œuvre. * </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; } return true; } } @SuppressWarnings("serial") public class LockedScopedProxyFactoryBean extends ScopedProxyFactoryBean implements MethodInterceptor { private String targetBeanName; @Override public void setBeanFactory(BeanFactory beanFactory) { super.setBeanFactory(beanFactory); Object proxy = getObject(); if (proxy 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. Exemple
创建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. Résumé
1) 当配置更新并通过refresh端点刷新后,会执行ContextRefresher的refresh方法,该方法会记录当前的Environment,而后构建一个简易的SpringApplicationBuilder并执行其run方法,此时ConfigFileApplicationListener会读取我们修改过后的配置并绑定到SpringApplication对象上,最后进行changes操作来变更已有的PropertySource
2) @RefreshScope最好配合@Bean使用,当且仅当变更配置后,需要重新获取最新的bean时使用。加上该注解的Bean会被代理并且延迟加载,所有的scope属性为Refresh的bean会被包装成BeanLifecycleWrapper存入缓存(ConcurrentHashMap)中,所有的读取,修改,删除都是基于该缓存的。
Résumer
The above is the analysis of the SpringCloud configuration refresh principle introduced by the editor. J'espère que ce sera utile à tout le monde. Si vous avez des questions, laissez-moi un message. L'éditeur répondra à tout le monde à temps!