spring cloud bus Consumer Refresh 过程
还是从 Bus Server 的 /actuator/busrefresh/{serviceName}:**
端点发出的刷新事件,由 BusConsumer 消费
Consumer 刷新 时序图:
可以看出, 消费者在接收到刷新事件后,做了两件事件:
-
BusConsumer 为入口,接收到消息后 publish RemoteApplicationEvent 事件
-
RemoteApplicationEventListener 处理
-
RefreshListener 处理刷新事件,刷新上下文容器,重新绑定Properties
RemoteApplicationEventListener
public class RemoteApplicationEventListener implements ApplicationListener<RemoteApplicationEvent> {
private final Log log = LogFactory.getLog(getClass());
private final ServiceMatcher serviceMatcher;
private final BusBridge busBridge;
public RemoteApplicationEventListener(ServiceMatcher serviceMatcher, BusBridge busBridge) {
this.serviceMatcher = serviceMatcher;
this.busBridge = busBridge;
}
@Override
public void onApplicationEvent(RemoteApplicationEvent event) {
if (this.serviceMatcher.isFromSelf(event) && !(event instanceof AckRemoteApplicationEvent)) {
if (log.isDebugEnabled()) {
log.debug("Sending remote event on bus: " + event);
}
// TODO: configurable mimetype?
this.busBridge.send(event);
}
}
}
//PathServiceMatcher.java
public boolean isFromSelf(RemoteApplicationEvent event) {
String originService = event.getOriginService();
String serviceId = getBusId();
return this.matcher.match(originService, serviceId);
}
**很明显,serviceMatcher.isFromSelf(event) 为false , originService 为 cloud-config-server (配置中心), serviceId 为 cloud-client (自定义的服务), 所以 RemoteApplicationEventListener 相当于没有没有处理任何操作 **
RefreshListener
public class RefreshListener implements ApplicationListener<RefreshRemoteApplicationEvent> {
private static Log log = LogFactory.getLog(RefreshListener.class);
private ContextRefresher contextRefresher;
private ServiceMatcher serviceMatcher;
public RefreshListener(ContextRefresher contextRefresher, ServiceMatcher serviceMatcher) {
this.contextRefresher = contextRefresher;
this.serviceMatcher = serviceMatcher;
}
@Override
public void onApplicationEvent(RefreshRemoteApplicationEvent event) {
log.info("Received remote refresh request.");
if (serviceMatcher.isForSelf(event)) {
Set<String> keys = this.contextRefresher.refresh();
log.info("Keys refreshed " + keys);
}
else {
log.info("Refresh not performed, the event was targeting " + event.getDestinationService());
}
}
}
- 判断出是给自己处理的事件
- 交给 ContextRefresher 的 refresh 方法处理
public abstract class ContextRefresher {
protected final Log logger = LogFactory.getLog(getClass());
protected static final String REFRESH_ARGS_PROPERTY_SOURCE = "refreshArgs";
protected static final String[] DEFAULT_PROPERTY_SOURCES = new String[] {
// order matters, if cli args aren't first, things get messy
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME, "defaultProperties" };
protected Set<String> standardSources = new HashSet<>(
Arrays.asList(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME,
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, "configurationProperties"));
//omit...
public synchronized Set<String> refresh() {
Set<String> keys = refreshEnvironment();
this.scope.refreshAll();
return keys;
}
public synchronized Set<String> refreshEnvironment() {
Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
updateEnvironment();
Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
protected abstract void updateEnvironment()
}
- refreshEnvironment 更新上下文
- 调用 refreshEnvironment
- 从上下文中复制一份老的配置内容
- updateEnvironment 从 远程拉取最新的配置内容
- 新老配置项对比出差异 key
- 发布 EnvironmentChangeEvent 来重新绑定 Properties
- 调用 refreshEnvironment
- RefreshScope 调用 refreshAll 刷新 spring 容器中 Scope 为 refreshScope 的Bean(简单的来讲就是删除,在下次 getBean 的时候放入到 RefreshScope 中的Bean 缓存中)
从上下文中复制一份老的配置内容
这个没有什么好说的
updateEnvironment 从 远程拉取最新的配置内容
通过 ConfigDataLoader 接口的 load 方法从远程配置中心拉取最新配置内容, 实现类是 ConfigServerConfigDataLoader
:
// 部分代码
public class ConfigServerConfigDataLoader implements ConfigDataLoader<ConfigServerConfigDataResource>, Ordered {
@Override
public int getOrder() {
return -1;
}
@Override
// TODO: implement retry LoaderInterceptor
public ConfigData load(ConfigDataLoaderContext context, ConfigServerConfigDataResource resource) {
if (context.getBootstrapContext().isRegistered(ConfigServerInstanceMonitor.class)) {
// force initialization if needed
context.getBootstrapContext().get(ConfigServerInstanceMonitor.class);
}
if (context.getBootstrapContext().isRegistered(LoaderInterceptor.class)) {
LoaderInterceptor interceptor = context.getBootstrapContext().get(LoaderInterceptor.class);
Binder binder = context.getBootstrapContext().get(Binder.class);
return interceptor.apply(new LoadContext(context, resource, binder, this::doLoad));
}
return doLoad(context, resource);
}
public ConfigData doLoad(ConfigDataLoaderContext context, ConfigServerConfigDataResource resource) {
ConfigClientProperties properties = resource.getProperties();
List<PropertySource<?>> composite = new ArrayList<>();
Exception error = null;
String errorBody = null;
try {
String[] labels = new String[] { "" };
if (StringUtils.hasText(properties.getLabel())) {
labels = StringUtils.commaDelimitedListToStringArray(properties.getLabel());
}
String state = ConfigClientStateHolder.getState();
// Try all the labels until one works
for (String label : labels) {
Environment result = getRemoteEnvironment(context, resource, label.trim(), state);
//omit...
}
errorBody = String.format("None of labels %s found", Arrays.toString(labels));
}
catch (HttpServerErrorException e) {
//omt...
}
logger.warn("Could not locate PropertySource: " + (error != null ? error.getMessage() : errorBody));
return null;
}
protected Environment getRemoteEnvironment(ConfigDataLoaderContext context, ConfigServerConfigDataResource resource,
String label, String state) {
ConfigClientProperties properties = resource.getProperties();
RestTemplate restTemplate = context.getBootstrapContext().get(RestTemplate.class);
String path = "/{name}/{profile}";
String name = properties.getName();
String profile = resource.getProfiles();
String token = properties.getToken();
int noOfUrls = properties.getUri().length;
if (noOfUrls > 1) {
logger.info("Multiple Config Server Urls found listed.");
}
Object[] args = new String[] { name, profile };
if (StringUtils.hasText(label)) {
// workaround for Spring MVC matching / in paths
label = Environment.denormalize(label);
args = new String[] { name, profile, label };
path = path + "/{label}";
}
ResponseEntity<Environment> response = null;
List<MediaType> acceptHeader = Collections.singletonList(MediaType.parseMediaType(properties.getMediaType()));
for (int i = 0; i < noOfUrls; i++) {
ConfigClientProperties.Credentials credentials = properties.getCredentials(i);
String uri = credentials.getUri();
String username = credentials.getUsername();
String password = credentials.getPassword();
logger.info("Fetching config from server at : " + uri);
try {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(acceptHeader);
addAuthorizationToken(properties, headers, username, password);
if (StringUtils.hasText(token)) {
headers.add(TOKEN_HEADER, token);
}
if (StringUtils.hasText(state) && properties.isSendState()) {
headers.add(STATE_HEADER, state);
}
final HttpEntity<Void> entity = new HttpEntity<>((Void) null, headers);
response = restTemplate.exchange(uri + path, HttpMethod.GET, entity, Environment.class, args);
}
catch (HttpClientErrorException e) {
}
if (response == null || response.getStatusCode() != HttpStatus.OK) {
return null;
}
Environment result = response.getBody();
return result;
}
return null;
}
}
新老配置项对比出差异 key
没什么好说的
发布 EnvironmentChangeEvent 来重新绑定 Properties
当发布 EnvironmentChangeEvent
事后, ConfigurationPropertiesRebinder 会处理,这个类主要是处理 使用了 ConfigurationProperties
注解的Bean,进行属性的重新绑定
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 = ProxyUtils.getTargetObject(bean);
}
if (bean != null) {
// TODO: determine a more general approach to fix this.
// see https://github.com/spring-cloud/spring-cloud-commons/issues/571
if (getNeverRefreshable().contains(bean.getClass().getName())) {
return false; // ignore
}
this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);
this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);
return true;
}
}
catch (RuntimeException e) {
this.errors.put(name, e);
throw e;
}
catch (Exception e) {
this.errors.put(name, e);
throw new IllegalStateException("Cannot rebind to " + name, e);
}
}
return false;
}
@ManagedAttribute
public Set<String> getNeverRefreshable() {
String neverRefresh = this.applicationContext.getEnvironment()
.getProperty("spring.cloud.refresh.never-refreshable", "com.zaxxer.hikari.HikariDataSource");
return StringUtils.commaDelimitedListToSet(neverRefresh);
}
@ManagedAttribute
public Set<String> getBeanNames() {
return new HashSet<>(this.beans.getBeanNames());
}
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.applicationContext.equals(event.getSource())
// Backwards compatible
|| event.getKeys().equals(event.getSource())) {
rebind();
}
}
}
处理过程就是:
- 先通过 getBean 获取 Bean 实例
- 然后调用 bean 的初始化方法 initializeBean