-
-
Notifications
You must be signed in to change notification settings - Fork 10.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #989 from zhangzheng88/json-supported
1. Support injecting Json values with @ApolloJsonValue, the supported format is the same as Spring @value, such as @ApolloJsonValue("${someJsonPropertyKey}") 2. Support auto updating injected values for @ApolloJsonValue
- Loading branch information
Showing
18 changed files
with
917 additions
and
318 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 40 additions & 62 deletions
102
...src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloAnnotationProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,89 +1,67 @@ | ||
package com.ctrip.framework.apollo.spring.annotation; | ||
|
||
import java.lang.reflect.Field; | ||
import java.lang.reflect.Method; | ||
|
||
import org.springframework.beans.BeansException; | ||
import org.springframework.beans.factory.config.BeanPostProcessor; | ||
import org.springframework.core.Ordered; | ||
import org.springframework.core.PriorityOrdered; | ||
import org.springframework.core.annotation.AnnotationUtils; | ||
import org.springframework.util.ReflectionUtils; | ||
|
||
import com.ctrip.framework.apollo.Config; | ||
import com.ctrip.framework.apollo.ConfigChangeListener; | ||
import com.ctrip.framework.apollo.ConfigService; | ||
import com.ctrip.framework.apollo.model.ConfigChangeEvent; | ||
import com.google.common.base.Preconditions; | ||
import java.lang.reflect.Field; | ||
import java.lang.reflect.Method; | ||
import org.springframework.core.annotation.AnnotationUtils; | ||
import org.springframework.util.ReflectionUtils; | ||
|
||
/** | ||
* Apollo Annotation Processor for Spring Application | ||
* | ||
* @author Jason Song([email protected]) | ||
*/ | ||
public class ApolloAnnotationProcessor implements BeanPostProcessor, PriorityOrdered { | ||
@Override | ||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { | ||
Class clazz = bean.getClass(); | ||
processFields(bean, clazz.getDeclaredFields()); | ||
processMethods(bean, clazz.getDeclaredMethods()); | ||
return bean; | ||
} | ||
public class ApolloAnnotationProcessor extends ApolloProcessor { | ||
|
||
@Override | ||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { | ||
return bean; | ||
} | ||
protected void processField(Object bean, String beanName, Field field) { | ||
ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class); | ||
if (annotation == null) { | ||
return; | ||
} | ||
|
||
private void processFields(Object bean, Field[] declaredFields) { | ||
for (Field field : declaredFields) { | ||
ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class); | ||
if (annotation == null) { | ||
continue; | ||
} | ||
Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()), | ||
"Invalid type: %s for field: %s, should be Config", field.getType(), 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); | ||
|
||
String namespace = annotation.value(); | ||
Config config = ConfigService.getConfig(namespace); | ||
ReflectionUtils.makeAccessible(field); | ||
ReflectionUtils.setField(field, bean, config); | ||
} | ||
|
||
ReflectionUtils.makeAccessible(field); | ||
ReflectionUtils.setField(field, bean, config); | ||
@Override | ||
protected void processMethod(final Object bean, String beanName, final Method method) { | ||
ApolloConfigChangeListener annotation = AnnotationUtils | ||
.findAnnotation(method, ApolloConfigChangeListener.class); | ||
if (annotation == null) { | ||
return; | ||
} | ||
} | ||
Class<?>[] parameterTypes = method.getParameterTypes(); | ||
Preconditions.checkArgument(parameterTypes.length == 1, | ||
"Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length, | ||
method); | ||
Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]), | ||
"Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0], | ||
method); | ||
|
||
private void processMethods(final Object bean, Method[] declaredMethods) { | ||
for (final Method method : declaredMethods) { | ||
ApolloConfigChangeListener annotation = AnnotationUtils.findAnnotation(method, ApolloConfigChangeListener.class); | ||
if (annotation == null) { | ||
continue; | ||
ReflectionUtils.makeAccessible(method); | ||
String[] namespaces = annotation.value(); | ||
ConfigChangeListener configChangeListener = new ConfigChangeListener() { | ||
@Override | ||
public void onChange(ConfigChangeEvent changeEvent) { | ||
ReflectionUtils.invokeMethod(method, bean, changeEvent); | ||
} | ||
}; | ||
|
||
Class<?>[] parameterTypes = method.getParameterTypes(); | ||
Preconditions.checkArgument(parameterTypes.length == 1, | ||
"Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length, method); | ||
Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]), | ||
"Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0], method); | ||
|
||
ReflectionUtils.makeAccessible(method); | ||
String[] namespaces = annotation.value(); | ||
for (String namespace : namespaces) { | ||
Config config = ConfigService.getConfig(namespace); | ||
for (String namespace : namespaces) { | ||
Config config = ConfigService.getConfig(namespace); | ||
|
||
config.addChangeListener(new ConfigChangeListener() { | ||
@Override | ||
public void onChange(ConfigChangeEvent changeEvent) { | ||
ReflectionUtils.invokeMethod(method, bean, changeEvent); | ||
} | ||
}); | ||
} | ||
config.addChangeListener(configChangeListener); | ||
} | ||
} | ||
|
||
@Override | ||
public int getOrder() { | ||
//make it as late as possible | ||
return Ordered.LOWEST_PRECEDENCE; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
...lo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJsonValue.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.ctrip.framework.apollo.spring.annotation; | ||
|
||
import java.lang.annotation.Documented; | ||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
/** | ||
* Use this annotation to inject json property from Apollo, support the same format as Spring @Value. | ||
* | ||
* <p>Usage example:</p> | ||
* <pre class="code"> | ||
* // Inject the json property value for type SomeObject. | ||
* // Suppose SomeObject has 2 properties, someString and someInt, then the possible config | ||
* // in Apollo is someJsonPropertyKey={"someString":"someValue", "someInt":10}. | ||
* @ApolloJsonValue("${someJsonPropertyKey:someDefaultValue}") | ||
* private SomeObject someObject; | ||
* </pre> | ||
* | ||
* Create by zhangzheng on 2018/3/6 | ||
* | ||
* @see org.springframework.beans.factory.annotation.Value | ||
*/ | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target({ElementType.FIELD, ElementType.METHOD}) | ||
@Documented | ||
public @interface ApolloJsonValue { | ||
|
||
/** | ||
* The actual value expression: e.g. "${someJsonPropertyKey:someDefaultValue}". | ||
*/ | ||
String value(); | ||
} |
125 changes: 125 additions & 0 deletions
125
.../src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloJsonValueProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package com.ctrip.framework.apollo.spring.annotation; | ||
|
||
import com.ctrip.framework.apollo.build.ApolloInjector; | ||
import com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener; | ||
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.util.ConfigUtil; | ||
import com.google.common.base.Preconditions; | ||
import com.google.gson.Gson; | ||
import com.google.gson.reflect.TypeToken; | ||
import java.lang.reflect.Field; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Type; | ||
import java.util.Set; | ||
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.core.annotation.AnnotationUtils; | ||
import org.springframework.util.ReflectionUtils; | ||
|
||
/** | ||
* Create by zhangzheng on 2018/2/6 | ||
*/ | ||
public class ApolloJsonValueProcessor extends ApolloProcessor implements BeanFactoryAware { | ||
|
||
private static final Logger logger = LoggerFactory.getLogger(ApolloJsonValueProcessor.class); | ||
private static final Gson gson = new Gson(); | ||
|
||
private final ConfigUtil configUtil; | ||
private final PlaceholderHelper placeholderHelper; | ||
private final SpringValueRegistry springValueRegistry; | ||
private ConfigurableBeanFactory beanFactory; | ||
|
||
public ApolloJsonValueProcessor() { | ||
configUtil = ApolloInjector.getInstance(ConfigUtil.class); | ||
placeholderHelper = ApolloInjector.getInstance(PlaceholderHelper.class); | ||
springValueRegistry = ApolloInjector.getInstance(SpringValueRegistry.class); | ||
} | ||
|
||
@Override | ||
protected void processField(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(beanFactory, 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(key, springValue); | ||
logger.debug("Monitoring {}", springValue); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
protected void processMethod(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(beanFactory, 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(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.beanFactory = (ConfigurableBeanFactory) beanFactory; | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
...lo-client/src/main/java/com/ctrip/framework/apollo/spring/annotation/ApolloProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package com.ctrip.framework.apollo.spring.annotation; | ||
|
||
import java.lang.reflect.Field; | ||
import java.lang.reflect.Method; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import org.springframework.beans.BeansException; | ||
import org.springframework.beans.factory.config.BeanPostProcessor; | ||
import org.springframework.core.Ordered; | ||
import org.springframework.core.PriorityOrdered; | ||
import org.springframework.util.ReflectionUtils; | ||
|
||
/** | ||
* Create by zhangzheng on 2018/2/6 | ||
*/ | ||
public abstract class ApolloProcessor implements BeanPostProcessor, PriorityOrdered { | ||
|
||
@Override | ||
public Object postProcessBeforeInitialization(Object bean, String beanName) | ||
throws BeansException { | ||
Class clazz = bean.getClass(); | ||
for (Field field : findAllField(clazz)) { | ||
processField(bean, beanName, field); | ||
} | ||
for (Method method : findAllMethod(clazz)) { | ||
processMethod(bean, beanName, method); | ||
} | ||
return bean; | ||
} | ||
|
||
@Override | ||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { | ||
return bean; | ||
} | ||
|
||
/** | ||
* subclass should implement this method to process field | ||
*/ | ||
protected abstract void processField(Object bean, String beanName, Field field); | ||
|
||
/** | ||
* subclass should implement this method to process method | ||
*/ | ||
protected abstract void processMethod(Object bean, String beanName, Method method); | ||
|
||
|
||
@Override | ||
public int getOrder() { | ||
//make it as late as possible | ||
return Ordered.LOWEST_PRECEDENCE; | ||
} | ||
|
||
private List<Field> findAllField(Class clazz) { | ||
final List<Field> res = new LinkedList<>(); | ||
ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() { | ||
@Override | ||
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { | ||
res.add(field); | ||
} | ||
}); | ||
return res; | ||
} | ||
|
||
private List<Method> findAllMethod(Class clazz) { | ||
final List<Method> res = new LinkedList<>(); | ||
ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() { | ||
@Override | ||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { | ||
res.add(method); | ||
} | ||
}); | ||
return res; | ||
} | ||
} |
Oops, something went wrong.