Sabemos que en SpringCloud, cuando cambia la configuración, podemos obtener la última configuración sin iniciar el servicio visitando http: // xxxx/actualización. Entonces, ¿cómo lo hace? ¿Cómo podemos obtener el último objeto de origen de datos después de cambiar la configuración de la base de datos y actualizar? Veamos cómo lo hace SpringCloud.
1. Cambios ambientales
1.1. Acerca de Contextrefresher
Cuando accedamos /actualizamos, la clase RefreshendPoint lo procesará. Veamos el código fuente:
/ * * Copyright 2013-2014 El autor o autores originales. * * Licenciado bajo la licencia Apache, versión 2.0 (la "licencia"); * No puede usar este archivo, excepto de conformidad con la licencia. * Puede obtener una copia de la licencia en * * http://www.apache.org/licenses/license-2.0 * * A menos que sea requerido por la ley aplicable o acordado por escrito, el software * distribuido bajo la licencia se distribuye sobre una base "como es", * sin garantías o condiciones de ningún tipo, ya sea expresa o implícita. * Consulte la licencia para los permisos de gobierno específicos que rigen el idioma y * limitaciones bajo la licencia. */paquete org.springframework.cloud.endpoint; import java.util.arrays; import java.util.collection; import java.util.set; import org.springframework.boot.actuate.endpoint.abstractendpoint; import. org.springframework.cloud.context.refresh.contextrefresher; import org.springframework.jmx.export.annotation.managedOperation; import org.springframework.jmx.export.annotation.managedresource;/** * @author syer * @author venil noronha */@ConfigurationProperties (prefix = "endpoints.refresh", ignoreUnknownFields = false) @ManagedResourcePublic Class RefreshendPoint extiende AbstractEndpoint <colección <string>> {private Contextrefresher contentefrefresher; public RefreshendPoint (contrextrefresher contrextrefresher) {super ("refresh"); this.contextrefresher = contrextrefresher; } @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 ()); }}A través del código fuente, aprendimos que al acceder al punto final de actualización, el método de actualización de Contextrefresher realmente se ejecuta. Luego seguimos rastreando el código fuente y encontramos su método de actualización:
Public Synchronized Set <String> Refresh () {Map <String, Object> antes = Extract (this.context.getEnvironment (). GetPropertySources ()); addConfigFilestoenVironment (); Establecer <String> Keys = Changes (antes, extra (this.context.getEnvironment (). GetPropertySources ())). KeySet (); this.context.publisheVent (nuevo entorno de cambio de entorno (context, claves)); this.scope.refreshall (); Revuelve las llaves; }Podemos ver que el método de actualización hace lo siguiente:
1) Obtenga todos los platos de propiedad antes de actualizar
2) Llame al método addconfigfilestoenvironment para obtener la última configuración
3) Llame al método de cambios para actualizar la información de configuración
4) Publicar el evento de cambio de medio ambiente
5) Llame al método de actualización de actualización para actualizar el rango
Centrémonos en 2, 3 y 4 pasos
1.2. Método AddConfigFilestoenVironment
Primero veamos cómo se implementa este método:
/ * para probar */ configurureApplicationContext addconfigFilSpoenVironment () {configurureableApplicationContext capture = null; intente {StandardEnvironment Environment = CopyEnVironment (this.context.getEnvironment ()); SpringApplicationBuilder Builder = new SpringApplicationBuilder (vacía.class) .Bannermode (mode.off) .web (falso) .environment (entorno); // solo los oyentes que afectan el entorno (por ejemplo, excluyendo el registro // oyente porque tiene efectos secundarios) builder.application () .setListeners (arrays.aslist (nuevo bootstrapapplicationListener (), nuevo configFileApplicationListener ())); captura = builder.run (); if (ambiente.getPropertySources (). Contiene (refresh_args_property_source)) {ambiente.getpropertySources (). eliminar (refresh_args_property_source); } MutablePropertySources Target = this.context.getEnvironment () .getPropertySources (); Cadena TargetName = null; for (PropertySource <?> Fuente: Environment.GetPropertySources ()) {String name = Source.getName (); if (target.contains (name)) {targetName = name; } if (! this.StandArdources.contains (name)) {if (target.contains (name)) {target.replace (name, fuente); } else {if (TargetName! = NULL) {Target.AddaFter (TargetName, Source); } else {// TargetName era nulo, por lo que estamos al comienzo de la lista Target.addFirst (fuente); TargetName = name; }}}}}} Finalmente {configuableApplicationContext cerrable = captura; while (cerrable! = null) {try {cerrable.close (); } capt (excepción e) {// ignorar; } if (cerrable.getParent () instancia de configururaApplicationContext) {cerrable = (configuableApplicationContext) cerrable.getParent (); } else {break; }}} return Capture; }1) Este método primero copia el entorno actual
2) Cree un programa de inicio de SpringBoot simple a través de SpringApplicationBuilder y comenzar
Builder.Application (). SetListeners (Arrays.aslist (nuevo bootstraPapplicationListener (), nuevo configFileApplicationListener ()));
Aquí se agregarán dos oyentes: BootstraPApplicationListener y ConfigFileApplicationListener. A través del aprendizaje anterior, sabemos que BootstraPapplicationListener es el oyente central del programa Bootstrap, y ConfigFileApplicationListener también es una clase muy importante:
/ * * Copyright 2012-2017 El autor o autores originales. * * Licenciado bajo la licencia Apache, versión 2.0 (la "licencia"); * No puede usar este archivo, excepto de conformidad con la licencia. * Puede obtener una copia de la licencia en * * http://www.apache.org/licenses/license-2.0 * * A menos que sea requerido por la ley aplicable o acordado por escrito, el software * distribuido bajo la licencia se distribuye sobre una base "como es", * sin garantías o condiciones de ningún tipo, ya sea expresa o implícita. * Consulte la licencia para los permisos de gobierno específicos que rigen el idioma y * limitaciones bajo la licencia. */paquete org.springframework.boot.context.config; import java.io.ioException; import java.ux.ArrayList; import java.util.arrays; import java.util.collection; import java.util.collections; import java.util.iterator; importador; java.util.linkedList; import java.util.list; import java.util.queue; import java.util.set; importar org.apache.commons.logging.log; importar org.springframework.beans.beansexception; import. org.springframework.beans.factory.config.BeanFactoryPostProcessor; importar org.springframework.beans.factory.config.configurablelistableBeanFactory; import og.springframework.boot.springapplication; import org.springframework.boot.bind.propertiesconfigury; org.springframework.boot.bind.propertySourcesPropertyValues; import org.springframework.boot.bind.relaxeddatabinder; import org.springframework.boot.bind.relaxedpropertyResolver; org.springframework.boot.context.event.ApplicationenVironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent; importar org.springframework.boot.env.enumerablecomprossepropertySource; importación; org.springframework.boot.env.environmentPostProcessor; import org.springframework.boot.env.propertySourcesloader; import og.springframework.boot.logging.deferredlog; import og.springframework.context.applicationEvent; org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.configurationClassPostProcessor; import org.springframework.context.event.smarpaplicationListener; import org.springframeWork.ordersered; importar; importación; import org.springframework.core.annotation.annotationAwareerordRomparator; import org.springframework.core.convert.conversionService; import org.springframework.core.convert.support.defaultConversionService; import og.springframework.core.env.configurinviburinvonmentment; org.springframework.core.env.enumerablePropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.propertySources; import og.springframework.core.env.propertySources; import org.springframework.core.io.defaultresourceloader; import org.springframework.core.io.resourceloader; import org.springframework.core.io.support.springfactoriesloader; import og.springframework.util.assert; import org.springframework.util.resourceutils; import org.springframework.util.stringutils; import og.springframework.validation.bindexception;/** * {@link Processor de entorno} que configura el entorno de contexto al cargar * las propiedades de las ubicaciones de archivos bien conocidas. Por defecto, las propiedades se cargarán desde * 'Application.Properties' y/o 'Aplicación Se puede especificar usando * {@link #setSearchLocations (String)} y {@link #SetSearchNames (String)}. * <p> * Los archivos adicionales también se cargarán según los perfiles activos. Por ejemplo, si un perfil 'Web' * está activo 'Application-Web.Properties' y 'Application-Web.yml' se considerará *. * <p> * La propiedad 'spring.config.name' se puede usar para especificar un nombre alternativo para cargar * y la propiedad 'spring.config.location' se puede usar para especificar ubicaciones de búsqueda alternativas * o archivos específicos. * <p> * Las propiedades de configuración también están vinculadas a {@link SpringApplication}. Esto hace que * sea posible establecer {@link SpringApplication} Propiedades dinámicamente, al igual que las fuentes * ("Spring.Main.ources" - A CSV List) la bandera para indicar un entorno web * ("Spring.Main.web_environment = True") o el indicador para apagar el banner * ("Spring.Main.Show_Banner = False"). * * @author Dave Syer * @author Phillip Webb * @author Stephane Nicoll * @author Andy Wilkinson * @author Eddú Meléndez */public class ConfigFileApplicationListener implementos de entorno Posteador, SmartApplicationListener, ordenado {String final estático privado Final de manera predeterminada = ";"; ";"; ";"; "; // Tenga en cuenta que el pedido es de menos a la cadena final estática privada de la cadena final de faush_search_locations = "classpath:/, classpath:/config/, file: ./, file: ./ config/"; cadena final estática privada default_names = "aplicación"; /*** El nombre de la propiedad "Perfiles activos". */ public static final String Active_Profiles_Property = "Spring.Profiles.active"; /*** El nombre de la propiedad "Incluye perfiles". */ public static final String include_profiles_property = "spring.profiles.include"; /*** El nombre de la propiedad "Nombre de configuración". */ public static final String config_name_property = "spring.config.name"; /*** El nombre de la propiedad de la "ubicación de configuración". */ public static final String config_location_property = "spring.config.location"; /*** El orden predeterminado para el procesador. */ public static final int default_order = ordenado.highest_precedence + 10; /*** Nombre de la configuración de la aplicación {@link PropertySource}. */ public static final String Application_Configuration_Property_source_name = "ApplicationConfigurationProperties"; Private final DeferRedLog logger = new DeFerredLog (); Locaciones de búsqueda de cadenas privadas; nombres de cadenas privadas; Private int orden = default_order; Conversión final privada Conversión Conversionservice = new DefaultConversionService (); @Override public boolean SupportSeVentType (class <? Extends AplicationEvent> EventType) {return ApplicationEnvironmentPreparedEvent.class.isassignableFrom (eventType) || ApplicationPreparedevent.class.isassignableFrom (eventType); } @Override public boolean SupportSSourCeType (class <?> Aclass) {return true; } @Override public void onApplicationEvent (AplicationEvent Event) {if (Event OnstoneOf AplicationEnVirmentPrepareVent) {OnApplicationEnvironmentPreparedEvent ((AplicationNmentPreparedEvent) Event); } if (Event OstoneOf ApplicationPreparedEvent) {OnApplicationPreparedEvent (evento); }} private void onapplicationenvironmentPreparedEvent (AplicationEnvironmentPreparedEvent Event) {List <EnvironmentPostProcessor> PostProcessors = LoadPostProcessors (); PostProcessors.Add (esto); AnnotationAwareRoRordComparator.sort (PostProcessors); para (EnvironmentPostProcessor PostProcessor: PostProcessors) {PostProcessor.PostProcessenVironment (event.getEnvironment (), event.getSpringApplication ()); }} List <EnvironmentPostProcessor> LoadPostProcessors () {return SpringFactoriesLoader.LoadFactories (EnvironmentPostProcessor.Class, GetClass (). GetClassLoader ()); } @Override public void PostProcessenVironment (entorno configurable en el entorno, aplicación SpringApplication) {addPropertySources (ambiente, aplicación.getResourCelOader ()); configureignorebeanInfo (entorno); BindToSpringApplication (entorno, aplicación); } private void configureigneRebeanInfo (entorno configurableRenVironment) {if (system.getProperty (CachedIntrospectionResults.ignore_BeanInfo_Property_Name) == null) {RelajedPropertyResolver resolver = new RelajedPropertyResolver (entorno "Spring.BeanInfo". ");); Boolean ignore = resolver.getProperty ("ignorar", boolean.class, boolean.true); System.SetProperty (CachedIntrospectionResults.ignore_Beaninfo_Property_Name, ignore.ToString ()); }} private void onapplicationPrepareReVent (evento ApplicationEvent) {this.logger.replayto (confilePplicationListener.class); addPostProcessors (((AplationPreparedEvent) evento) .getApplicationContext ()); } /*** Agregar fuentes de propiedad de archivo de configuración al entorno especificado. * @param entorno El entorno para agregar origen a * @param resourceloader el cargador de recursos * @see #addpostprocessors (configurureableApplicationContext) */ protegido void addPropertySources (ConfigurableEnvironment Environment, Recursceloader INTRESOADER) nuevo cargador (entorno, resourceloader) .load (); } /*** Vincula el entorno al {@link SpringApplication}. * @param entorno El entorno para vincular * @param Aplicación La aplicación para vincular a */ protegido void bindToSpringApplication (entorno configurable -eMironment, aplicación springapplication) {PropertiesConfigurationFactory <springApplication> binder = new PropertiesConfigurationFactory <SpringApplication> (aplicación); binder.setTargetName ("Spring.Main"); binder.setConversionService (this.ConversionService); Binder.SetPropertySources (Environment.getPropertySources ()); intente {binder.bindProperToToTarget (); } Catch (BindException ex) {Throw New IlegalStateException ("no se puede unir a SpringApplication", ex); }} /*** Agregue postprocesadores apropiados para postfigurar las fuentes de propiedad. * @param contexto El contexto para configurar */ protegido void addPostProcessors (ContextiveApplicationContext context) {context.AddBeanFactoryPostProcessor (New PropertySourceRingPosTprocessor (context)); } public void setOrder (int Order) {this.order = orden; } @Override public int getOrder () {return this.order; } /*** Establezca los lugares de búsqueda que se considerarán como una lista separada por comas. Cada * ubicación de búsqueda debe ser una ruta de directorio (que termine en "/") y se prefijará * por los nombres de archivo construidos a partir de {@link #setSearchNames (String) Nombres de búsqueda} y * perfiles (si los hay) más extensiones de archivo compatibles con los cargadores de propiedades. * Las ubicaciones se consideran en el orden especificado, con elementos posteriores que tienen prioridad * (como una fusión de mapa). * @param ubicaciones las ubicaciones de búsqueda */ public void setSearchLocations (ubicaciones de cadena) {afirmo.hasLength (ubicaciones, "las ubicaciones no deben estar vacías"); this.SearchLocations = ubicaciones; } /** * Establece los nombres de los archivos que deben cargarse (excluyendo la extensión del archivo) como una * lista separada por comas. * @param nombra los nombres para cargar */ public void setSearchNames (nombres de cadenas) {afirmar.hasLength (nombres, "Los nombres no deben estar vacíos"); this.names = nombres; } /** * {@link beanFactoryPostProcessor} para reordenar nuestras fuentes de propiedad debajo de cualquiera * {@code @propertySource} agregados por {@link ConfigurationClassPostProcessor}. */ Private Class PropertySourceRingingPostPossor implementa BeanFactoryPostProcessor, ordenado {contexto privado configurableApplicationContext; PropertySourCeOrderingPostProcessor (Contexto de configuración APPLICATIONCONEXTEXT) {this.context = context; } @Override public int getOrder () {return ordenado.highest_precedence; } @Override public void PostProcessBeanFactory (configurureableListableBeanFactory BeanFactory) lanza BeanSexception {reorderSources (this.context.getEnvironment ()); } REORDERSOURCES DE VOCES PRIVADO (entorno configurableNEnvironment) {ConfigurationPropertySources .FinishAndRelocate (Environment.getPropertySources ()); PropertySource <?> DefaultPropertySources = Environment.getPropertySources () .Remove (default_properties); if (DefaultProperties! = NULL) {Environment.getPropertySources (). AddLast (DefaultProperties); }}} /*** Carga fuentes de propiedades candidatas y configura los perfiles activos. */ private class Loader {private final log logger = configFileApplicationListener.this.logger; entorno privado final configurable y entorno de entorno; Resourceloader final privado Resourceloader; PropertySources Loader Loadies Loader privado; Perfiles de la cola privada <perry>; Lista privada <proile> ProcessedProfiles; Profiles activados booleanos privados; Loader (entorno configurable en el medio ambiente, resourceloader resourceloader) {this.environment = entorno; this.resourCelOaader = resourceloader == nulo? new DeFaultresourCelOader (): ResourCelOader; } public void load () {this.propertiesloader = new PropertySourcesLoader (); this.ActivatedProfiles = false; this.profiles = collections.aslifQuequeue (new LinkedList <PELICTER> ()); this.processedProfiles = new LinkedList <PELIL> (); // Perfiles activos preexistentes establecidos a través del entorno.setactiveProfiles () // son perfiles adicionales y los archivos de configuración pueden agregar más si // quieren, así que no llame a AddactiveProfiles () aquí. Establecer <PEPILE> InitialActiveProfiles = inicializeActiveProfiles (); this.profiles.addall (getunProcessedActiveProfiles (InitialActiveProfiles)); if (this.profiles.isEmpty ()) {for (String DefaultProfileName: this.environment.getDefaultProfiles ()) {perfil Defaultprofile = new perfil (defaultProfileName, true); if (! this.profiles.contains (defaultprofile)) {this.profiles.add (defaultprofile); }}} // El perfil predeterminado para estos fines se representa como nulo. Lo agregamos // el último para que esté primero fuera de la cola (los perfiles activos luego // anularán cualquier configuración en los valores predeterminados cuando la lista se invierte más adelante). this.profiles.add (nulo); while (! this.profiles.isEmpty ()) {perfil de perfil = this.profiles.poll (); para (ubicación de cadena: getSearchLocations ()) {if (! ubicación.endswith ("/")) {// La ubicación ya es un nombre de archivo, así que no busque más // carga de nombres (ubicación, nulo, perfil); } else {for (name de cadena: getSearchNames ()) {Load (ubicación, nombre, perfil); }}} this.processedProfiles.Add (perfil); } addconfigurationProperties (this.propertiesloader.getPropertySources ()); } set private <perry> InitializeActiveProfiles () {if (! this.environment.containsproperty (activo_profiles_property) &&! this.environment.containsproperty (include_profiles_property)) {return Collections.EmptySet (); } // Cualquier perfil activo preexistente establecido a través de fuentes de propiedad (por ejemplo, sistema // propiedades) tiene prioridad sobre los agregados en los archivos de configuración. SpringProfiles SpringProfiles = BindSpringProfiles (this.environment.getPropertySources ()); Establecer <perry> ActiveProfiles = new LinkedHashset <perry> (SpringProfiles.GetActiveProfiles ()); ActiveProfiles.addall (SpringProfiles.GetInCludProfiles ()); MayBeactivateProfiles (ActiveProfiles); devolver activoprofiles; } /*** Devuelve los perfiles activos que aún no se han procesado. Si un perfil está * habilitado a través de {@link #active_profiles_property} y * {@link configurableNenvironment #addActiveProfile (String)} Debe ser * filtrado para que el valor {@link #active_profiles_property} tenga precedencia. * <p> * concretamente, si el perfil "nube" está habilitado a través del entorno, tendrá * menos precedencia que cualquier perfil se establece a través del {@link #active_profiles_property}. * @param InitialActiveProfiles Los perfiles que han habilitado a través de * {@link #active_profiles_property} * @return los perfiles activos sin procesar desde el entorno para habilitar */ private list <ferry> getunProcessActiveProfiles (set <prepile> InitialActiveProfiles) {LIST <Cole> sin procesar for (String ProfileName: this.environment.getActiveProfiles ()) {perfil de perfil = nuevo perfil (perfileName); if (! InitialActiveProfiles.contains (perfil)) {sin procesar ActiveProfiles.Add (perfil); }} // Invertirlos para que el orden sea el mismo que de GetProfilesForValue () // (la última gana cuando las propiedades finalmente se resuelven) colección. regresar sin procesar activos apropiados; } private void load (ubicación de cadena, nombre de cadena, perfil de perfil) {string group = "perfil =" + (perfil == null? "": perfil); if (! StringUtils.hastext (name)) {// Intente cargar directamente desde la ubicación LoadNoGroup (grupo, ubicación, perfil); } else {// busca un archivo con el nombre de dado para (string ext: this.propertiesloader.getallFileExtensions ()) {if (perfil! = null) {// intente el archivo de perfil loadmogroup (grupo, ubicación + nombre + "-" + perfil + "." + ext, null); for (perfil procesadoprofile: this.processprocessprofiles) {if (ProcessedProfile! = NULL) {loadIngroup (grupo, ubicación + nombre + "-" + procesadoprofile + "." + ext, perfil); }} // A veces las personas ponen "Spring.Profiles: dev" in // Applicationdev.yml (GH-340). Podría decirse que debemos intentar errar // en eso, pero podemos ser amables y cargarlo de todos modos. LoadNoCroup (grupo, ubicación + nombre + "-" + perfil + "." + ext, perfil); } // también pruebe la sección específica del perfil (si la hay) del archivo normal loadmogroup (grupo, ubicación + nombre + "." + Ext, perfil); }}} private PropertySource <?> LoadIngroup (identificador de cadena, ubicación de cadena, perfil de perfil) {try {return doloadintogroup (identificador, ubicación, perfil); } Catch (Exception Ex) {Throw New IlegalStateException ("No se pudo cargar la fuente de propiedad desde la ubicación '" + ubicación + "'", ex); }} private PropertySource <?> doloadintogrupo (identificador de cadena, ubicación de cadena, perfil de perfil) lanza ioexception {recursos recursos = this.resourceloader.getresource (ubicación); PropertySource <?> PropertySource = null; StringBuilder msg = new StringBuilder (); if (recource! = null && resource.exists ()) {string name = "applicationConfig: [" + ubicación + "]"; String Group = "ApplicationConfig: [" + Identificador + "]"; PropertySource = this.propertiesloader.load (recursos, grupo, nombre, (perfil == null? null: perfil.getName ())); if (PropertySource! = null) {msg.append ("cargado"); HandleProfileProperties (PropertySource); } else {msg.append ("omitir (vacío)"); }} else {msg.append ("omitir"); } msg.append ("archivo de configuración"); msg.append (getResourcedescription (ubicación, recurso)); if (perfil! = null) {msg.append ("para perfil") .append (perfil); } if (recurse == null || this.logger.trace (msg); } else {this.logger.debug (msg); } return PropertySource; } String private getResourceDescription (ubicación de cadena, recurso de recursos) {String ResourceCedescription = "'" + ubicación + "'"; if (recource! = null) {try {ResourceDescription = String.Format ("'%s' (%s)", resource.geturi (). toasciistring (), ubicación); } Catch (ioException ex) {// Use la ubicación como descripción}} return ResourceDescription; } private nulo handleProfileProperties (PropertySource <?> PropertySource) {SpringProfiles SpringProfiles = BindSpringProfiles (PropertySource); MayBeActivateProfiles (SpringProfiles.GetActiveProfiles ()); addProfiles (SpringProfiles.GetInCludProfiles ()); } SpringProfiles privados BindSpringProfiles (PropertySource <?> PropertySource) {mutablePropertySources PropertySources = new MutablePropertySources (); PropertySources.AddFirst (PropertySource); return BindSpringProfiles (PropertySources); } SpringProfiles privados BindSpringProfiles (PropertySources PropertySources) {SpringProfiles SpringProfiles = new SpringProfiles (); RelatedDatabinder Databinder = new RelleedDatabinder (SpringProfiles, "Spring.Profiles"); databinder.bind (New PropertySourcesPropertyValues (PropertySources, False)); SpringProfiles.SetActive (ResolvePlaceHolders (SpringProfiles.getActive ())); SpringProfiles.SetInClude (ResolvePlaceHolders (SpringProfiles.getinclude ())); regresar springprofiles; } Lista privada <String> ResolvePlaceHolders (List <String> valores) {List <String> resuelve = new ArrayList <String> (); for (valor de cadena: valores) {resuelve.add (this.environment.resolveplaceHolders (valor)); } retorno resuelto; } private void MayBeActivateProfiles (set <perry> perfiles) {if (this.ActivateProfiles) {if (! Profiles.isEmpty ()) {this.logger.debug ("Perfiles ya activados," + perfiles + "'no se aplicará"); } devolver; } if (! perfiles.isEmpty ()) {addProfiles (perfiles); this.logger.debug ("Perfiles activados" + stringUtils.CollectionToCommadelimitedString (perfiles)); this.ActivatedProfiles = True; removeunProcessedDefaultProfiles (); }} private void removeProcessedDefaultProfiles () {for (iterator <perrate> iterator = this.profiles.iterat o (); iterator .hasnext ();) {if (iterator.next (). isDefaultprofile ()) {iterator.remove (); }}} private void addProfiles (set <perry> perfiles) {for (perfil de perfil: perfiles) {this.profiles.add (perfil); if (! EnvironmentHasActiveProfile (perfil.getName ())) {// Si ya está aceptado, asumimos que el pedido se estableció // PrependProfile (this.environment, perfil); }}} ambiente booleano privadoHasActiveProfile (perfil de cadena) {for (String ActiveProfile: this.environment.getActiveProfiles ()) {if (ActiveProfile.equals (perfil)) {return true; }} return false; } Private void PrependProfile (entorno configurableNEnvironment, perfil de perfil) {set <string> perfiles = new LinkedHashset <String> (); ambiente.getActiveProfiles (); // Asegúrese de que se inicialicen // pero este debe ir primero (los últimos gana en un choque clave de propiedad) perfiles.Add (perfil.getName ()); perfiles.addall (arrays.aslist (ambiente.getActiveProfiles ())); Environment.SetActiveProfiles (perfiles.toarray (new String [perfiles.size ()])); } set private <String> getSearchLocations () {set <string> ubicaciones = new LinkedHashset <String> (); // La configuración configurada por el usuario tiene prioridad, por lo que los hacemos primero si (this.environment.containsproperty (config_location_property)) {for (string rath: asresolvedSet (this.environment.getproperty (config_location_property), null)) {if (if (if (sath.contains ("$") {) {{) StringUtils.cleanPath (ruta); if (? }} local.Add (ruta); }} ubicación.addall (asResOlvedSet (configFileApplicationListener.this.searchLocations, default_search_locations)); ubicaciones de regreso; } set private <string> getSearchNames () {if (this.environment.containsproperty (config_name_property)) {return asResOlvedSet (this.environment.getProperty (config_name_property), null); } return AsResOlvedSet (configFileApplicationListener.this.names, default_names); } set private <string> asRResOlvedSet (valor de cadena, string fallback) {list <string> list = arrays.aslist (stringUtils.trimArrayElEments (stringUtils.commadelimitedListToStringArray (value! = null? this.environment.resolveplaceHolders (valor): fallback)); Colección.reverse (lista); devolver nuevo LinkedHashset <String> (List); } private void addconfigurationProperties (fuentes de MutablePropertySources) {List <PropertySource <? >> ReordenSources = new ArrayList <PropertySource <?>> (); para (PropertySource <?> item: fuentes) {reorderedSources.add (item); } addConfigurationProperties (New ConfigurationPropertySources (reordenedSources);} private void addConfigurationProperties (ConfigurationPropertySources ConfigurationSources) {MutablePrepertySources ExistingSources = this.environment .getPropertySources (); if (existeSources.contains (predeterminados (predeterminados) ExistingSources.AddBefore (default_properties, configurationSources); no ser nulo "); this.name = name; this.defaultprofile = defaultprofile;} public String getName () {return this.name;} public boolean isDefaultProfile () {return this.defaFault;} @Override public String toString () {return this.name;} @Override en intshodeS this.name.hashCode (); PropertySource} s A medida que están cargados pueden reubicarlos una vez que se han procesado las clases de configuración. super (Application_Configuration_Property_Source_Name, fuentes); fuente) .getPropertynames ()); NULL; if (PropertySource Of EnumerableCompositProperTySource) {enumerableCompositEpropertySource = (enumerableCompositEpropertySource) PropertySource para PropertySources.Addafter (Nombre, PropertySource); List <String> Active = New ArrayList <String> (); this.include = include; ProfileNames) {Profiles.Add (nuevo perfil (ProfileName));Según el comentario de Javadoc, esta clase cargará Application.Properties o Application.yml desde la ubicación especificada y leerá sus propiedades en envidación. Preste atención a estos métodos:
@Override public void onApplicationEvent (AplicationEvent Event) {if (Event OstoneOf AplicationEnvironmentPreparedEvent) {OnApplicationEnvironmentPreparedEvent ((AplicationEnvironmentPreparedEvent) Event); } if (Event OstoneOf ApplicationPreparedEvent) {OnApplicationPreparedEvent (evento); }} Cuando se inicia el programa SpringBoot, la escucha del evento definitivamente se activará. If the current ApplicationEnvironmentPreparedEvent event is currently called, the onApplicationEnvironmentPreparedEvent method will be called, and the method will be executed in the end:
@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. * * Licenciado bajo la licencia Apache, versión 2.0 (la "licencia"); * No puede usar este archivo, excepto de conformidad con la licencia. * Puede obtener una copia de la licencia en * * http://www.apache.org/licenses/license-2.0 * * A menos que sea requerido por la ley aplicable o acordado por escrito, el software * distribuido bajo la licencia se distribuye sobre una base "como es", * sin garantías o condiciones de ningún tipo, ya sea expresa o implícita. * Consulte la licencia para los permisos de gobierno específicos que rigen el idioma y * limitaciones bajo la licencia. */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 privateContext 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); devolver verdadero; } 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. * * Licenciado bajo la licencia Apache, versión 2.0 (la "licencia"); * No puede usar este archivo, excepto de conformidad con la licencia. * Puede obtener una copia de la licencia en * * http://www.apache.org/licenses/license-2.0 * * A menos que sea requerido por la ley aplicable o acordado por escrito, el software * distribuido bajo la licencia se distribuye sobre una base "como es", * sin garantías o condiciones de ningún tipo, ya sea expresa o implícita. * Consulte la licencia para los permisos de gobierno específicos que rigen el idioma y * limitaciones bajo la licencia. */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()); entorno privado entorno; @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. * * Licenciado bajo la licencia Apache, versión 2.0 (la "licencia"); * No puede usar este archivo, excepto de conformidad con la licencia. * Puede obtener una copia de la licencia en * * http://www.apache.org/licenses/license-2.0 * * A menos que sea requerido por la ley aplicable o acordado por escrito, el software * distribuido bajo la licencia se distribuye sobre una base "como es", * sin garantías o condiciones de ningún tipo, ya sea expresa o implícita. * Consulte la licencia para los permisos de gobierno específicos que rigen el idioma y * limitaciones bajo la licencia. */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. * * Licenciado bajo la licencia Apache, versión 2.0 (la "licencia"); 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 + nombre; } // Ensure lifecycle is finished if bean was disposable if (super.destroy(name)) { this.context.publishEvent(new RefreshScopeRefreshedEvent(name)); devolver verdadero; } 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. * * Licenciado bajo la licencia Apache, versión 2.0 (la "licencia"); 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); devolver verdadero; } 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()); resultado de retorno; } @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 verdadero; } } @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 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); }}}}}}该方法遍历所有的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. Ejemplo
创建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. Resumen
1) 当配置更新并通过refresh端点刷新后,会执行ContextRefresher的refresh方法,该方法会记录当前的Environment,而后构建一个简易的SpringApplicationBuilder并执行其run方法,此时ConfigFileApplicationListener会读取我们修改过后的配置并绑定到SpringApplication对象上,最后进行changes操作来变更已有的PropertySource
2) @RefreshScope最好配合@Bean使用,当且仅当变更配置后,需要重新获取最新的bean时使用。加上该注解的Bean会被代理并且延迟加载,所有的scope属性为Refresh的bean会被包装成BeanLifecycleWrapper存入缓存(ConcurrentHashMap)中,所有的读取,修改,删除都是基于该缓存的。
Resumir
The above is the analysis of the SpringCloud configuration refresh principle introduced by the editor. Espero que sea útil para todos. Si tiene alguna pregunta, déjame un mensaje. The editor will reply to everyone in time!