一、问题背景

项目中使用Nacos作为配置中心,实现配置动态更新,在配置类中监听EnvironmentChangeEvent事件对更新后的配置做一些初始化处理,代码如下:

CallbackConfig

@Order()
@Data
@Accessors(chain = true)
@ConfigurationProperties(prefix = "transparent-delivery.callback")
@Component
@RefreshScope
public class CallbackConfig implements InitializingBean, ApplicationListener<EnvironmentChangeEvent> {
    private static final Logger log = LoggerFactory.getLogger("CONFIG_LOG");

    private List<ChatbotConfig> configs;
    private Map<Integer, ChatbotConfig> CHATBOT_MAP;

    public ChatbotConfig fetchByUserId(Integer userId) {
        return CHATBOT_MAP.get(userId);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("Config chatbot callback information:{}!", JSON.toJSONString(configs));
        initConfigs(configs);
    }

    @Override
    public void onApplicationEvent(EnvironmentChangeEvent event) {
        log.info("Nacos refresh config information the current information is:{}!", JSON.toJSONString(configs));
        initConfigs(configs);
    }
}

但是每次在Nacos更新配置后,日志打印的值仍是修改之前的,为什么会出现这个现象?

二、EnvironmentChangeEvent的Listener执行顺序

在Nacos上更新配置后,Spring会发布一个RefreshEvent事件,这个事件最后会被委托给ContextRefresh去处理,接下来我们结合源码,看一下处理流程。

RefreshEventListener

package org.springframework.cloud.endpoint.event;

public class RefreshEventListener implements SmartApplicationListener {

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationReadyEvent) {
			handle((ApplicationReadyEvent) event);
		}
		else if (event instanceof RefreshEvent) {
			handle((RefreshEvent) event);
		}
	}

	public void handle(ApplicationReadyEvent event) {
		this.ready.compareAndSet(false, true);
	}

	public void handle(RefreshEvent event) {
		if (this.ready.get()) { // don't handle events before app is ready
			Set<String> keys = this.refresh.refresh();
		}
	}
}

当SpringCloud接收到一个RefreshEvent事件后会委托给ContextRefresherrefresh()方法去处理。

ContextRefresher

package org.springframework.cloud.context.refresh;

/**
 * @author Dave Syer
 * @author Venil Noronha
 */
public class ContextRefresher {

	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());
		addConfigFilesToEnvironment();
		Set<String> keys = changes(before,
				extract(this.context.getEnvironment().getPropertySources())).keySet();
		this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
		return keys;
	}
}

ContextRefresherRefresh()方法先将配置文件添加到Environment中,然后发布EnvironmentChangeEvent事件。

AbstractApplicationContext

   @Override
public void publishEvent(ApplicationEvent event) {
	publishEvent(event, null);
}
   
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
	// 此处略去部分代码......

	// Multicast right now if possible - or lazily once the multicaster is initialized
	if (this.earlyApplicationEvents != null) {
		this.earlyApplicationEvents.add(applicationEvent);
	}
	else {
           // 核心处理逻辑
		getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
	}

	// Publish event via parent context as well...
	if (this.parent != null) {
		if (this.parent instanceof AbstractApplicationContext) {
			((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
		}
		else {
			this.parent.publishEvent(event);
		}
	}
}

getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);通过事件广播器将事件广播出去。

SimpleApplicationEventMulticaster

package org.springframework.context.event;

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
	@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}
}

multicastEvent方法中获取当前事件的所有Listener后,循环调用Listener的onApplicationEvent方法。

AbstractApplicationEventMulticaster

