首页 > 开发 > Java > 正文

SpringCloud配置刷新原理解析

2024-07-14 08:40:38
字体:
来源:转载
供稿:网友

我们知道在SpringCloud中,当配置变更时,我们通过访问http://xxxx/refresh,可以在不启动服务的情况下获取最新的配置,那么它是如何做到的呢,当我们更改数据库配置并刷新后,如何能获取最新的数据源对象呢?下面我们看SpringCloud如何做到的。

一、环境变化

1.1、关于ContextRefresher

  当我们访问/refresh时,会被RefreshEndpoint类所处理。我们来看源代码:

/* * 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.endpoint;import java.util.Arrays;import java.util.Collection;import java.util.Set;import org.springframework.boot.actuate.endpoint.AbstractEndpoint;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.cloud.context.refresh.ContextRefresher;import org.springframework.jmx.export.annotation.ManagedOperation;import org.springframework.jmx.export.annotation.ManagedResource;/** * @author Dave Syer * @author Venil Noronha */@ConfigurationProperties(prefix = "endpoints.refresh", ignoreUnknownFields = false)@ManagedResourcepublic class RefreshEndpoint extends AbstractEndpoint<Collection<String>> { private ContextRefresher contextRefresher; public RefreshEndpoint(ContextRefresher contextRefresher) {  super("refresh");  this.contextRefresher = contextRefresher; } @ManagedOperation public String[] refresh() {  Set<String> keys = contextRefresher.refresh();  return keys.toArray(new String[keys.size()]); } @Override public Collection<String> invoke() {  return Arrays.asList(refresh()); }}

  通过源代码我们了解到:当访问refresh端点时,实际上执行的是ContextRefresher的refresh方法,那么我们继续追踪源代码,找到其refresh方法:

public synchronized Set<String> refresh() {  Map<String, Object> before = extract(    this.context.getEnvironment().getPropertySources());  addConfigFilesToEnvironment();  Set<String> keys = changes(before,    extract(this.context.getEnvironment().getPropertySources())).keySet();  this.context.publishEvent(new EnvironmentChangeEvent(context, keys));  this.scope.refreshAll();  return keys; }

  我们可以看到refresh方法做了如下几件事情:

  1)获取刷新之前的所有PropertySource

  2) 调用addConfigFilesToEnvironment方法获取最新的配置

  3) 调用changes方法更新配置信息

  4) 发布EnvironmentChangeEnvent事件

  5)调用refreshScope的refreshAll方法刷新范围

  我们重点关注一下2,3,4步骤

1.2、addConfigFilesToEnvironment方法

  我们先来看看这个方法是怎么实现的:

/* for testing */ ConfigurableApplicationContext addConfigFilesToEnvironment() {  ConfigurableApplicationContext capture = null;  try {   StandardEnvironment environment = copyEnvironment(     this.context.getEnvironment());   SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class)     .bannerMode(Mode.OFF).web(false).environment(environment);   // Just the listeners that affect the environment (e.g. excluding logging   // listener because it has side effects)   builder.application()     .setListeners(Arrays.asList(new BootstrapApplicationListener(),       new ConfigFileApplicationListener()));   capture = builder.run();   if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {    environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);   }   MutablePropertySources target = this.context.getEnvironment()     .getPropertySources();   String targetName = null;   for (PropertySource<?> source : environment.getPropertySources()) {    String name = source.getName();    if (target.contains(name)) {     targetName = name;    }    if (!this.standardSources.contains(name)) {     if (target.contains(name)) {      target.replace(name, source);     }     else {      if (targetName != null) {       target.addAfter(targetName, source);      }      else {       // targetName was null so we are at the start of the list       target.addFirst(source);       targetName = name;      }     }    }   }  }  finally {   ConfigurableApplicationContext closeable = capture;   while (closeable != null) {    try {     closeable.close();    }    catch (Exception e) {     // Ignore;    }    if (closeable.getParent() instanceof ConfigurableApplicationContext) {     closeable = (ConfigurableApplicationContext) closeable.getParent();    }    else {     break;    }   }  }  return capture; }

  1) 该方法首先拷贝当前的Environment

  2) 通过SpringApplicationBuilder构建了一个简单的SpringBoot启动程序并启动

builder.application().setListeners(Arrays.asList(new BootstrapApplicationListener(),       new ConfigFileApplicationListener()));

  这里面会添加两个监听器分别为:BootstrapApplicationListener与ConfigFileApplicationListener,通过先前的学习,我们知道BootstrapApplicationListener是引导程序的核心监听器,而ConfigFileApplicationListener也是非常重要的类:

