Spring Cloud實戰小貼士:Zuul處理Cookie和重定向
所以解決該問題的思路也很簡單,我們只需要通過設置sensitiveHeaders即可,設置方法分為兩種:
- 全局設置:
zuul.sensitive-headers=
- 指定路由設置:
zuul.routes.<routeName>.sensitive-headers=
zuul.routes.<routeName>.custom-sensitive-headers=true
重定向問題
在使用Spring Cloud Zuul對接Web網站的時候,處理完了會話控制問題之后。往往我們還會碰到如下圖所示的問題,我們在瀏覽器中通過Zuul發起了登錄請求,該請求會被路由到某WebSite服務,該服務在完成了登錄處理之后,會進行重定向到某個主頁或歡迎頁面。此時,仔細的開發者會發現,在登錄完成之后,我們瀏覽器中URL的HOST部分發生的改變,該地址變成了具體WebSite服務的地址了。這就是在這一節,我們將分析和解決的重定向問題!
出現該問題的根源是Spring Cloud Zuul沒有正確的處理HTTP請求頭信息中的Host導致。在Brixton版本中,Spring Cloud Zuul的PreDecorationFilter
過濾器實現時完全沒有考慮這一問題,它更多的定位於REST API的網關。所以如果要在Brixton版本中增加這一特性就相對較為復雜,不過好在Camden版本之后,Spring Cloud Netflix 1.2.x版本的Zuul增強了該功能,我們只需要通過配置屬性zuul.add-host-header=true
就能讓原本有問題的重定向操作得到正確的處理。關於更多Host頭信息的處理,讀者可以參考本文之前的分析思路,可以通過查看PreDecorationFilter
過濾器的源碼來詳細更多實現細節。
http://blog.didispace.com/spring-cloud-zuul-cookie-redirect/
一 介紹
很多場景下,需要在運行期間動態調整配置。如果配置發生了修改,微服務也應該實現配置的刷新。
下面實現配置的手動刷新。
二 新建項目microservice-config-client-refresh
三 為項目添加spring-boot-starter-actuator依賴,該依賴包含了/refresh端點,用於配置的刷新。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
四 在Controller上添加注解@RefreshScope。添加@RefreshScope的類會在配置更改時得到特殊處理。
package com.itmuch.cloud.study.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${profile}")
private String profile;
@GetMapping("/profile")
public String hello() {
return this.profile;
}
}
五 測試
1 啟動microservice-config-server
2 啟動microservice-config-client-refresh
3 訪問http://localhost:8081/profile,獲得結果
dev-1.0
4 修改Git倉庫中microservice-foo-dev.properties的文件內容為:
profile=dev-1.0-change
5 重新訪問http://localhost:8081/profile,獲得結果依然是:
dev-1.0
6 發送post請求到http://localhost:8081/refresh,結果返回
[
"config.client.version",
"profile"
]
7 再次訪問http://localhost:8081/profile,返回結果為:
dev-1.0-change
說明配置已經刷新
---------------------
作者:chengqiuming
來源:CSDN
原文:https://blog.csdn.net/chengqiuming/article/details/80872615
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
https://tech.asimio.net/2017/10/10/Routing-requests-and-dynamically-refreshing-routes-using-Spring-Cloud-Zuul-Server.html
https://www.cnblogs.com/flying607/p/8459397.html
《spring擴展點之三:Spring 的監聽事件 ApplicationListener 和 ApplicationEvent 用法,在spring啟動后做些事情》
在spring-cloud-netflix-core-1.4.4.RELEASE.jar中org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent.java
@SuppressWarnings("serial") public class RoutesRefreshedEvent extends ApplicationEvent {private RouteLocator locator; public RoutesRefreshedEvent(RouteLocator locator) { super(locator); this.locator = locator; } public RouteLocator getLocator() { return this.locator; }
}
在spring-cloud-netflix-core-1.4.4.RELEASE-sources.jar中的org.springframework.cloud.netflix.zuul.zuulZuulServerAutoConfiguration.java配置了監聽事件。
private static class ZuulRefreshListener implements ApplicationListener<ApplicationEvent> {@Autowired private ZuulHandlerMapping zuulHandlerMapping; private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor(); @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextRefreshedEvent || event instanceof RefreshScopeRefreshedEvent || event instanceof RoutesRefreshedEvent) { this.zuulHandlerMapping.setDirty(true); } else if (event instanceof HeartbeatEvent) { if (this.heartbeatMonitor.update(((HeartbeatEvent) event).getValue())) { this.zuulHandlerMapping.setDirty(true); } } } }</pre>
關於spring的ApplicationEvent見《spring擴展點之三:Spring 的監聽事件 ApplicationListener 和 ApplicationEvent 用法,在spring啟動后做些事情》
https://blog.csdn.net/weixin_34267123/article/details/86021770
java配置
@Configuration public class ZuulConfig { <span class="hljs-meta"><span class="hljs-meta">@Bean</span>(name=<span class="hljs-string"><span class="hljs-string">"zuul.CONFIGURATION_PROPERTIES"</span>)
<span class="hljs-meta"><span class="hljs-meta">@RefreshScope</span>
<span class="hljs-meta"><span class="hljs-meta">@ConfigurationProperties</span>(<span class="hljs-string"><span class="hljs-string">"zuul"</span>)
<span class="hljs-meta"><span class="hljs-meta">@Primary</span>
<span class="hljs-function"><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-keyword">public</span> ZuulProperties </span><span class="hljs-title"><span class="hljs-function"><span class="hljs-title">zuulProperties</span></span><span class="hljs-params"><span class="hljs-function"><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword"><span class="hljs-keyword">return</span> <span class="hljs-keyword"><span class="hljs-keyword">new</span> ZuulProperties();
}
}
修改路由
到git config server,修改zuul的路由,比如
zuul:
host:
socket-timeout-millis: 60000 connect-timeout-millis: 30000 proxy: addProxyHeaders: true routes: baidu: path: /baidu url: http://baidu.com
刷新
curl -i -X POST localhost:10000/refresh
驗證
curl -i localhost:10000/routes
作者:go4it
鏈接:https://www.jianshu.com/p/a9332b111002
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。
我們知道在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)
@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 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);
}
}
- The "active profiles" property name.
}
根據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:

我們可以看到該類監聽了ChangeEnvrionmentEvent事件,它最主要作用是拿到更新的配置以后,重新綁定@ConfigurationProperties標記的類使之能夠讀取最新的屬性
LoggingRebinder:

該類也是監聽了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源碼:

在這個注解上我們關注一下此處標記了@Scope("refresh"),我們知道Spring的Bean屬性有個叫scope的,它定義了bean的作用范圍,常見的有singleon,prototype,session等。此處新定義了一個范圍叫做refresh,在此我貼出RefreshScope的源代碼來分析一下:

該類繼承了GenericScope:

這里面我們先看一下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(); } } } }</pre>
注意此處獲取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(); }</pre>
這里的代碼邏輯很簡單清除與釋放緩存里被@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; } </pre>
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)中,所有的讀取,修改,刪除都是基於該緩存的
原文地址:https://www.cnblogs.com/softidea/p/10427041.html