SpringCloud配置刷新原理解析
我们知道在springcloud中,当配置变更时,我们通过访问,可以在不启动服务的情况下获取最新的配置,那么它是如何做到的呢,当我们更改数据库配置并刷新后,如何能获取最新的数据源对象呢?下面我们看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) @managedresource public 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 *) 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 @managedresource public 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") @documented public @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 * */ @managedresource public 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 @restcontroller public 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-server endpoints: refresh: sensitive: false server: port: 8089 student: 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配置刷新原理解析,希望对大家有所帮助
上一篇: SQLServer 镜像功能完全实现
推荐阅读
-
SpringCloud配置刷新原理解析
-
spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理
-
SpringMvc web.xml配置实现原理过程解析
-
SpringCloud断路器Hystrix原理及用法解析
-
Mac下安装与配置nginx php7.1,apache和nginx解析PHP的原理
-
XListView实现下拉刷新和上拉加载原理解析
-
SpringCloud学习笔记-自动刷新配置-SpringCloud-Bus(消息总线)
-
springcloud配置中心CONFIG自动刷新
-
7.springcloud Config配置自动刷新
-
springcloud配置config实现半自动刷新