/* * Copyright 2012-2017 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.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;import java.util.List;import java.util.Queue;import java.util.Set;import org.apache.commons.logging.Log;import org.springframework.beans.BeansException;import org.springframework.beans.CachedIntrospectionResults;import org.springframework.beans.factory.config.BeanFactoryPostProcessor;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;import org.springframework.boot.SpringApplication;import org.springframework.boot.bind.PropertiesConfigurationFactory;import org.springframework.boot.bind.PropertySourcesPropertyValues;import org.springframework.boot.bind.RelaxedDataBinder;import org.springframework.boot.bind.RelaxedPropertyResolver;import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;import org.springframework.boot.context.event.ApplicationPreparedEvent;import org.springframework.boot.env.EnumerableCompositePropertySource;import org.springframework.boot.env.EnvironmentPostProcessor;import org.springframework.boot.env.PropertySourcesLoader;import org.springframework.boot.logging.DeferredLog;import org.springframework.context.ApplicationEvent;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.context.annotation.ConfigurationClassPostProcessor;import org.springframework.context.event.SmartApplicationListener;import org.springframework.core.Ordered;import org.springframework.core.annotation.AnnotationAwareOrderComparator;import org.springframework.core.convert.ConversionService;import org.springframework.core.convert.support.DefaultConversionService;import org.springframework.core.env.ConfigurableEnvironment;import org.springframework.core.env.EnumerablePropertySource;import org.springframework.core.env.MutablePropertySources;import org.springframework.core.env.PropertySource;import org.springframework.core.env.PropertySources;import org.springframework.core.io.DefaultResourceLoader;import org.springframework.core.io.Resource;import org.springframework.core.io.ResourceLoader;import org.springframework.core.io.support.SpringFactoriesLoader;import org.springframework.util.Assert;import org.springframework.util.ResourceUtils;import org.springframework.util.StringUtils;import org.springframework.validation.BindException;/** * {@link EnvironmentPostProcessor} that configures the context environment by loading * properties from well known file locations. By default properties will be loaded from * 'application.properties' and/or 'application.yml' files in the following locations: * <ul> * <li>classpath:</li> * <li>file:./</li> * <li>classpath:config/</li> * <li>file:./config/:</li> * </ul> * <p> * Alternative search locations and names can be specified using * {@link #setSearchLocations(String)} and {@link #setSearchNames(String)}. * <p> * Additional files will also be loaded based on active profiles. For example if a 'web' * profile is active 'application-web.properties' and 'application-web.yml' will be * considered. * <p> * The 'spring.config.name' property can be used to specify an alternative name to load * and the 'spring.config.location' property can be used to specify alternative search * locations or specific files. * <p> * Configuration properties are also bound to the {@link SpringApplication}. This makes it * possible to set {@link SpringApplication} properties dynamically, like the sources * ("spring.main.sources" - a CSV list) the flag to indicate a web environment * ("spring.main.web_environment=true") or the flag to switch off the 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  implements EnvironmentPostProcessor, SmartApplicationListener, Ordered { private static final String DEFAULT_PROPERTIES = "defaultProperties"; // Note the order is from least to most specific (last one wins) private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"; private static final String DEFAULT_NAMES = "application"; /**  * The "active profiles" property name.  */ public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active"; /**  * The "includes profiles" property name.  */ public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include"; /**  * The "config name" property name.  */ public static final String CONFIG_NAME_PROPERTY = "spring.config.name"; /**  * The "config location" property name.  */ public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location"; /**  * The default order for the processor.  */ public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10; /**  * Name of the application configuration {@link PropertySource}.  */ public static final String APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME = "applicationConfigurationProperties"; private final DeferredLog logger = new DeferredLog(); private String searchLocations; private String names; private int order = DEFAULT_ORDER; private final ConversionService conversionService = new DefaultConversionService(); @Override public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {  return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)    || ApplicationPreparedEvent.class.isAssignableFrom(eventType); } @Override public boolean supportsSourceType(Class<?> aClass) {  return true; } @Override public void onApplicationEvent(ApplicationEvent event) {  if (event instanceof ApplicationEnvironmentPreparedEvent) {   onApplicationEnvironmentPreparedEvent(     (ApplicationEnvironmentPreparedEvent) event);  }  if (event instanceof ApplicationPreparedEvent) {   onApplicationPreparedEvent(event);  } } private void onApplicationEnvironmentPreparedEvent(   ApplicationEnvironmentPreparedEvent event) {  List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();  postProcessors.add(this);  AnnotationAwareOrderComparator.sort(postProcessors);  for (EnvironmentPostProcessor postProcessor : postProcessors) {   postProcessor.postProcessEnvironment(event.getEnvironment(),     event.getSpringApplication());  } } List<EnvironmentPostProcessor> loadPostProcessors() {  return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,    getClass().getClassLoader()); } @Override public void postProcessEnvironment(ConfigurableEnvironment environment,   SpringApplication application) {  addPropertySources(environment, application.getResourceLoader());  configureIgnoreBeanInfo(environment);  bindToSpringApplication(environment, application); } private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {  if (System.getProperty(    CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {   RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment,     "spring.beaninfo.");   Boolean ignore = resolver.getProperty("ignore", Boolean.class, Boolean.TRUE);   System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,     ignore.toString());  } } private void onApplicationPreparedEvent(ApplicationEvent event) {  this.logger.replayTo(ConfigFileApplicationListener.class);  addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext()); } /**  * Add config file property sources to the specified environment.  * @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);  new Loader(environment, resourceLoader).load(); } /**  * 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);  } } /**  * Add appropriate post-processors to post-configure the property-sources.  * @param context the context to configure  */ 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; } /**  * Set the search locations that will be considered as a comma-separated list. Each  * search location should be a directory path (ending in "/") and it will be prefixed  * by the file names constructed from {@link #setSearchNames(String) search names} and  * profiles (if any) plus file extensions supported by the properties loaders.  * Locations are considered in the order specified, with later items taking precedence  * (like a map merge).  * @param locations the search locations  */ public void setSearchLocations(String locations) {  Assert.hasLength(locations, "Locations must not be empty");  this.searchLocations = locations; } /**  * Sets the names of the files that should be loaded (excluding file extension) as a  * comma-separated list.  * @param names the names to load  */ public void setSearchNames(String names) {  Assert.hasLength(names, "Names must not be empty");  this.names = names; } /**  * {@link BeanFactoryPostProcessor} to re-order our property sources below any  * {@code @PropertySource} items added by the {@link ConfigurationClassPostProcessor}.  */ private class PropertySourceOrderingPostProcessor   implements BeanFactoryPostProcessor, Ordered {  private ConfigurableApplicationContext context;  PropertySourceOrderingPostProcessor(ConfigurableApplicationContext context) {   this.context = context;  }  @Override  public int getOrder() {   return Ordered.HIGHEST_PRECEDENCE;  }  @Override  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)    throws BeansException {   reorderSources(this.context.getEnvironment());  }  private void reorderSources(ConfigurableEnvironment environment) {   ConfigurationPropertySources     .finishAndRelocate(environment.getPropertySources());   PropertySource<?> defaultProperties = environment.getPropertySources()     .remove(DEFAULT_PROPERTIES);   if (defaultProperties != null) {    environment.getPropertySources().addLast(defaultProperties);   }  } } /**  * Loads candidate property sources and configures the active profiles.  */ private class Loader {  private final Log logger = ConfigFileApplicationListener.this.logger;  private final ConfigurableEnvironment environment;  private final ResourceLoader resourceLoader;  private PropertySourcesLoader propertiesLoader;  private Queue<Profile> profiles;  private List<Profile> processedProfiles;  private boolean activatedProfiles;  Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {   this.environment = environment;   this.resourceLoader = resourceLoader == null ? new DefaultResourceLoader()     : resourceLoader;  }  public void load() {   this.propertiesLoader = new PropertySourcesLoader();   this.activatedProfiles = false;   this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());   this.processedProfiles = new LinkedList<Profile>();   // Pre-existing active profiles set via Environment.setActiveProfiles()   // are additional profiles and config files are allowed to add more if   // they want to, so don't call addActiveProfiles() here.   Set<Profile> initialActiveProfiles = initializeActiveProfiles();   this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));   if (this.profiles.isEmpty()) {    for (String defaultProfileName : this.environment.getDefaultProfiles()) {     Profile defaultProfile = new Profile(defaultProfileName, true);     if (!this.profiles.contains(defaultProfile)) {      this.profiles.add(defaultProfile);     }    }   }   // The default profile for these purposes is represented as null. We add it   // last so that it is first out of the queue (active profiles will then   // override any settings in the defaults when the list is reversed later).   this.profiles.add(null);   while (!this.profiles.isEmpty()) {    Profile profile = this.profiles.poll();    for (String location : getSearchLocations()) {     if (!location.endsWith("/")) {      // location is a filename already, so don't search for more      // filenames      load(location, null, profile);     }     else {      for (String name : getSearchNames()) {       load(location, name, profile);      }     }    }    this.processedProfiles.add(profile);   }   addConfigurationProperties(this.propertiesLoader.getPropertySources());  }  private Set<Profile> initializeActiveProfiles() {   if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)     && !this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) {    return Collections.emptySet();   }   // Any pre-existing active profiles set via property sources (e.g. System   // properties) take precedence over those added in config files.   SpringProfiles springProfiles = bindSpringProfiles(     this.environment.getPropertySources());   Set<Profile> activeProfiles = new LinkedHashSet<Profile>(     springProfiles.getActiveProfiles());   activeProfiles.addAll(springProfiles.getIncludeProfiles());   maybeActivateProfiles(activeProfiles);   return activeProfiles;  }  /**   * Return the active profiles that have not been processed yet. If a profile is   * enabled via both {@link #ACTIVE_PROFILES_PROPERTY} and   * {@link ConfigurableEnvironment#addActiveProfile(String)} it needs to be   * filtered so that the {@link #ACTIVE_PROFILES_PROPERTY} value takes precedence.   * <p>   * Concretely, if the "cloud" profile is enabled via the environment, it will take   * less precedence that any profile set via the {@link #ACTIVE_PROFILES_PROPERTY}.   * @param initialActiveProfiles the profiles that have been enabled via   * {@link #ACTIVE_PROFILES_PROPERTY}   * @return the unprocessed active profiles from the environment to enable   */  private List<Profile> getUnprocessedActiveProfiles(    Set<Profile> initialActiveProfiles) {   List<Profile> unprocessedActiveProfiles = new ArrayList<Profile>();   for (String profileName : this.environment.getActiveProfiles()) {    Profile profile = new Profile(profileName);    if (!initialActiveProfiles.contains(profile)) {     unprocessedActiveProfiles.add(profile);    }   }   // Reverse them so the order is the same as from getProfilesForValue()   // (last one wins when properties are eventually resolved)   Collections.reverse(unprocessedActiveProfiles);   return unprocessedActiveProfiles;  }  private void load(String location, String name, Profile profile) {   String group = "profile=" + (profile == null ? "" : profile);   if (!StringUtils.hasText(name)) {    // Try to load directly from the location    loadIntoGroup(group, location, profile);   }   else {    // Search for a file with the given name    for (String ext : this.propertiesLoader.getAllFileExtensions()) {     if (profile != null) {      // Try the profile-specific file      loadIntoGroup(group, location + name + "-" + profile + "." + ext,        null);      for (Profile processedProfile : this.processedProfiles) {       if (processedProfile != null) {        loadIntoGroup(group, location + name + "-"          + processedProfile + "." + ext, profile);       }      }      // Sometimes people put "spring.profiles: dev" in      // application-dev.yml (gh-340). Arguably we should try and error      // out on that, but we can be kind and load it anyway.      loadIntoGroup(group, location + name + "-" + profile + "." + ext,        profile);     }     // Also try the profile-specific section (if any) of the normal file     loadIntoGroup(group, location + name + "." + ext, profile);    }   }  }  private PropertySource<?> loadIntoGroup(String identifier, String location,    Profile profile) {   try {    return doLoadIntoGroup(identifier, location, profile);   }   catch (Exception ex) {    throw new IllegalStateException(      "Failed to load property source from location '" + location + "'",      ex);   }  }  private PropertySource<?> doLoadIntoGroup(String identifier, String location,    Profile profile) throws IOException {   Resource resource = this.resourceLoader.getResource(location);   PropertySource<?> propertySource = null;   StringBuilder msg = new StringBuilder();   if (resource != null && resource.exists()) {    String name = "applicationConfig: [" + location + "]";    String group = "applicationConfig: [" + identifier + "]";    propertySource = this.propertiesLoader.load(resource, group, name,      (profile == null ? null : profile.getName()));    if (propertySource != null) {     msg.append("Loaded ");     handleProfileProperties(propertySource);    }    else {     msg.append("Skipped (empty) ");    }   }   else {    msg.append("Skipped ");   }   msg.append("config file ");   msg.append(getResourceDescription(location, resource));   if (profile != null) {    msg.append(" for profile ").append(profile);   }   if (resource == null || !resource.exists()) {    msg.append(" resource not found");    this.logger.trace(msg);   }   else {    this.logger.debug(msg);   }   return propertySource;  }  private String getResourceDescription(String location, Resource resource) {   String resourceDescription = "'" + location + "'";   if (resource != null) {    try {     resourceDescription = String.format("'%s' (%s)",       resource.getURI().toASCIIString(), location);    }    catch (IOException ex) {     // Use the location as the description    }   }   return resourceDescription;  }  private void handleProfileProperties(PropertySource<?> propertySource) {   SpringProfiles springProfiles = bindSpringProfiles(propertySource);   maybeActivateProfiles(springProfiles.getActiveProfiles());   addProfiles(springProfiles.getIncludeProfiles());  }  private SpringProfiles bindSpringProfiles(PropertySource<?> propertySource) {   MutablePropertySources propertySources = new MutablePropertySources();   propertySources.addFirst(propertySource);   return bindSpringProfiles(propertySources);  }  private SpringProfiles bindSpringProfiles(PropertySources propertySources) {   SpringProfiles springProfiles = new SpringProfiles();   RelaxedDataBinder dataBinder = new RelaxedDataBinder(springProfiles,     "spring.profiles");   dataBinder.bind(new PropertySourcesPropertyValues(propertySources, false));   springProfiles.setActive(resolvePlaceholders(springProfiles.getActive()));   springProfiles.setInclude(resolvePlaceholders(springProfiles.getInclude()));   return springProfiles;  }  private List<String> resolvePlaceholders(List<String> values) {   List<String> resolved = new ArrayList<String>();   for (String value : values) {    resolved.add(this.environment.resolvePlaceholders(value));   }   return resolved;  }  private void maybeActivateProfiles(Set<Profile> profiles) {   if (this.activatedProfiles) {    if (!profiles.isEmpty()) {     this.logger.debug("Profiles already activated, '" + profiles       + "' will not be applied");    }    return;   }   if (!profiles.isEmpty()) {    addProfiles(profiles);    this.logger.debug("Activated profiles "      + StringUtils.collectionToCommaDelimitedString(profiles));    this.activatedProfiles = true;    removeUnprocessedDefaultProfiles();   }  }  private void removeUnprocessedDefaultProfiles() {   for (Iterator<Profile> iterator = this.profiles.iterator(); iterator     .hasNext();) {    if (iterator.next().isDefaultProfile()) {     iterator.remove();    }   }  }  private void addProfiles(Set<Profile> profiles) {   for (Profile profile : profiles) {    this.profiles.add(profile);    if (!environmentHasActiveProfile(profile.getName())) {     // If it's already accepted we assume the order was set     // intentionally     prependProfile(this.environment, profile);    }   }  }  private boolean environmentHasActiveProfile(String profile) {   for (String activeProfile : this.environment.getActiveProfiles()) {    if (activeProfile.equals(profile)) {     return true;    }   }   return false;  }  private void prependProfile(ConfigurableEnvironment environment,    Profile profile) {   Set<String> profiles = new LinkedHashSet<String>();   environment.getActiveProfiles(); // ensure they are initialized   // But this one should go first (last wins in a property key clash)   profiles.add(profile.getName());   profiles.addAll(Arrays.asList(environment.getActiveProfiles()));   environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));  }  private Set<String> getSearchLocations() {   Set<String> locations = new LinkedHashSet<String>();   // User-configured settings take precedence, so we do them first   if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {    for (String path : asResolvedSet(      this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {     if (!path.contains("$")) {      path = StringUtils.cleanPath(path);      if (!ResourceUtils.isUrl(path)) {       path = ResourceUtils.FILE_URL_PREFIX + path;      }     }     locations.add(path);    }   }   locations.addAll(     asResolvedSet(ConfigFileApplicationListener.this.searchLocations,       DEFAULT_SEARCH_LOCATIONS));   return locations;  }  private Set<String> getSearchNames() {   if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {    return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),      null);   }   return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);  }  private Set<String> asResolvedSet(String value, String fallback) {   List<String> list = Arrays.asList(StringUtils.trimArrayElements(     StringUtils.commaDelimitedListToStringArray(value != null       ? this.environment.resolvePlaceholders(value) : fallback)));   Collections.reverse(list);   return new LinkedHashSet<String>(list);  }  private void addConfigurationProperties(MutablePropertySources sources) {   List<PropertySource<?>> reorderedSources = new ArrayList<PropertySource<?>>();   for (PropertySource<?> item : sources) {    reorderedSources.add(item);   }   addConfigurationProperties(     new ConfigurationPropertySources(reorderedSources));  }  private void addConfigurationProperties(    ConfigurationPropertySources configurationSources) {   MutablePropertySources existingSources = this.environment     .getPropertySources();   if (existingSources.contains(DEFAULT_PROPERTIES)) {    existingSources.addBefore(DEFAULT_PROPERTIES, configurationSources);   }   else {    existingSources.addLast(configurationSources);   }  } } private static class Profile {  private final String name;  private final boolean defaultProfile;  Profile(String name) {   this(name, false);  }  Profile(String name, boolean defaultProfile) {   Assert.notNull(name, "Name must not be null");   this.name = name;   this.defaultProfile = defaultProfile;  }  public String getName() {   return this.name;  }  public boolean isDefaultProfile() {   return this.defaultProfile;  }  @Override  public String toString() {   return this.name;  }  @Override  public int hashCode() {   return this.name.hashCode();  }  @Override  public boolean equals(Object obj) {   if (obj == this) {    return true;   }   if (obj == null || obj.getClass() != getClass()) {    return false;   }   return ((Profile) obj).name.equals(this.name);  } } /**  * Holds the configuration {@link PropertySource}s as they are loaded can relocate  * them once configuration classes have been processed.  */ static class ConfigurationPropertySources   extends EnumerablePropertySource<Collection<PropertySource<?>>> {  private final Collection<PropertySource<?>> sources;  private final String[] names;  ConfigurationPropertySources(Collection<PropertySource<?>> sources) {   super(APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME, sources);   this.sources = sources;   List<String> names = new ArrayList<String>();   for (PropertySource<?> source : sources) {    if (source instanceof EnumerablePropertySource) {     names.addAll(Arrays.asList(       ((EnumerablePropertySource<?>) source).getPropertyNames()));    }   }   this.names = names.toArray(new String[names.size()]);  }  @Override  public Object getProperty(String name) {   for (PropertySource<?> propertySource : this.sources) {    Object value = propertySource.getProperty(name);    if (value != null) {     return value;    }   }   return null;  }  public static void finishAndRelocate(MutablePropertySources propertySources) {   String name = APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME;   ConfigurationPropertySources removed = (ConfigurationPropertySources) propertySources     .get(name);   if (removed != null) {    for (PropertySource<?> propertySource : removed.sources) {     if (propertySource instanceof EnumerableCompositePropertySource) {      EnumerableCompositePropertySource composite = (EnumerableCompositePropertySource) propertySource;      for (PropertySource<?> nested : composite.getSource()) {       propertySources.addAfter(name, nested);       name = nested.getName();      }     }     else {      propertySources.addAfter(name, propertySource);     }    }    propertySources.remove(APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME);   }  }  @Override  public String[] getPropertyNames() {   return this.names;  } } /**  * Holder for {@code spring.profiles} properties.  */ static final class SpringProfiles {  private List<String> active = new ArrayList<String>();  private List<String> include = new ArrayList<String>();  public List<String> getActive() {   return this.active;  }  public void setActive(List<String> active) {   this.active = active;  }  public List<String> 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方法