protected Collection<ApplicationListener<?>> getApplicationListeners(
		ApplicationEvent event, ResolvableType eventType) {
       // 此处略去部分代码......

	if (this.beanClassLoader == null ||
			(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
					(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
		// Fully synchronized building and caching of a ListenerRetriever
		synchronized (this.retrievalMutex) {
			retriever = this.retrieverCache.get(cacheKey);
			if (retriever != null) {
				return retriever.getApplicationListeners();
			}
			retriever = new ListenerRetriever(true);
			Collection<ApplicationListener<?>> listeners =
					retrieveApplicationListeners(eventType, sourceType, retriever);
			this.retrieverCache.put(cacheKey, retriever);
			return listeners;
		}
	}
	else {
		// No ListenerRetriever caching -> no synchronization necessary
		return retrieveApplicationListeners(eventType, sourceType, null);
	}
}

private Collection<ApplicationListener<?>> retrieveApplicationListeners(
		ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {

	List<ApplicationListener<?>> allListeners = new ArrayList<>();
	Set<ApplicationListener<?>> listeners;
	Set<String> listenerBeans;
	synchronized (this.retrievalMutex) {
		listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
		listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
	}

	// Add programmatically registered listeners, including ones coming
	// from ApplicationListenerDetector (singleton beans and inner beans).
	for (ApplicationListener<?> listener : listeners) {
		if (supportsEvent(listener, eventType, sourceType)) {
			if (retriever != null) {
				retriever.applicationListeners.add(listener);
			}
			allListeners.add(listener);
		}
	}

	AnnotationAwareOrderComparator.sort(allListeners);
	if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
		retriever.applicationListeners.clear();
		retriever.applicationListeners.addAll(allListeners);
	}
	return allListeners;
}

上述代码中根据事件类型获取所有Listener,然后根据AnnotationAwareOrderComparator.sort(allListeners)对Listener进行排序。

三、AnnotationAwareOrderComparator排序原理

AnnotationAwareOrderComparator

public class AnnotationAwareOrderComparator extends OrderComparator {

	/**
	 * This implementation checks for {@link Order @Order} or
	 * {@link javax.annotation.Priority @Priority} on various kinds of
	 * elements, in addition to the {@link org.springframework.core.Ordered}
	 * check in the superclass.
	 */
	@Override
	@Nullable
	protected Integer findOrder(Object obj) {
		Integer order = super.findOrder(obj);
		if (order != null) {
			return order;
		}
		return findOrderFromAnnotation(obj);
	}

	@Nullable
	private Integer findOrderFromAnnotation(Object obj) {
		AnnotatedElement element = (obj instanceof AnnotatedElement ? (AnnotatedElement) obj : obj.getClass());
		MergedAnnotations annotations = MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY);
		Integer order = OrderUtils.getOrderFromAnnotations(element, annotations);
		if (order == null && obj instanceof DecoratingProxy) {
			return findOrderFromAnnotation(((DecoratingProxy) obj).getDecoratedClass());
		}
		return order;
	}
}    

OrderComparator

public class OrderComparator implements Comparator<Object> {
    	@Override
	public int compare(@Nullable Object o1, @Nullable Object o2) {
		return doCompare(o1, o2, null);
	}

	private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
		boolean p1 = (o1 instanceof PriorityOrdered);
		boolean p2 = (o2 instanceof PriorityOrdered);
		if (p1 && !p2) {
			return -1;
		}
		else if (p2 && !p1) {
			return 1;
		}

		int i1 = getOrder(o1, sourceProvider);
		int i2 = getOrder(o2, sourceProvider);
		return Integer.compare(i1, i2);
	}

	private int getOrder(@Nullable Object obj, @Nullable OrderSourceProvider sourceProvider) {
		Integer order = null;
		// 此处略去部分代码
		return (order != null ? order : getOrder(obj));
	}

	protected int getOrder(@Nullable Object obj) {
		if (obj != null) {
			Integer order = findOrder(obj);
			if (order != null) {
				return order;
			}
		}
		return Ordered.LOWEST_PRECEDENCE;
	}
}    

AnnotationAwareOrderComparator 继承 OrderComparator,获取对象的Order值并进行排序,如果Order为空,则会被设置为最小优先级。

OrderUtils获取对象Order值

public abstract class OrderUtils {

