Skip to content

Commit

Permalink
support configuring namespaces as placeholders
Browse files Browse the repository at this point in the history
  • Loading branch information
Anilople authored Nov 22, 2020
1 parent bd388e8 commit 6ce0932
Show file tree
Hide file tree
Showing 16 changed files with 711 additions and 200 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,73 @@
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.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.property.PlaceholderHelper;
import com.ctrip.framework.apollo.spring.property.SpringValue;
import com.ctrip.framework.apollo.spring.property.SpringValueRegistry;
import com.ctrip.framework.apollo.spring.util.SpringInjector;
import com.ctrip.framework.apollo.util.ConfigUtil;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Set;

import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.util.ReflectionUtils;

/**
* Apollo Annotation Processor for Spring Application
*
* @author Jason Song(song_s@ctrip.com)
*/
public class ApolloAnnotationProcessor extends ApolloProcessor {
public class ApolloAnnotationProcessor extends ApolloProcessor implements BeanFactoryAware,
EnvironmentAware {

private static final Logger logger = LoggerFactory.getLogger(ApolloAnnotationProcessor.class);
private static final Gson GSON = new Gson();

private final ConfigUtil configUtil;
private final PlaceholderHelper placeholderHelper;
private final SpringValueRegistry springValueRegistry;

/**
* resolve the expression.
*/
private ConfigurableBeanFactory configurableBeanFactory;

private Environment environment;

public ApolloAnnotationProcessor() {
configUtil = ApolloInjector.getInstance(ConfigUtil.class);
placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class);
springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class);
}

@Override
protected void processField(Object bean, String beanName, Field field) {
this.processApolloConfig(bean, field);
this.processApolloJsonValue(bean, beanName, field);
}

@Override
protected void processMethod(final Object bean, String beanName, final Method method) {
this.processApolloConfigChangeListener(bean, method);
this.processApolloJsonValue(bean, beanName, method);
}

private void processApolloConfig(Object bean, Field field) {
ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class);
if (annotation == null) {
return;
Expand All @@ -30,15 +78,15 @@ protected void processField(Object bean, String beanName, Field field) {
Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()),
"Invalid type: %s for field: %s, should be Config", field.getType(), field);

String namespace = annotation.value();
Config config = ConfigService.getConfig(namespace);
final String namespace = annotation.value();
final String resolvedNamespace = this.environment.resolveRequiredPlaceholders(namespace);
Config config = ConfigService.getConfig(resolvedNamespace);

ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, bean, config);
}

@Override
protected void processMethod(final Object bean, String beanName, final Method method) {
private void processApolloConfigChangeListener(final Object bean, final Method method) {
ApolloConfigChangeListener annotation = AnnotationUtils
.findAnnotation(method, ApolloConfigChangeListener.class);
if (annotation == null) {
Expand All @@ -63,11 +111,15 @@ public void onChange(ConfigChangeEvent changeEvent) {
}
};

Set<String> interestedKeys = annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null;
Set<String> interestedKeyPrefixes = annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes) : null;
Set<String> interestedKeys =
annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null;
Set<String> interestedKeyPrefixes =
annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes)
: null;

for (String namespace : namespaces) {
Config config = ConfigService.getConfig(namespace);
final String resolvedNamespace = this.environment.resolveRequiredPlaceholders(namespace);
Config config = ConfigService.getConfig(resolvedNamespace);

if (interestedKeys == null && interestedKeyPrefixes == null) {
config.addChangeListener(configChangeListener);
Expand All @@ -76,4 +128,90 @@ public void onChange(ConfigChangeEvent changeEvent) {
}
}
}


private void processApolloJsonValue(Object bean, String beanName, Field field) {
ApolloJsonValue apolloJsonValue = AnnotationUtils.getAnnotation(field, ApolloJsonValue.class);
if (apolloJsonValue == null) {
return;
}
String placeholder = apolloJsonValue.value();
Object propertyValue = placeholderHelper
.resolvePropertyValue(this.configurableBeanFactory, beanName, placeholder);

// propertyValue will never be null, as @ApolloJsonValue will not allow that
if (!(propertyValue instanceof String)) {
return;
}

boolean accessible = field.isAccessible();
field.setAccessible(true);
ReflectionUtils
.setField(field, bean, parseJsonValue((String) propertyValue, field.getGenericType()));
field.setAccessible(accessible);

if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeholder);
for (String key : keys) {
SpringValue springValue = new SpringValue(key, placeholder, bean, beanName, field, true);
springValueRegistry.register(this.configurableBeanFactory, key, springValue);
logger.debug("Monitoring {}", springValue);
}
}
}

