接入Apollo后,我们可以通过注解方式一行注解,即可获取所需的配置信息(由于Apollo支持热部署,能够实现动态加载)
1.定义用来标识Apolo配置的注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface ApolloValue {
String value() default "";
}
2.定义映射配置信息的对象
import lombok.Data;
@Data
public class OwlApolloConfigBean {
@ApolloValue("demo.demo")
private String demo;
}
3.接入Apollo,获取配置信息,并通过监听事件,实现动态加载。
/**
* @author XingPengLong
* @date 2018-06-13 下午9:10.
*/
@Slf4j
@Configuration
@EnableApolloConfig
public class OwlApolloConfig {
private static Config appConfig;
private static final String APOLLO_KEY = "owlApolloConfigBean";
private static ConcurrentHashMap<String, OwlApolloConfigBean> concurrentHashMap = new ConcurrentHashMap<>();
static {
//config instance is singleton for each namespace and is never null
appConfig = ConfigService.getAppConfig();
appConfig.addChangeListener(changeEvent -> {
for (String key : changeEvent.changedKeys()) {
log.info("OwlApolloConfig#static before concurrentHashMap:{}", JSON.toJSONString(concurrentHashMap));
ConfigChange change = changeEvent.getChange(key);
OwlApolloConfigBean owlApolloConfigBean = concurrentHashMap.get(APOLLO_KEY);
switch (change.getChangeType()) {
case MODIFIED:
String propertyName = change.getPropertyName();
String newValue = change.getNewValue();
if (StringUtils.isEmpty(newValue)) {
break;
}
Field[] declaredFields = owlApolloConfigBean.getClass().getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
Annotation[] declaredAnnotations = field.getDeclaredAnnotations();
for (Annotation annotation : declaredAnnotations) {
if (annotation instanceof ApolloValue) {
String value = ((ApolloValue) annotation).value();
if (propertyName.equals(value)) {
try {
field.set(owlApolloConfigBean, change.getNewValue());
} catch (IllegalAccessException e) {
log.error("set field meet error:" + field.getName());
}
}
break;
}
}
}
break;
case ADDED:
break;
case DELETED:
break;
default:
return;
}
log.info("OwlApolloConfig#static after concurrentHashMap:{}", JSON.toJSONString(concurrentHashMap));
}
});
}
@Bean
public OwlApolloConfigBean getOwlApolloConfigBean() {
Set<String> propertyNames = appConfig.getPropertyNames();
OwlApolloConfigBean owlApolloConfigBean = concurrentHashMap.get(APOLLO_KEY);
if (owlApolloConfigBean == null) {
owlApolloConfigBean = new OwlApolloConfigBean();
}
Field[] declaredFields = owlApolloConfigBean.getClass().getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
Annotation[] declaredAnnotations = field.getDeclaredAnnotations();
for (Annotation annotation : declaredAnnotations) {
if (annotation instanceof ApolloValue) {
String value = ((ApolloValue) annotation).value();
if (propertyNames.contains(value)) {
try {
field.set(owlApolloConfigBean, appConfig.getProperty(value, ""));
} catch (IllegalAccessException e) {
log.error("set field meet error:" + field.getName());
}
}
break;
}
}
}
log.info("OwlApolloConfig#getOwlApolloConfigBean concurrentHashMap:{}", JSON.toJSONString(concurrentHashMap));
concurrentHashMap.putIfAbsent(APOLLO_KEY, owlApolloConfigBean);
log.info("OwlApolloConfig#getOwlApolloConfigBean concurrentHashMap:{}", JSON.toJSONString(concurrentHashMap));
return owlApolloConfigBean;
}
}
4.使用
@RestController
public class DemoController {
@Autowired
private OwlApolloConfig owlApolloConfig;
@GetMapping(value = "/")
public PlainResult<String> getOwlApolloConfigBeanDemo() {
String demo = owlApolloConfig.getOwlApolloConfigBean().getDemo();
return PlainResultBuilder.build(demo);
}
}
5.总结
以上方法仅仅实现了注解接入Apollo,后续有更好的办法,如映射对象分组、无缝加入到Spring容器等。待续。
优化:2018/11/05 加入Spring容器管理
1.定义配置信息映射的对象
@ConfigurationProperties("gciBiz") @Getter @Setter public class GciBizProperties { private String demo; } |
2.通过Spring提供的 ApplicationReadyEvent 钩子,拿到通过ConfigurationProperties 标记的类对象,测试发现这些类均是以 (类型-类所在包的全路径)的格式由Spring容器进行管理。
/** * Apollo服务配置 * 注:暂时仅支持以逗号分隔的两级嵌套的结构 * * @author XingPengLong * @date 2018-09-19 下午5:26. */ @EnableConfigurationProperties(GciBizProperties.class) @Slf4j @Configuration @EnableApolloConfig public class ApolloConfiguration implements ApplicationListener<ApplicationReadyEvent> { private static final String COM_YOUZAN_OWL_GCI_BIZ_CONFIG_BEAN_APOLLO = "-com.youzan.owl.gci.biz.config.bean.apollo"; private static final String PROPERTY_NAME_REGEX = "\\."; private ApplicationContext applicationContext; private void init() { Config appConfig = ConfigService.getAppConfig(); appConfig.addChangeListener((ConfigChangeEvent changeEvent) -> { for (String key : changeEvent.changedKeys()) { ConfigChange change = changeEvent.getChange(key); switch (change.getChangeType()) { case MODIFIED: wrapperModifier(change); break; case ADDED: break; case DELETED: break; default: return; } } }); } private void wrapperModifier(ConfigChange change) { String propertyName = change.getPropertyName(); if (StringUtils.isEmpty(change.getNewValue())) { return; } String[] split = propertyName.split(PROPERTY_NAME_REGEX); //暂时仅支持两级嵌套 if (split.length == 2) { applicationContext.getBeansWithAnnotation(ConfigurationProperties.class).entrySet().stream() .filter(k -> k.getKey().startsWith(split[0] + COM_YOUZAN_OWL_GCI_BIZ_CONFIG_BEAN_APOLLO)) .forEach(item -> setAvailableFieldValue(change, split[1], item)); } } private void setAvailableFieldValue(ConfigChange change, String anObject, Map.Entry<String, Object> item) { Field[] declaredFields = item.getValue().getClass().getDeclaredFields(); for (Field field : declaredFields) { field.setAccessible(true); if (field.getName().equals(anObject)) { try { field.set(item.getValue(), change.getNewValue()); } catch (IllegalAccessException i) { log.error("setAvailableFieldValue meet error:" + field.getName(), i); } break; } } log.info("ApolloConfiguration {}", JSON.toJSONString(item.getValue())); } @Override public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { if (applicationReadyEvent.getApplicationContext().getParent() == null) { this.applicationContext = applicationReadyEvent.getApplicationContext(); init(); } } } |
3.使用
@RestController public class DemoController { @Autowired private GciBizProperties gciBizProperties; @GetMapping(value = "/test") public String test() { return gciBizProperties.getDemo(); } |
暂时仅支持以逗号分隔的两级嵌套的结构,比较僵硬,未完待续。。。