private Map<String, Object> changes(Map<String, Object> before,   Map<String, Object> after) {  Map<String, Object> result = new HashMap<String, Object>();  for (String key : before.keySet()) {   if (!after.containsKey(key)) {    result.put(key, null);   }   else if (!equal(before.get(key), after.get(key))) {    result.put(key, after.get(key));   }  }  for (String key : after.keySet()) {   if (!before.containsKey(key)) {    result.put(key, after.get(key));   }  }  return result; }

  changes方法其实就是处理配置变更信息的,分以下几种情况:

  1)如果刷新过后配置文件新增配置就添加到Map里

  2)  如果有配置变更就添加变更后的配置

  3) 如果删除了原先的配置,就把原先的key对应的值设置为null

  至此经过changes方法后,上下文环境已经拥有最新的配置了。

1.4、发布事件

  当上述步骤都执行完毕后,紧接着会发布EnvrionmentChangeEvent事件,可是这个事件谁来监听呢?在这里我贴出官网的一段描述:

   应用程序将收听EnvironmentChangeEvent,并以几种标准方式进行更改(用户可以以常规方式添加ApplicationListeners附加ApplicationListeners)。当观察到EnvironmentChangeEvent时,它将有一个已更改的键值列表,应用程序将使用以下内容:

1.重新绑定上下文中的任何@ConfigurationProperties bean