	/**
	 * Return the order from the specified annotations collection.
	 * <p>Takes care of {@link Order @Order} and
	 * {@code @javax.annotation.Priority}.
	 * @param element the source element
	 * @param annotations the annotation to consider
	 * @return the order value, or {@code null} if none can be found
	 */
	@Nullable
	static Integer getOrderFromAnnotations(AnnotatedElement element, MergedAnnotations annotations) {
		if (!(element instanceof Class)) {
			return findOrder(annotations);
		}
		Object cached = orderCache.get(element);
		if (cached != null) {
			return (cached instanceof Integer ? (Integer) cached : null);
		}
		Integer result = findOrder(annotations);
		orderCache.put(element, result != null ? result : NOT_ANNOTATED);
		return result;
	}

	@Nullable
	private static Integer findOrder(MergedAnnotations annotations) {
		MergedAnnotation<Order> orderAnnotation = annotations.get(Order.class);
		if (orderAnnotation.isPresent()) {
			return orderAnnotation.getInt(MergedAnnotation.VALUE);
		}
		MergedAnnotation<?> priorityAnnotation = annotations.get(JAVAX_PRIORITY_ANNOTATION);
		if (priorityAnnotation.isPresent()) {
			return priorityAnnotation.getInt(MergedAnnotation.VALUE);
		}
		return null;
	}
}    

从上面源码可以看出,如果没有设置@Order或是javax.annotation.Priority, Order值返回为null

四、EnvironmentChangeEvent的Listener有哪些

EnvironmentChangeEvent的Listener:

  • 默认的EnvironmentChangeEvent的Listener ConfigurationPropertiesRebinder
  • 自定义的EnvironmentChangeEvent的Listener CallbackConfig

其它EnvironmentChangeEvent的Listener暂不做分析处理。

ConfigurationPropertiesRebinder

package org.springframework.cloud.context.properties;

@Component
@ManagedResource
public class ConfigurationPropertiesRebinder
		implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> {
    
    @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) {
					// 此处省略部分代码......
					this.applicationContext.getAutowireCapableBeanFactory()
							.destroyBean(bean);
					this.applicationContext.getAutowireCapableBeanFactory()
							.initializeBean(bean, name);
					return true;
				}
			}
			// 此处省略异常处理代码......
		}
		return false;
	}
}            

在上述Listener中对@ConfigurationProperties标注的类进行销毁和重新创建,实现了配置更新。

CallbackConfig

@Order()
@Data
@Accessors(chain = true)
@ConfigurationProperties(prefix = "config.callback")
@Component
@RefreshScope
public class CallbackConfig implements InitializingBean, ApplicationListener<EnvironmentChangeEvent> {
    private static final Logger log = LoggerFactory.getLogger("CONFIG_LOG");

    private List<ChatbotConfig> configs;
    private Map<Integer, ChatbotConfig> CHATBOT_MAP;

    public ChatbotConfig fetchByUserId(Integer userId) {
        return CHATBOT_MAP.get(userId);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("Config chatbot callback information:{}!", JSON.toJSONString(configs));
        initConfigs(configs);
    }

    @Override
    public void onApplicationEvent(EnvironmentChangeEvent event) {
        log.info("Nacos refresh config information the current information is:{}!", JSON.toJSONString(configs));
        initConfigs(configs);
    }
}

五、原因分析

EnvironmentChangeEvent的Listener中有个默认的 ConfigurationPropertiesRebinder,这个Listener实现了配置的动态更新,但是这个Listener未使用@Order 注解,所以优先级是最小的。
我们自定义的EnvironmentChangeEvent的Listener CallbackConfig 中虽然设置了 @Order 注解并且默认值为最小优先级,但是和 ConfigurationPropertiesRebinder 的优先级是相同的,它们的执行顺序取决于加入集合的顺序,所以执行顺序不可控。无法通过设置 @Order 将自定的Listener放在 ConfigurationPropertiesRebinder 之后执行。

六、解决方案

如果是单独使用 @ConfigurationProperties 注解,则可以将配置的初始化验证放在 @PostConstruct 中处理。

There is one exception. Any @ConfigurationProperties bean that is also in @RefreshScope is not rebound when the event is consumed. They could be rebound, but in the light of what happens in @RefreshScope, it would be redundant. Instead, they follow the usual path of @RefreshScope beans.

参考文章

Dynamic Configuration Properties in Spring Boot and Spring Cloud