Sabemos que no SpringCloud, quando a configuração mudar, podemos obter a configuração mais recente sem iniciar o serviço visitando http: // xxxx/atualização. Então, como isso faz isso? Como podemos obter o objeto de fonte de dados mais recente depois de alterar a configuração e atualização do banco de dados? Vamos ver como o Springcloud faz isso.
1. Mudanças ambientais
1.1. Sobre o contextrefresher
Quando acessarmos /atualizar, ele será processado pela classe RefreshendPoint. Vejamos o código -fonte:
/ * * Copyright 2013-2014 O autor original ou autores. * * Licenciado sob a licença Apache, versão 2.0 (a "licença"); * Você não pode usar esse arquivo, exceto em conformidade com a licença. * Você pode obter uma cópia da licença em * * http://www.apache.org/license/license-2.0 * *, a menos que exigido pela lei aplicável ou acordada por escrito, o software * distribuído sob a licença seja distribuído em uma base "como é" *, sem garantia ou condição de qualquer tipo, seja expresso ou implícito. * Consulte a licença para as permissões e limitações do idioma específico e * sob a licença. */package org.springframework.cloud.endpoint; importar java.util.arrays; importar java.util.collection; importar java.util.set; importar org.springframewration.Boot.actuate.endPoint.abStractEngPoint; importação.SpringFramework.Foot.Coot.CoT. org.springframework.cloud.context.refresh.contextRefresher; importar org.springframework.jmx.export.annotation.managageoperation; importar org.springframework.jmx.export.annoghetResorce;/** * @Athorkor da syer. */@ConfigurationProperties (prefix = "EndPoints.Refresh", ignoreunknownfields = false) @ManagedResourcepCublic Classe RefreshendPoint estende abstratendpoint <coleção <string>> {private contextRefresher contextrefresher; public RefreshendPoint (contextrefresher contextrefresher) {super ("refresh"); this.ContextRefresher = contextrefresher; } @ManagedOPeration public String [] refresh () {set <string> keys = contextrefresher.refresh (); retornar as chaves.toArray (new String [keys.size ()]); } @Override public Collection <String> Invoke () {return Arrays.asList (refresh ()); }}Através do código -fonte, aprendemos que, ao acessar o terminal de atualização, o método de atualização do contextrefresher é realmente executado. Em seguida, continuamos a rastrear o código -fonte e encontrar seu método de atualização:
public Sincronized Set <String> refresh () {map <string, object> antes = extrair (this.context.getenvironment (). getPropertySources ()); addConfigFilestoAnvironment (); Set <string> chaves = alterações (antes, extract (this.Context.getenvironment (). GetPropertySources ())). KeySet (); this.Context.publishEvent (New EnvironmentChangeEvent (contexto, chaves)); this.Scope.Refreshall (); chaves de retorno; }Podemos ver que o método de atualização faz o seguinte:
1) Obtenha todas as fontes de propriedade antes da atualização
2) Ligue para o método AddConfigFilestoambeRonment para obter a configuração mais recente
3) Chame o método de alterações para atualizar as informações de configuração
4) Publique o EMBORAALCHANGEENVENT EVENTO
5) Ligue para o método de refresco de refreschscope para atualizar o intervalo
Vamos nos concentrar em 2, 3 e 4 etapas
1.2. Método AddConfigFilestoAmbaring
Vamos primeiro olhar como esse método é implementado:
/ * para teste */ configurableApplicationContext addConfigFilestoenvironment () {configurableApplicationContext Capture = null; tente {StandardEnvironment Ambient = CopyEnvironment (this.Context.getenvironment ()); SpringApplicationBuilder Builder = new SpringApplicationBuilder (empty.class) .banNermode (mode.off) .Web (false) .ENVIRENAMENT (Ambiental); // Apenas os ouvintes que afetam o ambiente (por exemplo, excluindo o registro // ouvintes porque tem efeitos colaterais) Builder.Application () .SetListeners (Arrays.asList (new bootstrapApplicationListener (), new configFileApplicationListener ()); capture = construtor.run (); if (Environment.getPropertySources (). } MutablePropertySources Target = this.Context.getenvironment () .getPropertySources (); String TargetName = NULL; for (PropertySource <?> Fonte: Environment.getPropertySources ()) {string name = source.getName (); if (Target.Contains (nome)) {TargetName = Name; } if (! this.standardsources.contains (nome)) {if (Target.Contains (nome)) {Target.replace (nome, origem); } else {if (TargetName! = null) {Target.addafter (TargetName, fonte); } else {// TargetName foi nulo, então estamos no início do destino da lista.addfirst (fonte); TargetName = nome; }}}}}} finalmente {configurableApplicationContext Closeable = Capture; while (fechado! = null) {try {closable.close (); } catch (Exceção e) {// ignore; } if (closable.getParent () Instância de configurableApplicationContext) {fechamento = (configurableApplicationContext) fechamento.getParent (); } else {break; }}} retornar captura; }1) Este método primeiro copia o ambiente atual
2) Crie um programa de inicialização simples de trampolim através do SpringApplicationBuilder e inicie
builder.Application (). SetListeners (Arrays.asList (new bootstrapApplicationListener (), new ConfigFileApplicationListener ());
Dois ouvintes serão adicionados aqui: BootstrapApplicationListener e ConfigFileApplicationListener. Através da aprendizagem anterior, sabemos que o BootstrapApplicationListener é o ouvinte principal do programa Bootstrap, e o ConfigFileApplicationListener também é uma classe muito importante:
/ * * Copyright 2012-2017 O autor original ou autores. * * Licenciado sob a licença Apache, versão 2.0 (a "licença"); * Você não pode usar esse arquivo, exceto em conformidade com a licença. * Você pode obter uma cópia da licença em * * http://www.apache.org/license/license-2.0 * *, a menos que exigido pela lei aplicável ou acordada por escrito, o software * distribuído sob a licença seja distribuído em uma base "como é" *, sem garantia ou condição de qualquer tipo, seja expresso ou implícito. * Consulte a licença para as permissões e limitações do idioma específico e * sob a licença. */package org.springframework.boot.context.config;import java.io.IOException;import java.util.ArrayList;import java.util.Arrays;import java.util.Collection;import java.util.Collections;import java.util.Iterator;import java.util.LinkedHashSet;import java.util.LinkedList; importar java.util.list; importar java.util.queue; importar java.util.set; importar org.apache.commons.logging.logs; org.springframework.beans.factory.config.BeanFactoryPostProcessor; importar org.springframework.beans.factory.config.configurableListableBeanFactory; importação org.springframework.springApplication; ImportActor.SpringfringfringfringFrameworkworkwoot.SpringApplication; ImportAcration; org.springframework.boot.bind.propertysourcesPropertyValues; importar org.springframework.boot.bind.relaxedDatabinder; importar org.springframework.boot.bind.relaxedPropertyResolver; import org.springframework.boot.context.event.applicationenvironmentPreparedEvent; importação org.springframework.boot.context.event.applicationPreparedEvent; Import.springframework.env.enumerabablecomPoTySourtyCeisCe org.springframework.boot.env.environmentPostProcessor; importar org.springframework.boot.env.propertysourcesloader; import org.springframework.boot.logging.deferredlog; import org.splingframework.coot.apting.apting.DeferredLog; importar; org.springframework.context.configurableApplicationContext; importar org.springframework.context.annotation.configurationclassPostProcessor; importação org.springframework.context.event.smartorplicationListener; org.springframework.core.annotation.annotationAWareOrdercomParator; importar org.springframework.core.convert.conversionService; importar org.springframework.springframewror.corenenEnving.support.defaultConversion; importar org.springframework.corenenenving.support.defaultConsões; importação; org.springframework.core.env.enumerablePropertySource; importar org.springframework.core.env.mutablePropertySources; importar org.springframework.core.env.propertysources; org.springframework.core.io.defaultResourceLoader; importar org.springframework.core.io.resourceloader; importar org.springframework.core.io.support.springFactoriesLoader; importar org.springframewil.util.util.sutil.support.springFactories; org.springframework.util.resourceutils; importar org.springframework.util.stringutils; importar org.springframework.validation.bindexception;/** * {@link AmbientsProcessor} que configura o ambiente de contexto por carregamento * Propriedades de propriedades de propriedades de bem -estar de propriedades de bem -estar. Por padrão, as propriedades serão carregadas de arquivos * 'Application.Properties' e/ou 'Application.yml' nos seguintes locais: * <ul> * <li> ClassPath: </li> * <li> Arquivo: ./ </li> * <li> Classpath: config/</li> <li> Arquivo: .//: <li> <li> * * * *. Locais e nomes podem ser especificados usando * {@link #setsearchlocations (string)} e {@link #setsearchNames (string)}. * <p> * arquivos adicionais também serão carregados com base em perfis ativos. Por exemplo, se um perfil 'web' * estiver ativo 'aplicativo-web.properties' e 'aplicativo-web.yml' serão * considerados. * <p> * A propriedade 'spring.config.name' pode ser usada para especificar um nome alternativo para carregar * e a propriedade 'spring.config.Location' pode ser usada para especificar locais alternativos * ou arquivos específicos. * <p> * As propriedades de configuração também estão vinculadas à {@link springApplication}. Isso torna * possível definir {@link SpringApplication} Propriedades dinamicamente, como as fontes * ("spring.main.sources" - uma lista de CSV) o sinalizador para indicar um ambiente da web * ("spring.main.web_environment = true") ou o sinalizador para desligar o banner * ("spring.main.main * * @Author Dave Syer * @Author Phillip webb * @Author Stephane Nicoll * @Author Andy Wilkinson * @Author Eddú Meléndez */public classe ConfigFileApplicationListener implementa o AmbientPostorroil, o SmartApticationLister (Orderd {PrivateListener STATTIONMEMMENGEMENGRAIMENTES STATTIONSTRACESTROCESSOR, SMARTAPLICATIONSTENTER, Orderd {PrivateD) // Observe que o pedido é do menos para o mais específico (o último vence) da String estática privada default_search_locations = "ClassPath:/, ClassPath:/config/, arquivo: ./, arquivo: ./ config/"; String final estática privada default_names = "Application"; /*** O nome da propriedade "Perfis ativos". */ public static final string Active_profiles_property = "spring.profiles.active"; /*** O nome da propriedade "Inclui perfis". */ string final public static incluir_profiles_property = "spring.profiles.include"; /*** O nome da propriedade "Nome da configuração". */ public static final string config_name_property = "spring.config.name"; /*** O nome da propriedade "Config Location". */ public static final string config_location_property = "spring.config.location"; /*** O pedido padrão para o processador. */ public static final int default_order = ordened.highest_precedence + 10; /*** Nome da configuração do aplicativo {@link PropertySource}. */ public static final string application_configuration_property_source_name = "ApplicationConfigurationProperties"; private final adteredLog logger = new DeferredLog (); Private String SearchLocações; nomes privados de string; Private Int pedido = default_order; ConversioService final privado conversioService = new DefaultConversionService (); @Override public boolean SupportSentType (classe <? Extende applicationEvent> EventType) {return ApplicationEnvironmentPreparedEvent.class.isassignablefrom (EventType) || ApplicationPreparedEvent.class.isassignableFrom (EventType); } @Override public boolean SupportSSourCetype (classe <?> Aclass) {return true; } @Override public void onApplicationEvent (ApplicationEvent Event) {if (evento de eventof ApplicationEnvironmentPreparedEvent) {OnApplicationEnvironmentPreparedEvent ((ApplicationEnvironmentPreparedEvent)); } if (Instância do evento do aplicativoPreparEDevent) {OnApplicationPreparedEvent (Event); }} private void onApplicicationEnvironmentPreparedEvent (ApplicationEnvironmentPreparedEvent Event) {List <Imen EnvironmentPostProcessor> Postprocessors = loadPostProcessors (); Postprocessors.add (isto); AnoTationAWareOrdercomparator.sort (pós -processadores); para (Postprocessador do AmbientPostProcessor: Pós -Processadores) {Postprocessor.PostProcesSenvironment (Event.getEnvironment (), event.getspringApplication ()); }} List <orthorthPostProcessor> loadPostProcessors () {return springfacoriesloader.loadFactories (EnvironmentPostProcessor.class, getClass (). GetClassLoader ()); } @Override public void Postprocessenvironment (ambiente de ambiente configurável, aplicativo de springApplication) {addPropertySources (ambiente, Application.getResourceLoader ()); configureignorebeanInfo (ambiente); bindtoSpringApplication (ambiente, aplicativo); } private void ConfigureignoreBeanInfo (ambiente de ambiente configurável) {if (System.getProperty (CachedintrospecçãoResults.ignore_beaninfo_property_name) == null) {relaxedPropertyRolver = NewaLedProperTyResolver (Environment). Boolean ignore = resolver.getProperty ("ignorar", boolean.class, boolean.true); System.SetProperty (CacheDintSpecçãoResults.ignore_beaninfo_property_name, ignore.toString ()); }} private void onApplicationPreparedEvent (evento ApplicationEvent) {this.logger.Replayto (configFileApplicationListener.class); addPostProcessors ((((ApplicationPreparedEvent) Evento) .getApplicationContext ()); } /*** Adicionar fontes de propriedade de arquivo de configuração ao ambiente especificado. * @param environment the environment to add source to * @param resourceLoader the resource loader * @see #addPostProcessors(ConfigurableApplicationContext) */ protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); novo carregador (ambiente, resourceLoader) .load (); } /*** Ligue o ambiente ao {@link SpringApplication}. * Ambiente @param O ambiente para ligar * @param Aplicativo O aplicativo para se ligar a */ Protected void bindtospringApplication (ambiente de ambiente configurável, aplicativo de springApplication) {PropriedadesConfigurationFactory <PringApplication> Binder = new PropertiesConfigurationPatory <PringApplication> (Application); Binder.setTargetName ("spring.main"); Binder.setConversionService (this.ConversionService); Binder.SetPropertySources (Environment.getPropertySources ()); tente {Binder.BindProperSTiestOTarget (); } catch (bindException ex) {lança new ilegalStateException ("não pode se ligar ao springapplication", ex); }} /*** Adicione os pós-processadores apropriados para pós-confiar as fontes da propriedade. * @Param Context O contexto para configurar */ Protected void addPostProcessors (ConfigurableApplicationContext context) {context.addBeanFactoryPostProcessor (new PropertySourceOrderingPostProcessor (Context)); } public void setOrder (Int Order) {this.order = order; } @Override public int getOrder () {return this.Order; } /*** Defina os locais de pesquisa que serão considerados como uma lista separada por vírgula. Cada localização de pesquisa deve ser um caminho de diretório (terminando em "/") e será prefixado * pelos nomes de arquivos construídos a partir de {@link #setsearchNames (string) nomes de pesquisa} e * perfis (se houver) e extensões de arquivo suportadas pelos carregadores de propriedades. * Os locais são considerados na ordem especificada, com itens posteriores tendo precedência * (como uma mesclagem de mapa). * Locais @param Os locais de pesquisa */ public void SetSearchLocations (localizações da string) {Assert.HasLength (Locais, "Locais não devem estar vazios"); this.searchLocações = locais; } /** * Define os nomes dos arquivos que devem ser carregados (excluindo a extensão do arquivo) como uma lista separada por vírgula. * @param nomeia os nomes para carregar */ public void SetSearchNames (nomes de string) {Assert.haslength (nomes, "nomes não devem estar vazios"); this.names = nomes; } /** * {@link BeanFactoryPostProcessor} para reordenar nossas fontes de propriedade abaixo de qualquer * {@code @propertySource} itens adicionados pelos {@link confiativeclassPostProcessor}. */ Classe privada PropertySourceOrderingPostProcessor implementa BeanFactoryPostProcessor, ordenado {private ConfigurableApplicationContext Context; PropertySourceOrderingPostProcessor (ConfigurableAplicationContext context) {this.Context = context; } @Override public int getOrder () {return orderd.highest_precedence; } @Override public void postprocessBeanFactory (configurableListableBeanFactory BeanFactory) lança beansexception {reordersources (this.context.getenvironment ()); } private void reordersources (ambiente configurável e ambiente) {ConfigurationPropertySources .FinishandRelocate (Environment.getPropertySources ()); PropertySource <?> DefaultPropertySources = Environment.getPropertySources () .remove (default_properties); if (defaultProperties! = null) {Environment.getPropertySources (). addlast (defaultProperties); }}} /*** Carrega fontes de propriedades candidatos e configura os perfis ativos. */ classe privada carregador {private final logger = configFileApplicationListener.this.logger; Ambiente final de configuração final privada; PRIVENT FINAL ResourceLoader ResourceLoader; PropertySourceRloader PropertiesLoader privado; Perfis de fila privada <Filid>; Lista privada <File> ProcessEdProfiles; Profiles privados de ativação booleanos; Loader (ambiente de ambiente configurável, resourceLoader ResourceLoader) {this.envionment = Environment; this.ResourceLoader = ResourceLoader == NULL? new AfaultResourceLoader (): ResourceLoader; } public void load () {this.PropertiesLoader = new PropertySourcesLoader (); this.activatedProfiles = false; this.profiles = collection.aslifoqueue (new LinkedList <Filer> ()); this.processousProfiles = new LinkedList <Filid> (); // Perfis ativos pré-existentes definidos via ambiente.SetActiveProfiles () // são perfis adicionais e arquivos de configuração podem adicionar mais se // eles desejam, portanto, não chame o addactiveProfiles () aqui. SET <File> InitialActiveProfiles = InitializeActiveProfiles (); this.profiles.addall (getUnProcessectiveProfiles (InitialActiveProfiles)); if (this.Profiles.isEmpty ()) {for (string defaultProfilename: this.enorambonment.getDefaultProfiles ()) {perfil defaultProfile = new perfil (defaultProfilename, true); if (! this.profiles.contains (defaultProfile)) {this.profiles.add (defaultProfile); }}} // O perfil padrão para esses fins é representado como nulo. Adicionamos // duram para que ele seja o primeiro da fila (os perfis ativos serão então // substituirão quaisquer configurações nos padrões quando a lista for revertida posteriormente). this.profiles.add (nulo); while (! this.profiles.isempty ()) {perfil perfil = this.profiles.poll (); para (localização da string: getSearchLocations ()) {if (! location.endswith ("/")) {// Localização já é um nome de arquivo, então não procure mais // nomes de arquivos carregamento (localização, nulo, perfil); } else {for (nome da string: getSearchNames ()) {load (localização, nome, perfil); }}} this.processedprofiles.add (perfil); } addConfigurationProperties (this.propertiesloader.getPropertySources ()); } Conjunto privado <Filid> InitializeActiveProfiles () {if (! this.environment.containsProperty (Active_Profiles_Property) &&! this.environment.containsProperty (incluir_profiles_property)) {Return CollectionS.EmtySet (); } // Qualquer perfis ativos pré-existentes definidos por fontes de propriedade (por exemplo, System // Properties) têm precedência sobre os adicionados nos arquivos de configuração. SpringProfiles springProfiles = bindspringprofiles (this.environment.getPropertySources ()); SET <File> ActiveProfiles = new LinkedHashSet <Filid> (SpringProfiles.getActiveProfiles ()); ActiveProfiles.addall (SpringProfiles.getincludeProfiles ()); MayBeActivateProfiles (ActiveProfiles); devolver o ativo profiles; } /*** Retorne os perfis ativos que ainda não foram processados. Se um perfil estiver * ativado através de {@link #active_profiles_property} e * {@link configurableenvironment #addactiveProfile (string)}, ele precisa ser * filtrado para que o valor {@link #active_profiles_property} tenha precedência. * <p> * concretamente, se o perfil "nuvem" for ativado através do ambiente, terá * menos precedência que qualquer perfil definido através do {@link #Active_Profiles_Property}. * @Param InitialActiveProfiles Os perfis que foram ativados por * {@link #Active_Profiles_Property} * @return os perfis ativos não processados do ambiente para ativar */ Private List <Filfile> GetUnprocedActiveProfiles (Set <File> InitialProfiles) {List> Profile> (ProfileScastActivePROFILS = para (String Profilename: this.environment.getActiveProfiles ()) {perfil perfil = new perfil (ProfileName); if (! InitialActiveProfiles.Contains (perfil)) {não -processadoPROFILES.ADD (perfil); }} // reverte -os para que a ordem seja a mesma que do getProfilesForValue () // (o último vence quando as propriedades são resolvidas) coleções. devolver profiles não -processados não -processados; } LOAD DE VOID PRIVADO (LOCALIZAÇÃO DA STRING, Nome da String, Perfil Profile) {String Group = "Profile =" + (perfil == null? "": perfil); if (! stringUtils.hastext (name)) {// tente carregar diretamente do local loadITogroup (grupo, localização, perfil); } else {// Pesquise um arquivo com o nome fornecido para (string ext: this.propertiesloader.getAllFileExtensions ()) {if (perfil! = null) {// Experimente o arquivo específico do perfil loadInToGroup (grupo, local, nome + "-" + perfil + "." + ext, null); para (Profile ProcessEdProfile: this.processousProfiles) {if (ProcessEdProfile! = null) {loadInTogroup (grupo, localização + nome + "-" + processEdProfile + "." + ext, perfil); }} // Às vezes, as pessoas colocam "Spring.Profiles: Dev" em // Application-Dev.yml (GH-340). Indiscutivelmente, devemos tentar errar // sobre isso, mas podemos ser gentis e carregá -lo de qualquer maneira. loadInTogroup (grupo, localização + nome + "-" + perfil + "." + ext, perfil); } // Também tente a seção específica do perfil (se houver) do arquivo normal de arquivo (grupo, localização + nome + "." + Ext, perfil); }}} private PropertySource <?> loadInTogroup (identificador de string, localização da string, perfil de perfil) {tente {return doloadIntogroup (identificador, localização, perfil); } catch (Exceção ex) {lança new IllegalStateException ("Falha ao carregar a fonte da propriedade do local '" + localização + "'", ex); }} private PropertySource <?> doloadIntoGroup (identificador de string, localização da string, perfil de perfil) lança IoException {Resource Resource = this.ResourceLoader.getResource (Location); PropertySource <?> PropertySource = null; Stringbuilder msg = new StringBuilder (); if (Resource! = null && Resource.exists ()) {String name = "ApplicationConfig: [" + location + "]"; String group = "ApplicationConfig: [" + identificador + "]"; PropertySource = this.PropertiesLoader.load (recurso, grupo, nome, (perfil == null? null: perfil.getName ())); if (PropertySource! = null) {msg.append ("carregado"); handleprofileProperties (PropertySource); } else {msg.append ("Skipped (vazio)"); }} else {msg.append ("Skipped"); } msg.append ("arquivo de configuração"); msg.append (getResourceDescription (localização, recurso)); if (perfil! = null) {msg.append ("para perfil") .append (perfil); } if (Resource == null ||! Resource.exists ()) {msg.append ("Recurso não encontrado"); this.logger.Trace (msg); } else {this.logger.debug (msg); } retornar propriedadesource; } String privada getResourceDescription (localização da string, recurso de recurso) {string ResourceDescription = "'" + Location + "'"; if (Resource! = NULL) {Try {ResourceScription = String.Format ("'%S' (%s)", Resource.geturi (). toasciistring (), localização); } catch (ioexception ex) {// use o local como a descrição}} retornar o ResourceDescription; } private void handleprofileProperties (PropertySource <?> PropertySource) {SpringProfiles SpringProfiles = bindsPringProfiles (PropertySource); MayBeActivateProfiles (SpringProfiles.getActiveProfiles ()); addProfiles (springprofiles.getincludeProfiles ()); } SpringProfiles privados bindsPringProfiles (PropertySource <?> PropertySource) {MutablePropertySources PropertySources = new MutablePropertySources (); PropertySources.addfirst (PropertySource); Return bindspringprofiles (PropertySources); } SpringProfiles privados bindspringprofiles (PropertySources PropertySources) {SpringProfiles springProfiles = new SpringProfiles (); Databinder RelaxedDatabinder = novo RelaxedDatabinder (SpringProfiles, "Spring.Profiles"); Databinder.Bind (New PropertySourcesPropertyValues (PropertySources, false)); SpringProfiles.SetActive (ResolvePlaceHolders (SpringProfiles.getActive ())); SpringProfiles.setinclude (resolvendoustholders (springprofiles.getinclude ())); Retornar o Springprofiles; } Lista privada <String> ResolvePlaceHolders (List <String> valores) {list <tring> resolvido = new ArrayList <String> (); for (String Value: valores) {resolvido.add (this.environment.ResolvePlaceHolders (valor)); } retornar resolvido; } private void maybeactivateProfiles (set <file> perfis) {if (this.activateProfiles) {if (! perfis.isempty ()) {this.logger.debug ("perfis já ativados" "" + perfis + "'não serão aplicados"); } retornar; } if (! perfis.isempty ()) {addProfiles (perfis); this.logger.debug ("perfis ativados" + stringUtils.collectionToMadelimitedString (perfis)); this.ActivatedProfiles = true; removerunproceddefaultprofiles (); }} private void RemoneunProcedDefaultProfiles () {for (iterator <filid> iterator = this.profiles.iterat ou (); iterator .hasnext ();) {if (iterator.Next (). ISDeFaultProfile ()) {iterator.Remove (); }}} private void AddProfiles (SET <File> perfis) {for (perfil perfil: perfis) {this.profiles.add (perfil); if (! EnvironmentHasActiveProfile (perfil.getName ())) {// se já for aceito, assumimos que o pedido foi definido // pretendProfile pretendentemente (this.environment, perfil); }}} private boolean AmbientHasactiveProfile (perfil da String) {for (String ActiveProfile: this.environment.getActiveProfiles ()) {if (ActiveProfile.equals (perfil)) {return true; }} retornar false; } private void precendProfile (ambiente configurável de ambiente, perfil de perfil) {SET <Ftring> perfis = new LinkedHashSet <String> (); Environment.getActiveProfiles (); // Verifique se eles são inicializados //, mas este deve ir primeiro (os últimos vitórias em um chash de chave de propriedade) perfis.add (perfil.getName ()); perfis.addall (Arrays.asList (Environment.getActiveProfiles ())); Environment.SetActiveProfiles (perfis.toArray (new String [perfis.size ()])); } Set privado <string> getSearchLocations () {set <string> locations = new LinkedHashSet <String> (); // As configurações configuradas pelo usuário têm precedência, então as fazemos primeiro se (this.environment.containsProperty (config_location_property)) {for (string path: asResolvedSet (this.environment.getProperty (config_location_property), null)) {se (! Stringutils.cleanpath (caminho); if (! Resourceutils.isurl (path)) {path = resourceutils.file_url_prefix + path; }} locations.add (caminho); }} locations.addall (AsResolvedSet (configFileApplicationListener.This.SearchLocações, Default_search_locations)); Locais de retorno; } conjunto privado <string> getSearchNames () {if (this.environment.containsProperty (config_name_property)) {return asResolvedSet (this.environment.getProperty (config_name_property), null); } retornar asResolvedSet (configFileApplicationListener.this.names, default_names); } conjunto privado <string> asResolvedSet (Valor da String, String Fallback) {List <String> list = Arrays.ASLIST (StringUtils.TrimarrayElements (StringUtils.CommadeLimitedListToStringArray (Value! Coleções.Reverse (lista); retornar novo LinkedHashSet <String> (List); } private void addConfigurationProperties (fontes mutablePropertySources) {list <PropertySource <? >> reordenousources = new ArrayList <PropertySource <? >> (); for (PropertySource <?> Item: Fontes) {reordenoudsources.add (item); } AddConfigurationProperties (novo ConfigurationPropertySources (reordenadosources);} Void privado AddConfigurationProperties (ConfigurationPropertySources Configurações. Existingingsources.Addbe antes (default_properties, Configurações); Assert.NotNull (Nome, Nome não deve ser nulo "); hashcode () {return this.name.hashcode (); Segura a configuração {@link PropertySource} s como carregada pode realocar * quando as classes de configuração forem processadas. ConfigurationPropertySources (Coleção <PropertySource <? >> Fontes) {Super (Application_configuration_property_source_name, fontes); names.addall (Arrays.asList ((enumerablePropertySource <?>). Propriedadesource.getProperty (Nome); (Removido! = NULL) {for (PropertySource <?> PropertySource: Removed.sources) {if (PropertySource Instância de enumerableCompositionPropertySource) {enumerableCompositionPropertySource Composite = (enumableComPoSPROPROPERTYSource): Propriedadesources.addafter (Nome, aninhado); / ** * Holder para {@code spring.profiles} Propriedades getInclude() { return this.include; } public void setInclude(List<String> include) { this.include = include; } Set<Profile> getActiveProfiles() { return asProfileSet(this.active); } Set<Profile> getIncludeProfiles() { return asProfileSet(this.include); } private Set<Profile> asProfileSet(List<String> profileNames) { List<Profile> profiles = new ArrayList<Profile>(); for (String profileName : profileNames) { 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 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. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.cloud.context.properties;import java.util.HashSet;import java.util.Map;import java.util.Set;import java.util.concurrent.ConcurrentHashMap;import org.springframework.aop.framework.Advised;import org.springframework.aop.support.AopUtils;import org.springframework.beans.BeansException;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.cloud.context.config.annotation.RefreshScope;import org.springframework.cloud.context.environment.EnvironmentChangeEvent;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.context.ApplicationListener;import org.springframework.core.env.Environment;import org.springframework.jmx.export.annotation.ManagedAttribute;import org.springframework.jmx.export.annotation.ManagedOperation;import org.springframework.jmx.export.annotation.ManagedResource;import org.springframework.stereotype.Component;/** * Listens for {@link EnvironmentChangeEvent} and rebinds beans that were bound to the * {@link Environment} using {@link ConfigurationProperties * <code>@ConfigurationProperties</code>}. When these beans are re-bound and * re-initialized the changes are available immediately to any component that is using the * <code>@ConfigurationProperties</code> bean. * * @see RefreshScope for a deeper and optionally more focused refresh of bean components * * @author Dave Syer * */@Component@ManagedResourcepublic class ConfigurationPropertiesRebinder implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> { private ConfigurationPropertiesBeans beans; private ApplicationContext applicationContext; private Map<String, Exception> errors = new ConcurrentHashMap<>(); public ConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) { this.beans = beans; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * A map of bean name to errors when instantiating the bean. * * @return the errors accumulated since the latest destroy */ public Map<String, Exception> getErrors() { return this.errors; } @ManagedOperation public void rebind() { this.errors.clear(); for (String name : this.beans.getBeanNames()) { rebind(name); } } @ManagedOperation public boolean rebind(String name) { if (!this.beans.getBeanNames().contains(name)) { return false; } if (this.applicationContext != null) { try { Object bean = this.applicationContext.getBean(name); if (AopUtils.isAopProxy(bean)) { bean = getTargetObject(bean); } this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean); this.applicationContext.getAutowireCapableBeanFactory() .initializeBean(bean, name); return true; } catch (RuntimeException e) { this.errors.put(name, e); throw e; } } return false; } @SuppressWarnings("unchecked") private static <T> T getTargetObject(Object candidate) { try { if (AopUtils.isAopProxy(candidate) && (candidate instanceof Advised)) { return (T) ((Advised) candidate).getTargetSource().getTarget(); } } catch (Exception ex) { throw new IllegalStateException("Failed to unwrap proxied object", ex); } return (T) candidate; } @ManagedAttribute public Set<String> getBeanNames() { return new HashSet<String>(this.beans.getBeanNames()); } @Override public void onApplicationEvent(EnvironmentChangeEvent event) { if (this.applicationContext.equals(event.getSource()) // Backwards compatible || event.getKeys().equals(event.getSource())) { rebind(); } }}我们可以看到该类监听了ChangeEnvrionmentEvent事件,它最主要作用是拿到更新的配置以后,重新绑定@ConfigurationProperties标记的类使之能够读取最新的属性
LoggingRebinder:
/* * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.cloud.logging;import java.util.Map;import java.util.Map.Entry;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.boot.bind.RelaxedPropertyResolver;import org.springframework.boot.logging.LogLevel;import org.springframework.boot.logging.LoggingSystem;import org.springframework.cloud.context.environment.EnvironmentChangeEvent;import org.springframework.context.ApplicationListener;import org.springframework.context.EnvironmentAware;import org.springframework.core.env.Environment;/** * Listener that looks for {@link EnvironmentChangeEvent} and rebinds logger levels if any * changed. * * @author Dave Syer * */public class LoggingRebinder implements ApplicationListener<EnvironmentChangeEvent>, EnvironmentAware { private final Log logger = LogFactory.getLog(getClass()); private Environment environment; @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void onApplicationEvent(EnvironmentChangeEvent event) { if (this.environment == null) { return; } LoggingSystem system = LoggingSystem.get(LoggingSystem.class.getClassLoader()); setLogLevels(system, this.environment); } protected void setLogLevels(LoggingSystem system, Environment environment) { Map<String, Object> levels = new RelaxedPropertyResolver(environment) .getSubProperties("logging.level."); for (Entry<String, Object> entry : levels.entrySet()) { setLogLevel(system, environment, entry.getKey(), entry.getValue().toString()); } } private void setLogLevel(LoggingSystem system, Environment environment, String name, String level) { try { if (name.equalsIgnoreCase("root")) { name = null; } level = environment.resolvePlaceholders(level); system.setLogLevel(name, LogLevel.valueOf(level.toUpperCase())); } catch (RuntimeException ex) { this.logger.error("Cannot set level: " + level + " for '" + name + "'"); } }}该类也是监听了ChangeEnvrionmentEvent事件,用于重新绑定日志级别
二、刷新范围
我们考虑如下场景,当我们变更数据库配置后,通过refresh刷新,虽然能获取到最新的配置,可是我们的DataSource对象早就被初始化好了,换句话说即便配置刷新了我们拿到的依然是配置刷新前的对象。怎么解决这个问题呢?
我们继续看ContextRefresher的refresh方法,最后有一处代码值得我们关注一下this.scope.refreshAll(),此处scope对象是RefreshScope类型,那么这个类有什么作用呢?那么我们先要关注一下@RefreshScope注解。在这里我在贴出官网一段解释:
当配置更改时,标有@RefreshScope的Spring @Bean将得到特殊处理。这解决了状态bean在初始化时只注入配置的问题。例如,如果通过Environment更改数据库URL时DataSource有开放连接,那么我们可能希望这些连接的持有人能够完成他们正在做的工作。然后下一次有人从游泳池借用一个连接,他得到一个新的URL
刷新范围bean是在使用时初始化的懒惰代理(即当调用一个方法时),并且作用域作为初始值的缓存。要强制bean重新初始化下一个方法调用,您只需要使其缓存条目无效。RefreshScope是上下文中的一个bean,它有一个公共方法refreshAll()来清除目标缓存中的范围内的所有bean。还有一个refresh(String)方法可以按名称刷新单个bean。此功能在/refresh端点(通过HTTP或JMX)中公开。
这里我贴出@RefreshScope源码:
/* * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.cloud.context.config.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import org.springframework.context.annotation.Scope;import org.springframework.context.annotation.ScopedProxyMode;/** * Convenience annotation to put a <code>@Bean</code> definition in * {@link org.springframework.cloud.context.scope.refresh.RefreshScope refresh scope}. * Beans annotated this way can be refreshed at runtime and any components that are using * them will get a new instance on the next method call, fully initialized and injected * with all dependencies. * * @author Dave Syer * */@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Scope("refresh")@Documentedpublic @interface RefreshScope { /** * @see Scope#proxyMode() */ ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;}在这个注解上我们关注一下此处标记了@Scope("refresh"),我们知道Spring的Bean属性有个叫scope的,它定义了bean的作用范围,常见的有singleon,prototype,session等。此处新定义了一个范围叫做refresh,在此我贴出RefreshScope的源代码来分析一下:
/* * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */package org.springframework.cloud.context.scope.refresh;import java.io.Serializable;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanDefinition;import org.springframework.beans.factory.support.BeanDefinitionRegistry;import org.springframework.cloud.context.scope.GenericScope;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.context.event.ContextRefreshedEvent;import org.springframework.context.event.EventListener;import org.springframework.core.Ordered;import org.springframework.jmx.export.annotation.ManagedOperation;import org.springframework.jmx.export.annotation.ManagedResource;/** * <p> * A Scope implementation that allows for beans to be refreshed dynamically at runtime * (see {@link #refresh(String)} and {@link #refreshAll()}). If a bean is refreshed then * the next time the bean is accessed (ie a method is executed) a new instance is * created. All lifecycle methods are applied to the bean instances, so any destruction * callbacks that were registered in the bean factory are called when it is refreshed, and * then the initialization callbacks are invoked as normal when the new instance is * created. A new bean instance is created from the original bean definition, so any * externalized content (property placeholders or expressions in string literals) is * re-evaluated when it is created. * </p> * <p> * Note that all beans in this scope are <em>only</em> initialized when first accessed, so * the scope forces lazy initialization semantics. The implementation involves creating a * proxy for every bean in the scope, so there is a flag * {@link #setProxyTargetClass(boolean) proxyTargetClass} which controls the proxy * creation, defaulting to JDK dynamic proxies and therefore only exposing the interfaces * implemented by a bean. If callers need access to other methods then the flag needs to * be set (and CGLib present on the classpath). Because this scope automatically proxies * all its beans, there is no need to add <code><aop:auto-proxy/></code> to any bean * definitions. * </p> * <p> * The scoped proxy approach adopted here has a side benefit that bean instances are * automatically {@link Serializable}, and can be sent across the wire as long as the * receiver has an identical application context on the other side. To ensure that the two * contexts agree that they are identity they have to have the same serialization id. One * will be generated automatically by default from the bean names, so two contexts with * the same bean names are by default able to exchange beans by name. If you need to * override the default id then provide an explicit {@link #setId(String) id} when the * Scope is declared. * </p> * * @author Dave Syer * * @since 3.1 * */@ManagedResourcepublic class RefreshScope extends GenericScope implements ApplicationContextAware, Ordered { private ApplicationContext context; private BeanDefinitionRegistry registry; private boolean eager = true; private int order = Ordered.LOWEST_PRECEDENCE - 100; /** * Create a scope instance and give it the default name: "refresh". */ public RefreshScope() { super.setName("refresh"); } @Override public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } /** * Flag to determine whether all beans in refresh scope should be instantiated eagerly * on startup. Default true. * * @param eager the flag to set */ public void setEager(boolean eager) { this.eager = eager; } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { this.registry = registry; super.postProcessBeanDefinitionRegistry(registry); } @EventListener public void start(ContextRefreshedEvent event) { if (event.getApplicationContext() == this.context && this.eager && this.registry != null) { eagerlyInitialize(); } } private void eagerlyInitialize() { for (String name : this.context.getBeanDefinitionNames()) { BeanDefinition definition = this.registry.getBeanDefinition(name); if (this.getName().equals(definition.getScope()) && !definition.isLazyInit()) { Object bean = this.context.getBean(name); if (bean != null) { bean.getClass(); } } } } @ManagedOperation(description = "Dispose of the current instance of bean name provided and force a refresh on next method execution.") public boolean refresh(String name) { if (!name.startsWith(SCOPED_TARGET_PREFIX)) { // User wants to refresh the bean with this name but that isn't the one in the // cache... name = SCOPED_TARGET_PREFIX + name; } // Ensure lifecycle is finished if bean was disposable if (super.destroy(name)) { this.context.publishEvent(new RefreshScopeRefreshedEvent(name)); return true; } retornar 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 implementation. * </p> * @author Dave Syer * * @since 3.1 * */public class GenericScope implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean { private static final Log logger = LogFactory.getLog(GenericScope.class); public static final String SCOPED_TARGET_PREFIX = "scopedTarget."; private BeanLifecycleWrapperCache cache = new BeanLifecycleWrapperCache( new StandardScopeCache()); private String name = "generic"; private ConfigurableListableBeanFactory beanFactory; private StandardEvaluationContext evaluationContext; private String id; private Map<String, Exception> errors = new ConcurrentHashMap<>(); private ConcurrentMap<String, ReadWriteLock> locks = new ConcurrentHashMap<>(); /** * Manual override for the serialization id that will be used to identify the bean * factory. The default is a unique key based on the bean names in the bean factory. * * @param id the id to set */ public void setId(String id) { this.id = id; } /** * The name of this scope. Default "generic". * * @param name the name value to set */ public void setName(String name) { this.name = name; } /** * The cache implementation to use for bean instances in this scope. * * @param cache the cache to use */ public void setScopeCache(ScopeCache cache) { this.cache = new BeanLifecycleWrapperCache(cache); } /** * A map of bean name to errors when instantiating the bean. * * @return the errors accumulated since the latest destroy */ public Map<String, Exception> getErrors() { return this.errors; } @Override public void destroy() { List<Throwable> errors = new ArrayList<Throwable>(); Collection<BeanLifecycleWrapper> wrappers = this.cache.clear(); for (BeanLifecycleWrapper wrapper : wrappers) { try { Lock lock = locks.get(wrapper.getName()).writeLock(); lock.lock(); try { wrapper.destroy(); } finally { lock.unlock(); } } catch (RuntimeException e) { errors.add(e); } } if (!errors.isEmpty()) { throw wrapIfNecessary(errors.get(0)); } this.errors.clear(); } /** * Destroy the named bean (ie flush it from the cache by default). * * @param name the bean name to flush * @return true if the bean was already cached, false otherwise */ protected boolean destroy(String name) { BeanLifecycleWrapper wrapper = this.cache.remove(name); if (wrapper != null) { Lock lock = locks.get(wrapper.getName()).writeLock(); lock.lock(); try { wrapper.destroy(); } finally { lock.unlock(); } this.errors.remove(name); return true; } retornar 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; } retornar true; } } @SuppressWarnings("serial") public class LockedScopedProxyFactoryBean extends ScopedProxyFactoryBean implements MethodInterceptor { private String targetBeanName; @Override public void setBeanFactory(BeanFactory beanFactory) { super.setBeanFactory(beanFactory); Object proxy = getObject(); if (proxy instanceof Advised) { Advised advised = (Advised) proxy; advised.addAdvice(0, this); } } @Override public void setTargetBeanName(String targetBeanName) { super.setTargetBeanName(targetBeanName); this.targetBeanName = targetBeanName; } @Override public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); if (AopUtils.isEqualsMethod(method) || AopUtils.isToStringMethod(method) || AopUtils.isHashCodeMethod(method) || isScopedObjectGetTargetObject(method)) { return invocation.proceed(); } Object proxy = getObject(); Lock lock = locks.get(this.targetBeanName).readLock(); lock.lock(); try { if (proxy instanceof Advised) { Advised advised = (Advised) proxy; ReflectionUtils.makeAccessible(method); return ReflectionUtils.invokeMethod(method, advised.getTargetSource().getTarget(), invocation.getArguments()); } return invocation.proceed(); } finally { lock.unlock(); } } private boolean isScopedObjectGetTargetObject(Method method) { return method.getDeclaringClass().equals(ScopedObject.class) && method.getName().equals("getTargetObject") && method.getParameterTypes().length == 0; } }}这里面我们先看一下RefreshScope的构造函数:
/** * Create a scope instance and give it the default name: "refresh". */ public RefreshScope() { super.setName("refresh"); }这里面创建了一个名字为refresh的scope。
紧接着在它的父类里我们可以看一下这个方法:
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; beanFactory.registerScope(this.name, this); setSerializationId(beanFactory); }此方法中使用BeanFactory注册了一个refresh的范围,使得scope为refresh的bean生效。@RefreshScope标注的类还有一个特点:会使用代理对象并进行延迟加载。我们来看一下postProcessBeanDefinitionRegistry方法
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { for (String name : registry.getBeanDefinitionNames()) { BeanDefinition definition = registry.getBeanDefinition(name); if (definition instanceof RootBeanDefinition) { RootBeanDefinition root = (RootBeanDefinition) definition; if (root.getDecoratedDefinition() != null && root.hasBeanClass() && root.getBeanClass() == ScopedProxyFactoryBean.class) { if (getName().equals(root.getDecoratedDefinition().getBeanDefinition() .getScope())) { root.setBeanClass(LockedScopedProxyFactoryBean.class); } } } } }该方法遍历所有的bean定义如果当前的bean的scope为refresh,那么就把当前的bean设置为LockedScopedProxyFactoryBean的代理对象。
RefreshScope还会监听一个ContextRefreshedEvent,该事件会在ApplicationContext初始化或者refreshed时触发,我们来看一下代码:
@EventListener public void start(ContextRefreshedEvent event) { if (event.getApplicationContext() == this.context && this.eager && this.registry != null) { eagerlyInitialize(); } } private void eagerlyInitialize() { for (String name : this.context.getBeanDefinitionNames()) { BeanDefinition definition = this.registry.getBeanDefinition(name); if (this.getName().equals(definition.getScope()) && !definition.isLazyInit()) { Object bean = this.context.getBean(name); if (bean != null) { bean.getClass(); } } } }注意此处获取refreshscope的bean,其中getBean是一个复杂而又繁琐的过程,此处我们先不在这里讨论,只不过经过这个方法以后,其通过代理机制会在GernericScope的BeanLifecycleWrapperCache缓存里把这个@RefreshScope标记的bean添加进去。
最后我们回过头来看一看RefreshScope的refreshAll方法:
@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.") public void refreshAll() { super.destroy(); this.context.publishEvent(new RefreshScopeRefreshedEvent()); }//.......GernericScope的destroy方法@Override public void destroy() { List<Throwable> errors = new ArrayList<Throwable>(); Collection<BeanLifecycleWrapper> wrappers = this.cache.clear(); for (BeanLifecycleWrapper wrapper : wrappers) { try { Lock lock = locks.get(wrapper.getName()).writeLock(); lock.lock(); try { wrapper.destroy(); } finally { lock.unlock(); } } catch (RuntimeException e) { errors.add(e); } } if (!errors.isEmpty()) { throw wrapIfNecessary(errors.get(0)); } this.errors.clear(); }这里的代码逻辑很简单清除与释放缓存里被@RefreshScope标记的bean 。
当我们要获取对象时,我们可以关注如下方法:
@Override public Object get(String name, ObjectFactory<?> objectFactory) { BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory)); locks.putIfAbsent(name, new ReentrantReadWriteLock()); try { return value.getBean(); } catch (RuntimeException e) { this.errors.put(name, e); throw e; } } //..... BeanLifecycleWrapper的方法public Object getBean() { if (this.bean == null) { synchronized (this.name) { if (this.bean == null) { this.bean = this.objectFactory.getObject(); } } } return this.bean; }BeanLifecycleWrapper这个是@RefreshScope标记bean的一个包装类,会被存储到缓存里,在这里取不到值的话就会从objectFactory里去拿
三、示例与总结
3.1、示例
创建AppConfig类代码如下:
package com.bdqn.lyrk.refresh.scope.server;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.cloud.context.config.annotation.RefreshScope;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration@EnableConfigurationProperties(StudentConfig.class)public class AppConfig { @RefreshScope @Bean public Student student(StudentConfig config) { Student student = new Student(); student.setName(config.getName()); return student; }}在这里,将Student设置为@RefreshScope 那么刷新以后会获取最新的Bean
启动类:
package com.bdqn.lyrk.refresh.scope.server;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@SpringBootApplication@RestControllerpublic class RefreshScopeApplication { @Autowired private Student student; @GetMapping public String student() { return student.getName(); } public static void main(String[] args) throws InterruptedException { SpringApplication.run(RefreshScopeApplication.class, args); }}application.yml文件:
spring: application: name: refresh-scope-serverendpoints: refresh: sensitive: falseserver: port: 8089student: name: admin
这里把refresh端点开放出来,然后变更配置后就可以获取最新的对象了
3.2、总结
1) 当配置更新并通过refresh端点刷新后,会执行ContextRefresher的refresh方法,该方法会记录当前的Environment,而后构建一个简易的SpringApplicationBuilder并执行其run方法,此时ConfigFileApplicationListener会读取我们修改过后的配置并绑定到SpringApplication对象上,最后进行changes操作来变更已有的PropertySource
2) @RefreshScope最好配合@Bean使用,当且仅当变更配置后,需要重新获取最新的bean时使用。加上该注解的Bean会被代理并且延迟加载,所有的scope属性为Refresh的bean会被包装成BeanLifecycleWrapper存入缓存(ConcurrentHashMap)中,所有的读取,修改,删除都是基于该缓存的。
Resumir
以上所述是小编给大家介绍的SpringCloud配置刷新原理解析,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!