2.为logging.level.*中的任何属性设置记录器级别

  根据官网描述我们知道将变更一下操作行为@ConfigurationProperties的bean与更改日志level,那么如何做到的呢?结合官网文档我们来关注以下两个类:

  ConfigurationPropertiesRebinder:

/* * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * 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 (i.e. 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 identical they have to have the same serialization id. One * will be generated automatically by default from the bean names, so two contexts with * the same bean names are by default able to exchange beans by name. If you need to * override the default id then provide an explicit {@link #setId(String) id} when the * Scope is declared. * </p> * * @author Dave Syer * * @since 3.1 * */@ManagedResourcepublic class RefreshScope extends GenericScope  implements ApplicationContextAware, Ordered { private ApplicationContext context; private BeanDefinitionRegistry registry; private boolean eager = true; private int order = Ordered.LOWEST_PRECEDENCE - 100; /**  * Create a scope instance and give it the default name: "refresh".  */ public RefreshScope() {  super.setName("refresh"); } @Override public int getOrder() {  return this.order; } public void setOrder(int order) {  this.order = order; } /**  * Flag to determine whether all beans in refresh scope should be instantiated eagerly  * on startup. Default true.  *  * @param eager the flag to set  */ public void setEager(boolean eager) {  this.eager = eager; } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)   throws BeansException {  this.registry = registry;  super.postProcessBeanDefinitionRegistry(registry); } @EventListener public void start(ContextRefreshedEvent event) {  if (event.getApplicationContext() == this.context && this.eager    && this.registry != null) {   eagerlyInitialize();  } } private void eagerlyInitialize() {  for (String name : this.context.getBeanDefinitionNames()) {   BeanDefinition definition = this.registry.getBeanDefinition(name);   if (this.getName().equals(definition.getScope()) && !definition.isLazyInit()) {    Object bean = this.context.getBean(name);    if (bean != null) {     bean.getClass();    }   }  } } @ManagedOperation(description = "Dispose of the current instance of bean name provided and force a refresh on next method execution.") public boolean refresh(String name) {  if (!name.startsWith(SCOPED_TARGET_PREFIX)) {   // User wants to refresh the bean with this name but that isn't the one in the   // cache...   name = SCOPED_TARGET_PREFIX + name;  }  // Ensure lifecycle is finished if bean was disposable  if (super.destroy(name)) {   this.context.publishEvent(new RefreshScopeRefreshedEvent(name));   return true;  }  return false; } @ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.") public void refreshAll() {  super.destroy();  this.context.publishEvent(new RefreshScopeRefreshedEvent()); } @Override public void setApplicationContext(ApplicationContext context) throws BeansException {  this.context = context; }}

  该类继承了GenericScope:

/* * Copyright 2002-2009 the original author or authors. * * 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.UUID;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.apache.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 (i.e. flush it from the cache by default).  *  * @param name the bean name to flush  * @return true if the bean was already cached, false otherwise  */ protected boolean destroy(String name) {  BeanLifecycleWrapper wrapper = this.cache.remove(name);  if (wrapper != null) {   Lock lock = locks.get(wrapper.getName()).writeLock();   lock.lock();   try {    wrapper.destroy();   }   finally {    lock.unlock();   }   this.errors.remove(name);   return true;  }  return false; } @Override public Object get(String name, ObjectFactory<?> objectFactory) {  BeanLifecycleWrapper value = this.cache.put(name,    new BeanLifecycleWrapper(name, objectFactory));  locks.putIfAbsent(name, new ReentrantReadWriteLock());  try {   return value.getBean();  }  catch (RuntimeException e) {   this.errors.put(name, e);   throw e;  } } @Override public String getConversationId() {  return this.name; } @Override public void registerDestructionCallback(String name, Runnable callback) {  BeanLifecycleWrapper value = this.cache.get(name);  if (value == null) {   return;  }  value.setDestroyCallback(callback); } @Override public Object remove(String name) {  BeanLifecycleWrapper value = this.cache.remove(name);  if (value == null) {   return null;  }  // Someone might have added another object with the same key, but we  // keep the method contract by removing the  // value we found anyway  return value.getBean(); } @Override public Object resolveContextualObject(String key) {  Expression expression = parseExpression(key);  return expression.getValue(this.evaluationContext, this.beanFactory); } private Expression parseExpression(String input) {  if (StringUtils.hasText(input)) {   ExpressionParser parser = new SpelExpressionParser();   try {    return parser.parseExpression(input);   }   catch (ParseException e) {    throw new IllegalArgumentException("Cannot parse expression: " + input,      e);   }  }  else {   return null;  } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)   throws BeansException {  this.beanFactory = beanFactory;  beanFactory.registerScope(this.name, this);  setSerializationId(beanFactory); } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)   throws BeansException {  for (String name : registry.getBeanDefinitionNames()) {   BeanDefinition definition = registry.getBeanDefinition(name);   if (definition instanceof RootBeanDefinition) {    RootBeanDefinition root = (RootBeanDefinition) definition;    if (root.getDecoratedDefinition() != null && root.hasBeanClass()      && root.getBeanClass() == ScopedProxyFactoryBean.class) {     if (getName().equals(root.getDecoratedDefinition().getBeanDefinition()       .getScope())) {      root.setBeanClass(LockedScopedProxyFactoryBean.class);     }    }   }  } } /**  * If the bean factory is a DefaultListableBeanFactory then it can serialize scoped  * beans and deserialize them in another context (even in another JVM), as long as the  * ids of the bean factories match. This method sets up the serialization id to be  * either the id provided to the scope instance, or if that is null, a hash of all the  * bean names.  *  * @param beanFactory the bean factory to configure  */ private void setSerializationId(ConfigurableListableBeanFactory beanFactory) {  if (beanFactory instanceof DefaultListableBeanFactory) {   String id = this.id;   if (id == null) {    List<String> list = new ArrayList<>(      Arrays.asList(beanFactory.getBeanDefinitionNames()));    Collections.sort(list);    String names = list.toString();    logger.debug("Generating bean factory id from names: " + names);    id = UUID.nameUUIDFromBytes(names.getBytes()).toString();   }   logger.info("BeanFactory id=" + id);   ((DefaultListableBeanFactory) beanFactory).setSerializationId(id);  }  else {   logger.warn(     "BeanFactory was not a DefaultListableBeanFactory, scoped proxy beans "       + "cannot be serialized.");  } } static RuntimeException wrapIfNecessary(Throwable throwable) {  if (throwable instanceof RuntimeException) {   return (RuntimeException) throwable;  }  if (throwable instanceof Error) {   throw (Error) throwable;  }  return new IllegalStateException(throwable); } protected String getName() {  return this.name; } private static class BeanLifecycleWrapperCache {  private final ScopeCache cache;  public BeanLifecycleWrapperCache(ScopeCache cache) {   this.cache = cache;  }  public BeanLifecycleWrapper remove(String name) {   return (BeanLifecycleWrapper) this.cache.remove(name);  }  public Collection<BeanLifecycleWrapper> clear() {   Collection<Object> values = this.cache.clear();   Collection<BeanLifecycleWrapper> wrappers = new LinkedHashSet<BeanLifecycleWrapper>();   for (Object object : values) {    wrappers.add((BeanLifecycleWrapper) object);   }   return wrappers;  }  public BeanLifecycleWrapper get(String name) {   return (BeanLifecycleWrapper) this.cache.get(name);  }  public BeanLifecycleWrapper put(String name, BeanLifecycleWrapper value) {   return (BeanLifecycleWrapper) this.cache.put(name, value);  } } /**  * Wrapper for a bean instance and any destruction callback (DisposableBean etc.) that  * is registered for it. Also decorates the bean to optionally guard it from  * concurrent access (for instance).  *  * @author Dave Syer  *  */ private static class BeanLifecycleWrapper {  private Object bean;  private Runnable callback;  private final String name;  private final ObjectFactory<?> objectFactory;  public BeanLifecycleWrapper(String name, ObjectFactory<?> objectFactory) {   this.name = name;   this.objectFactory = objectFactory;  }  public String getName() {   return this.name;  }  public void setDestroyCallback(Runnable callback) {   this.callback = callback;  }  public Object getBean() {   if (this.bean == null) {    synchronized (this.name) {     if (this.bean == null) {      this.bean = this.objectFactory.getObject();     }    }   }   return this.bean;  }  public void destroy() {   if (this.callback == null) {    return;   }   synchronized (this.name) {    Runnable callback = this.callback;    if (callback != null) {     callback.run();    }    this.callback = null;    this.bean = null;   }  }  @Override  public int hashCode() {   final int prime = 31;   int result = 1;   result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());   return result;  }  @Override  public boolean equals(Object obj) {   if (this == obj) {    return true;   }   if (obj == null) {    return false;   }   if (getClass() != obj.getClass()) {    return false;   }   BeanLifecycleWrapper other = (BeanLifecycleWrapper) obj;   if (this.name == null) {    if (other.name != null) {     return false;    }   }   else if (!this.name.equals(other.name)) {    return false;   }   return true;  } } @SuppressWarnings("serial") public class LockedScopedProxyFactoryBean extends ScopedProxyFactoryBean   implements MethodInterceptor {  private String targetBeanName;  @Override  public void setBeanFactory(BeanFactory beanFactory) {   super.setBeanFactory(beanFactory);   Object proxy = getObject();   if (proxy instanceof Advised) {    Advised advised = (Advised) proxy;    advised.addAdvice(0, this);   }  }  @Override  public void setTargetBeanName(String targetBeanName) {   super.setTargetBeanName(targetBeanName);   this.targetBeanName = targetBeanName;  }  @Override  public Object invoke(MethodInvocation invocation) throws Throwable {   Method method = invocation.getMethod();   if (AopUtils.isEqualsMethod(method)     || AopUtils.isToStringMethod(method)     || AopUtils.isHashCodeMethod(method)     || isScopedObjectGetTargetObject(method)) {    return invocation.proceed();   }   Object proxy = getObject();   Lock lock = locks.get(this.targetBeanName).readLock();   lock.lock();   try {    if (proxy instanceof Advised) {     Advised advised = (Advised) proxy;     ReflectionUtils.makeAccessible(method);     return ReflectionUtils.invokeMethod(method,       advised.getTargetSource().getTarget(),       invocation.getArguments());    }    return invocation.proceed();   }   finally {    lock.unlock();   }  }  private boolean isScopedObjectGetTargetObject(Method method) {   return method.getDeclaringClass().equals(ScopedObject.class)     && method.getName().equals("getTargetObject")     && method.getParameterTypes().length == 0;  } }}

     这里面我们先看一下RefreshScope的构造函数:

/**  * Create a scope instance and give it the default name: "refresh".  */ public RefreshScope() {  super.setName("refresh"); }

  这里面创建了一个名字为refresh的scope。

  紧接着在它的父类里我们可以看一下这个方法:

 @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)   throws BeansException {  this.beanFactory = beanFactory;  beanFactory.registerScope(this.name, this);  setSerializationId(beanFactory); }

  此方法中使用BeanFactory注册了一个refresh的范围,使得scope为refresh的bean生效。@RefreshScope标注的类还有一个特点:会使用代理对象并进行延迟加载。我们来看一下postProcessBeanDefinitionRegistry方法

 

@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)   throws BeansException {  for (String name : registry.getBeanDefinitionNames()) {   BeanDefinition definition = registry.getBeanDefinition(name);   if (definition instanceof RootBeanDefinition) {    RootBeanDefinition root = (RootBeanDefinition) definition;    if (root.getDecoratedDefinition() != null && root.hasBeanClass()      && root.getBeanClass() == ScopedProxyFactoryBean.class) {     if (getName().equals(root.getDecoratedDefinition().getBeanDefinition()       .getScope())) {      root.setBeanClass(LockedScopedProxyFactoryBean.class);     }    }   }  } }

  该方法遍历所有的bean定义 如果当前的bean的scope为refresh,那么就把当前的bean设置为 LockedScopedProxyFactoryBean的代理对象。

  RefreshScope还会监听一个ContextRefreshedEvent,该事件会在ApplicationContext初始化或者refreshed时触发,我们来看一下代码:

@EventListener public void start(ContextRefreshedEvent event) {  if (event.getApplicationContext() == this.context && this.eager    && this.registry != null) {   eagerlyInitialize();  } } private void eagerlyInitialize() {  for (String name : this.context.getBeanDefinitionNames()) {   BeanDefinition definition = this.registry.getBeanDefinition(name);   if (this.getName().equals(definition.getScope()) && !definition.isLazyInit()) {    Object bean = this.context.getBean(name);    if (bean != null) {     bean.getClass();    }   }  } }

  注意此处获取refreshscope的bean,其中getBean是一个复杂而又繁琐的过程,此处我们先不在这里讨论,只不过经过这个方法以后,其通过代理机制会在GernericScope的BeanLifecycleWrapperCache缓存里把这个@RefreshScope标记的bean添加进去。

  最后我们回过头来看一看RefreshScope的refreshAll方法:

 @ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.") public void refreshAll() {  super.destroy();  this.context.publishEvent(new RefreshScopeRefreshedEvent()); }//.......GernericScope的destroy方法 @Override public void destroy() {  List<Throwable> errors = new ArrayList<Throwable>();  Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();  for (BeanLifecycleWrapper wrapper : wrappers) {   try {    Lock lock = locks.get(wrapper.getName()).writeLock();    lock.lock();    try {     wrapper.destroy();    }    finally {     lock.unlock();    }   }   catch (RuntimeException e) {    errors.add(e);   }  }  if (!errors.isEmpty()) {   throw wrapIfNecessary(errors.get(0));  }  this.errors.clear(); }

  这里的代码逻辑很简单清除与释放缓存里被@RefreshScope标记的bean 。

  当我们要获取对象时,我们可以关注如下方法:

   

@Override public Object get(String name, ObjectFactory<?> objectFactory) {  BeanLifecycleWrapper value = this.cache.put(name,    new BeanLifecycleWrapper(name, objectFactory));  locks.putIfAbsent(name, new ReentrantReadWriteLock());  try {   return value.getBean();  }  catch (RuntimeException e) {   this.errors.put(name, e);   throw e;  } }  //..... BeanLifecycleWrapper的方法  public Object getBean() {   if (this.bean == null) {    synchronized (this.name) {     if (this.bean == null) {      this.bean = this.objectFactory.getObject();     }    }   }   return this.bean;  }   

BeanLifecycleWrapper这个是@RefreshScope标记bean的一个包装类,会被存储到缓存里,在这里取不到值的话就会从objectFactory里去拿

三、示例与总结

3.1、示例

  创建AppConfig类代码如下:

package com.bdqn.lyrk.refresh.scope.server;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.cloud.context.config.annotation.RefreshScope;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration@EnableConfigurationProperties(StudentConfig.class)public class AppConfig { @RefreshScope @Bean public Student student(StudentConfig config) {  Student student = new Student();  student.setName(config.getName());  return student; }}

  在这里,将Student设置为@RefreshScope 那么刷新以后会获取最新的Bean

  启动类:

package com.bdqn.lyrk.refresh.scope.server;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@SpringBootApplication@RestControllerpublic class RefreshScopeApplication { @Autowired private Student student; @GetMapping public String student() {  return student.getName(); } public static void main(String[] args) throws InterruptedException {  SpringApplication.run(RefreshScopeApplication.class, args); }}

  application.yml文件:

spring: application: name: refresh-scope-serverendpoints: refresh: sensitive: falseserver: port: 8089student: name: admin

  这里把refresh端点开放出来,然后变更配置后就可以获取最新的对象了

3.2、总结

  1) 当配置更新并通过refresh端点刷新后,会执行ContextRefresher的refresh方法,该方法会记录当前的Environment,而后构建一个简易的SpringApplicationBuilder并执行其run方法,此时ConfigFileApplicationListener会读取我们修改过后的配置并绑定到SpringApplication对象上,最后进行changes操作来变更已有的PropertySource

  2) @RefreshScope最好配合@Bean使用,当且仅当变更配置后,需要重新获取最新的bean时使用。加上该注解的Bean会被代理并且延迟加载,所有的scope属性为Refresh的bean会被包装成BeanLifecycleWrapper存入缓存(ConcurrentHashMap)中,所有的读取,修改,删除都是基于该缓存的。

总结

以上所述是小编给大家介绍的SpringCloud配置刷新原理解析,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!


注:相关教程知识阅读请移步到JAVA教程频道。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表