private void processApolloJsonValue(Object bean, String beanName, Method method) {
ApolloJsonValue apolloJsonValue = AnnotationUtils.getAnnotation(method, ApolloJsonValue.class);
if (apolloJsonValue == null) {
return;
}
String placeHolder = apolloJsonValue.value();

Object propertyValue = placeholderHelper
.resolvePropertyValue(this.configurableBeanFactory, beanName, placeHolder);

// propertyValue will never be null, as @ApolloJsonValue will not allow that
if (!(propertyValue instanceof String)) {
return;
}

Type[] types = method.getGenericParameterTypes();
Preconditions.checkArgument(types.length == 1,
"Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters",
bean.getClass().getName(), method.getName(), method.getParameterTypes().length);

boolean accessible = method.isAccessible();
method.setAccessible(true);
ReflectionUtils.invokeMethod(method, bean, parseJsonValue((String) propertyValue, types[0]));
method.setAccessible(accessible);

if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
Set<String> keys = placeholderHelper.extractPlaceholderKeys(placeHolder);
for (String key : keys) {
SpringValue springValue = new SpringValue(key, apolloJsonValue.value(), bean, beanName,
method, true);
springValueRegistry.register(this.configurableBeanFactory, key, springValue);
logger.debug("Monitoring {}", springValue);
}
}
}

private Object parseJsonValue(String json, Type targetType) {
try {
return GSON.fromJson(json, targetType);
} catch (Throwable ex) {
logger.error("Parsing json '{}' to type {} failed!", json, targetType, ex);
throw ex;
}
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
}

@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@
* private Config config;
* </pre>
*
* <p>Usage example with placeholder:</p>
* <pre class="code">
* // The namespace could also be specified as a placeholder, e.g. ${redis.namespace:xxx},
* // which will use the value of the key "redis.namespace" or "xxx" if this key is not configured.
* &#064;ApolloConfig("${redis.namespace:xxx}")
* private Config config;
* </pre>
*
*
* @author Jason Song(song_s@ctrip.com)
*/
@Retention(RetentionPolicy.RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
* //handle change event
* }
* <br />
* //The namespace could also be specified as a placeholder, e.g. ${redis.namespace:xxx}, which will use the value of the key "redis.namespace" or "xxx" if this key is not configured.
* &#064;ApolloConfigChangeListener({"${redis.namespace:xxx}"})
* private void onChange(ConfigChangeEvent changeEvent) {
* //handle change event
* }
* <br />
* //Listener on namespaces of "someNamespace" and "anotherNamespace", will only be notified when "someKey" or "anotherKey" is changed
* &#064;ApolloConfigChangeListener(value = {"someNamespace","anotherNamespace"}, interestedKeys = {"someKey", "anotherKey"})
* private void onChange(ConfigChangeEvent changeEvent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@
import com.ctrip.framework.apollo.spring.spi.ApolloConfigRegistrarHelper;
import com.ctrip.framework.foundation.internals.ServiceBootstrap;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

/**
* @author Jason Song(song_s@ctrip.com)
*/
public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar {
public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {

private ApolloConfigRegistrarHelper helper = ServiceBootstrap.loadPrimary(ApolloConfigRegistrarHelper.class);
private final ApolloConfigRegistrarHelper helper = ServiceBootstrap.loadPrimary(ApolloConfigRegistrarHelper.class);

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
helper.registerBeanDefinitions(importingClassMetadata, registry);
}

@Override
public void setEnvironment(Environment environment) {
this.helper.setEnvironment(environment);
}

}

This file was deleted.

Loading

0 comments on commit 6ce0932

Please sign in to comment.