Skip to content

Commit

Permalink
use spring application event to broadcast the config change event
Browse files Browse the repository at this point in the history
  • Loading branch information
nobodyiam committed Apr 16, 2022
1 parent a42b793 commit 7232497
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,33 @@
*/
package com.ctrip.framework.apollo.spring.config;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener;
import com.ctrip.framework.apollo.spring.spi.ApolloConfigChangeEvent;
import com.ctrip.framework.apollo.spring.util.SpringInjector;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;

import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;

import java.util.Collection;
import java.util.Iterator;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;

Expand All @@ -56,14 +56,16 @@
*
* @author Jason Song([email protected])
*/
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware,
ApplicationEventPublisherAware, PriorityOrdered {
private static final Multimap<Integer, String> NAMESPACE_NAMES = LinkedHashMultimap.create();
private static final Set<BeanFactory> AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES = Sets.newConcurrentHashSet();

private final ConfigPropertySourceFactory configPropertySourceFactory = SpringInjector
.getInstance(ConfigPropertySourceFactory.class);
private ConfigUtil configUtil;
private ConfigurableEnvironment environment;
private ApplicationEventPublisher applicationEventPublisher;

public static boolean addNamespaces(Collection<String> namespaces, int order) {
return NAMESPACE_NAMES.putAll(order, namespaces);
Expand Down Expand Up @@ -134,17 +136,16 @@ private void ensureBootstrapPropertyPrecedence(ConfigurableEnvironment environme
}

private void initializeAutoUpdatePropertiesFeature(ConfigurableListableBeanFactory beanFactory) {
if (!configUtil.isAutoUpdateInjectedSpringPropertiesEnabled() ||
!AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.add(beanFactory)) {
if (!AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.add(beanFactory)) {
return;
}

AutoUpdateConfigChangeListener autoUpdateConfigChangeListener = new AutoUpdateConfigChangeListener(
environment, beanFactory);
ConfigChangeListener configChangeEventPublisher = changeEvent ->
applicationEventPublisher.publishEvent(new ApolloConfigChangeEvent(changeEvent));

List<ConfigPropertySource> configPropertySources = configPropertySourceFactory.getAllConfigPropertySources();
for (ConfigPropertySource configPropertySource : configPropertySources) {
configPropertySource.addChangeListener(autoUpdateConfigChangeListener);
configPropertySource.addChangeListener(configChangeEventPublisher);
}
}

Expand All @@ -165,4 +166,9 @@ static void reset() {
NAMESPACE_NAMES.clear();
AUTO_UPDATE_INITIALIZED_BEAN_FACTORIES.clear();
}

@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,50 @@
package com.ctrip.framework.apollo.spring.property;

import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.build.ApolloInjector;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.spi.ApolloConfigChangeEvent;
import com.ctrip.framework.apollo.spring.util.SpringInjector;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.gson.Gson;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.env.Environment;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.CollectionUtils;

/**
* Create by zhangzheng on 2018/3/6
*/
public class AutoUpdateConfigChangeListener implements ConfigChangeListener{
private static final Logger logger = LoggerFactory.getLogger(AutoUpdateConfigChangeListener.class);
public class AutoUpdateConfigChangeListener implements ConfigChangeListener,
ApplicationListener<ApolloConfigChangeEvent>, ApplicationContextAware {

private static final Logger logger = LoggerFactory.getLogger(
AutoUpdateConfigChangeListener.class);

private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter;
private final Environment environment;
private final ConfigurableBeanFactory beanFactory;
private final TypeConverter typeConverter;
private ConfigurableBeanFactory beanFactory;
private TypeConverter typeConverter;
private final PlaceholderHelper placeholderHelper;
private final SpringValueRegistry springValueRegistry;
private final Gson gson;
private final ConfigUtil configUtil;

public AutoUpdateConfigChangeListener(Environment environment, ConfigurableListableBeanFactory beanFactory){
public AutoUpdateConfigChangeListener() {
this.typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter();
this.beanFactory = beanFactory;
this.typeConverter = this.beanFactory.getTypeConverter();
this.environment = environment;
this.placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class);
this.springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class);
this.gson = new Gson();
this.configUtil = ApolloInjector.getInstance(ConfigUtil.class);
}

@Override
Expand Down Expand Up @@ -90,15 +97,17 @@ private void updateSpringValue(SpringValue springValue) {

/**
* Logic transplanted from DefaultListableBeanFactory
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor, java.lang.String, java.util.Set, org.springframework.beans.TypeConverter)
*
* @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor,
* java.lang.String, java.util.Set, org.springframework.beans.TypeConverter)
*/
private Object resolvePropertyValue(SpringValue springValue) {
// value will never be null, as @Value and @ApolloJsonValue will not allow that
Object value = placeholderHelper
.resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder());

if (springValue.isJson()) {
value = parseJsonValue((String)value, springValue.getGenericType());
value = parseJsonValue((String) value, springValue.getGenericType());
} else {
if (springValue.isField()) {
// org.springframework.beans.TypeConverter#convertIfNecessary(java.lang.Object, java.lang.Class, java.lang.reflect.Field) is available from Spring 3.2.0+
Expand Down Expand Up @@ -135,4 +144,19 @@ private boolean testTypeConverterHasConvertIfNecessaryWithFieldParameter() {

return true;
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//it is safe enough to cast as all known application context is derived from ConfigurableApplicationContext
this.beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
this.typeConverter = this.beanFactory.getTypeConverter();
}

@Override
public void onApplicationEvent(ApolloConfigChangeEvent event) {
if (!configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
return;
}
this.onChange(event.getConfigChangeEvent());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ctrip.framework.apollo.spring.spi;

import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import org.springframework.context.ApplicationEvent;

/**
* A Spring Application Event that is fired when Apollo config changes.
*/
public class ApolloConfigChangeEvent extends ApplicationEvent {

public ApolloConfigChangeEvent(ConfigChangeEvent source) {
super(source);
}

public ConfigChangeEvent getConfigChangeEvent() {
return (ConfigChangeEvent) getSource();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor;
import com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor;
import com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import com.ctrip.framework.apollo.spring.util.BeanRegistrationUtil;
import com.google.common.collect.Lists;
Expand Down Expand Up @@ -55,6 +56,8 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B

BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry,
AutoUpdateConfigChangeListener.class.getName(), AutoUpdateConfigChangeListener.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(),
PropertySourcesProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.ctrip.framework.apollo.core.spi.Ordered;
import com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor;
import com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor;
import com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener;
import com.ctrip.framework.apollo.spring.property.SpringValueDefinitionProcessor;
import com.ctrip.framework.apollo.spring.util.BeanRegistrationUtil;
import java.util.HashMap;
Expand All @@ -37,6 +38,8 @@ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) t

BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(),
PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry,
AutoUpdateConfigChangeListener.class.getName(), AutoUpdateConfigChangeListener.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(),
ApolloAnnotationProcessor.class);
BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2022 Apollo 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 com.ctrip.framework.apollo.spring.config;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.AbstractSpringIntegrationTest;
import com.ctrip.framework.apollo.spring.spi.ApolloConfigChangeEvent;
import com.google.common.collect.Lists;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;

public class PropertySourcesProcessorTest extends AbstractSpringIntegrationTest {

private ConfigurableEnvironment environment;
private ConfigurableListableBeanFactory beanFactory;
private PropertySourcesProcessor processor;
private MutablePropertySources propertySources;
private ApplicationEventPublisher applicationEventPublisher;

@Override
@Before
public void setUp() throws Exception {
super.setUp();
propertySources = mock(MutablePropertySources.class);
environment = mock(ConfigurableEnvironment.class);
when(environment.getPropertySources()).thenReturn(propertySources);
beanFactory = mock(ConfigurableListableBeanFactory.class);
applicationEventPublisher = mock(ApplicationEventPublisher.class);
processor = new PropertySourcesProcessor();
processor.setEnvironment(environment);
processor.setApplicationEventPublisher(applicationEventPublisher);
}

@Override
@After
public void tearDown() throws Exception {
super.tearDown();
PropertySourcesProcessor.reset();
}

@Test
public void testInitializePropertySources() {
String namespaceName = "someNamespace";
String anotherNamespaceName = "anotherNamespace";
Config config = mock(Config.class);
Config anotherConfig = mock(Config.class);
mockConfig(namespaceName, config);
mockConfig(anotherNamespaceName, anotherConfig);
PropertySourcesProcessor.addNamespaces(Lists.newArrayList(namespaceName, anotherNamespaceName),
0);

processor.postProcessBeanFactory(beanFactory);

ArgumentCaptor<CompositePropertySource> argumentCaptor = ArgumentCaptor.forClass(
CompositePropertySource.class);
verify(propertySources).addFirst(argumentCaptor.capture());

CompositePropertySource compositePropertySource = argumentCaptor.getValue();
assertEquals(2, compositePropertySource.getPropertySources().size());

ConfigPropertySource propertySource = (ConfigPropertySource) Lists.newArrayList(
compositePropertySource.getPropertySources()).get(0);
ConfigPropertySource anotherPropertySource = (ConfigPropertySource) Lists.newArrayList(
compositePropertySource.getPropertySources()).get(1);

assertEquals(namespaceName, propertySource.getName());
assertSame(config, propertySource.getSource());
assertEquals(anotherNamespaceName, anotherPropertySource.getName());
assertSame(anotherConfig, anotherPropertySource.getSource());
}

@Test
public void testApplicationEvent() {
String namespaceName = "someNamespace";
Config config = mock(Config.class);
mockConfig(namespaceName, config);
PropertySourcesProcessor.addNamespaces(Lists.newArrayList(namespaceName), 0);
ConfigChangeEvent someConfigChangeEvent = mock(ConfigChangeEvent.class);

processor.postProcessBeanFactory(beanFactory);

ArgumentCaptor<ConfigChangeListener> argumentCaptor = ArgumentCaptor.forClass(
ConfigChangeListener.class);
verify(config).addChangeListener(argumentCaptor.capture());

ConfigChangeListener listener = argumentCaptor.getValue();
listener.onChange(someConfigChangeEvent);

ArgumentCaptor<ApolloConfigChangeEvent> eventCaptor = ArgumentCaptor.forClass(
ApolloConfigChangeEvent.class);
verify(applicationEventPublisher).publishEvent(eventCaptor.capture());

ApolloConfigChangeEvent event = eventCaptor.getValue();
assertSame(someConfigChangeEvent, event.getConfigChangeEvent());
}
}

0 comments on commit 7232497

Please sign in to comment.