Apollo热发布配置

一、背景 项目中需要用的阿波罗热发布,一种是利用注解ApolloConfigChangeListener的方式,可用如下配置就可。

@Component @Slf4j public class ApolloRefreshConfig2implements ApplicationContextAware{@Autowired private RefreshScope refreshScope; private ApplicationContext applicationContext; @ApolloConfigChangeListener() public void onChange(ConfigChangeEvent changeEvent) { refreshProperties(changeEvent); }private void refreshProperties(ConfigChangeEvent changeEvent) { this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); refreshScope.refreshAll(); }@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }

可是在测试中发现热部署并没有更新,后查看注解ApolloConfigChangeListener,发现如果不指定value值则默认namespace为 application。而我们项目中并没有使用application,而是拆分出来更多的命名空间;所以并不适用,需要指定value,如value=https://www.it610.com/article/{"edl-pub-service-common","Java.bmc-local-eureka","Java.bmc-pub-mysql","Java.bmc-redis","Java.bmc-rabbitmq","Java.bmc-httplog"};
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface ApolloConfigChangeListener { /** * Apollo namespace for the config, if not specified then default to application */ String[] value() default {ConfigConsts.NAMESPACE_APPLICATION}; String[] interestedKeys() default {}; String[] interestedKeyPrefixes() default {}; }

这里放一下我们的配置文件
# 应用ID(在Apollo服务端新增项目添加的应用ID) app: id: bmc # apollo-configservice地址 apollo: meta: http://10.197.236.187:7070 bootstrap: enabled: true namespaces: edl-pub-service-common,Java.bmc-local-eureka,Java.bmc-pub-mysql,Java.bmc-redis,Java.bmc-rabbitmq,Java.bmc-httplog eagerLoad: enabled: true

可以不想再value中硬编码,那么问题来了:能否将value中的动态配置,比如读取配置文件中的数据?
二、尝试 1.@ApolloConfigChangeListener()注解的value直接设置EL表达式,结果------失败。
【Apollo热发布配置】如下代码设置
@ApolloConfigChangeListener(value = "https://www.it610.com/article/${apollo.bootstrap.namespacesArr}") public void onChange(ConfigChangeEvent changeEvent) { refreshProperties(changeEvent); }

经过debug尝试,发现Apollo会默认注册 AutoUpdateConfigChangeListener监听器,如果扫描到ApolloConfigChangeListener注解也会注册到监听器中(源码见com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor),可是在获取此注解的时候并没有判断是不是EL表达式,所以你传入 {apollo.bootstrap.namespacesArr};代码如下
@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); ReflectionUtils.makeAccessible(method); // *********** 重点在这里 ,直接读取 *********** String[] namespaces = annotation.value(); String[] annotatedInterestedKeys = annotation.interestedKeys(); String[] annotatedInterestedKeyPrefixes = annotation.interestedKeyPrefixes(); ConfigChangeListener configChangeListener = new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { ReflectionUtils.invokeMethod(method, bean, changeEvent); } }; Set interestedKeys = annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null; Set interestedKeyPrefixes = annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes) : null; for (String namespace : namespaces) { Config config = ConfigService.getConfig(namespace); if (interestedKeys == null && interestedKeyPrefixes == null) { config.addChangeListener(configChangeListener); } else { config.addChangeListener(configChangeListener, interestedKeys, interestedKeyPrefixes); } } }

所以该我们配置文件的属性改变因为不是指定的namespaces就不会更新。
2.利用反射动态修改@ApolloConfigChangeListener()注解的value,将value赋值为配置的属性,结果------失败。
此方法明显行不通,因为ApolloAnnotationProcessor中每次获取的ApolloConfigChangeListener 开始设置的值,而不是通过我们反射获取的值,反射的主要代码如下
try { method = ApolloRefreshConfig.class.getMethod("onChange", ConfigChangeEvent.class); ApolloConfigChangeListener annotation = method.getAnnotation(ApolloConfigChangeListener.class); InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation); Field value = https://www.it610.com/article/invocationHandler.getClass().getDeclaredField("memberValues"); value.setAccessible(true); Map stringObjectMap = (Map) value.get(invocationHandler); stringObjectMap.put("value", namespacesArr); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }

3.新写监听器,加入监听器集合中,结果------失败。
因为始终读取不到配置文件,猜测跟ApolloProcessor 继承 BeanPostProcessor有关吧。
@Component @Slf4j public class ApolloRefreshListener extends ApolloProcessor implements ApplicationContextAware {@Autowired private RefreshScope refreshScope; @Value("${apollo.bootstrap.namespaces}") private String namespaces; private ApplicationContext applicationContext; @Autowired private ConfigPropertySourceFactory configPropertySourceFactory; /*@PostConstruct private void init(){ List namespacesList = Lists.newArrayList(); Splitter.on(",").omitEmptyStrings().split(namespaces).forEach(item -> namespacesList.add(item)); namespacesArr = new String[namespacesList.size()]; }*/@Override protected void processField(Object bean, String beanName, Field field) { ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class); if (annotation == null) { return; }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); ReflectionUtils.makeAccessible(field); ReflectionUtils.setField(field, bean, config); }@Override protected void processMethod(final Object bean, String beanName, final Method method) {ApolloRefreshConfig apolloRefreshListener = new ApolloRefreshConfig(); List allConfigPropertySources = configPropertySourceFactory.getAllConfigPropertySources(); for (ConfigPropertySource allConfigPropertySource : allConfigPropertySources) { Config config = ConfigService.getConfig(allConfigPropertySource.getName()); config.addChangeListener(apolloRefreshListener); } }@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }}

三、结论 暂无解决办法,目前只有写死。

    推荐阅读