什么是ACM?
阿里云ACM是阿里云提供的一款远程应用配置参数管理的服务,通过线上配置应用配置的方式(数据库连接,redis连接等)来集中管理项目中的配置参数,达到配置集中管理和即时应用的目的(PS:我的理解)。
如何基于springframwork的框架下集成ACM达到配置集中管理的目的?下面就是我能提供给大家的一些建议。
思路
说实话,拿数据库连接举例,最开始我的实现思路是从ACM上获取应用配置后,通过载体类的形式将获取到的参数反向注入到springXML文件中的datasource配置参数中,果不其然,这种骚操作是行不通的。即使成功了,还需要注入redis等很多其它配置。然后又想到了从properties文件入手,不用spring原来的PropertyPlaceholderConfigurer注入方式,自己继承PropertyPlaceholderConfigurer来修改整个容器中预加载的配置参数,这样既不用修改现有代码通过springMVC@Value()注入的方式来获取值,还能从ACM上拿到远程数据库连接等配置,实践证明这种做法是可行的。
具体步骤
修改前的步骤:项目启动===>>PropertyPlaceholderConfigurer加载本地参数和JAVA系统参数(本项目的容器配置参数)整合===>>Spring接管应用配置参数===>>项目使用配置参数
修改后的步骤:项目启动===>>ACM获取应用配置参数===>>PropertyPlaceholderConfigurer(自实现子类)加载本地properties文件参数、ACM配置参数、JAVA系统参数(本项目的容器配置参数)整合===>>Spring接管应用配置参数===>>项目使用配置参数
因为与原先框架的流程相差仅有一个步骤的不同,所以只需要修改很少的代码就可以达到集成ACM的目的,接下来就一步步地实现我们的思路。
如何从ACM上获取应用配置参数
如果仔细阅读阿里云ACM服务技术文档,那么从ACM上获取我们的线上配置是一件很容易的事。基于我的项目后端语言使用的JAVA以及springframwork框架,不需要使用nacos spring(我的目的是从项目启动时从ACM上获取最新的应用配置参数,spring并不支持代码热部署,配置修改重启应用完全没毛病,所以选择了原生的java native sdk),比如使用JAVA NATIVE SDK来实现从线上获取应用配置参数:
private static Properties acmProperties = new Properties();
public static void main(String[] args) {
try {
// 从控制台命名空间管理中拷贝对应值
Properties properties = new Properties();
properties.put("endpoint", "$endpoint");
properties.put("namespace", "$namespace");
// 通过 ECS 实例 RAM 角色访问 ACM
// properties.put("ramRoleName", "$ramRoleName");
properties.put("accessKey", "$accessKey");
properties.put("secretKey", "$secretKey");
// 如果是加密配置,则添加下面两行进行自动解密
//properties.put("openKMSFilter", true);
//properties.put("regionId", "$regionId");
ConfigService.init(properties);
// 初始化的时候,给配置添加监听,配置变更会回调通知
ConfigService.addListener("${dataId}", "${group}", new PropertiesListener() {
@Override
public void innerReceive(Properties properties) {
// TODO Auto-generated method stub
acmProperties = properties;
System.out.println(properties);
}
});
} catch (ConfigException e) {
e.printStackTrace();
}
PropertyPlaceholderConfigurer(自实现子类)
整个类也是整合代码的关键类,唯一类。(不然我怎么说修改的代码很少呢!嚯嚯嚯),具体操作是继承该类,重写mergeProperties()方法,然后注入到Spring中,让spring接管所有配置参数。
public class GloablePropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer{
protected final static String CLASS_PACKAGE_PATH = "smart/assistant/console/gateway/acmconfig/";
protected final static String PROPERTIES_PACKAGE_PATH = "conf";
@Override
public Properties mergeProperties()
{
String path = this.getClass().getResource("").getPath()+"/important.properties";
path = path.replaceAll(CLASS_PACKAGE_PATH, PROPERTIES_PACKAGE_PATH);
Properties props=new Properties();
try {
props.load(new FileInputStream(path));
//获取ACM调用参数
String endpoint = props.getProperty(END_POINT);
String namespace = props.getProperty(NAME_SPACE);
String accessKey = props.getProperty(ACCESSKEY);
String secretKey = props.getProperty(SECRETKEY);
String dataId = props.getProperty(DATA_ID);
if(StringUtils.isNotBlank(System.getenv().get(DATA_ID)))
dataId = System.getenv().get(DATA_ID);//区别开发/测试/生产的ACM配置组
String groupId = props.getProperty(GROUP_ID);
//异步调用
receiveProperties(endpoint, namespace, accessKey, secretKey, dataId, groupId);
//如果不为空一直循环判断,等待返回结果
while(true){
if(!acmProperties.isEmpty())
break;
}
//将本地properties配置和ACM配置统一
for(Entry<Object, Object> entry:acmProperties.entrySet()){
props.put(entry.getKey(), entry.getValue());
}
return props;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//////////////////////////////获取配置////////////////////////////////////////////
protected final static String END_POINT = "acm.endpoint";
protected final static String NAME_SPACE = "acm.namespace";
protected final static String ACCESSKEY = "acm.accessKey";
protected final static String SECRETKEY = "acm.secretKey";
protected final static String DATA_ID = "acm.dataId";
protected final static String GROUP_ID = "acm.groupId";
//阿里云ACM控制台配置参数
public static Properties acmProperties = new Properties();
protected static void receiveProperties(String endpoint,String namespace,
String accessKey,String secretKey,String dataId,String groupId) {
// 从控制台命名空间管理中拷贝对应值
Properties properties = new Properties();
properties.put("endpoint", endpoint);
properties.put("namespace", namespace);
// 通过 ECS 实例 RAM 角色访问 ACM
properties.put("accessKey", accessKey);
properties.put("secretKey", secretKey);
// 如果是加密配置,则添加下面两行进行自动解密
//properties.put("openKMSFilter", true);
//properties.put("regionId", "$regionId");
ConfigService.init(properties);
// 主动获取配置
// 初始化的时候,给配置添加监听,配置变更会回调通知
ConfigService.addListener(dataId, groupId, new PropertiesListener() {
@Override
public void innerReceive(Properties properties) {
// 当配置更新后,通过该回调函数将最新值返回给用户。
// 注意回调函数中不要做阻塞操作,否则阻塞通知线程。
acmProperties = properties;
}
});
}
}
上面代码中循环是因为获取ACM参数是一个异步调用,我们需要等待调用返回的结果(踩坑,为什么一直拿不到参数QAQ!!!)在ConfigService.addListener()方法中,框架会一直监听整个ACM的配置,如果有更改就会通知给应用,但是对我来说Spring重载properties容器貌似还不能做到这样,所以没有什么卵用,但如果是SpringBoot或Spring Cloud支持代码热部署,可以reload数据库连接就会很有用。
Spring接管应用配置参数
不同于PropertyPlaceholderConfigurer原来在Spring.xml中的配置,我通过获取配置文件的绝对路径来加载配置文件的配置,因为我们不在依赖spring的配置了,(如果依赖就不实现我们自己想要的结果)。
<!-- <bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<value>classpath:conf/important.properties</value>
</list>
</property>
</bean> -->
<bean id="propertyConfigurer"
class="smart.assistant.console.gateway.acmconfig.GloablePropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
<property name="ignoreResourceNotFound" value="true"/>
<property name="localOverride" value="true"/>
</bean>
上面的配置把原来的locations注释掉,因为我们要整合整个配置文件里的配置参数和ACM的配置参数以及System.getenv()里的环境变量参数,新的配置也列在下面了。
Properties文件配置
#ACM
acm.endpoint=你在阿里云上的acm.endpoint
acm.namespace=你在阿里云上的acm.namespace
acm.accessKey=你在阿里云上的acm.accessKey
acm.secretKey=你在阿里云上的acm.secretKey
acm.dataId=你在阿里云上的acm.dataId
acm.groupId=你在阿里云上的acm.groupId
虽然,ACM接管了我们大部分的配置参数,但是我们还是少不了properties文件,因为我们还是需要参数去连接ACM,所以配置文件的配置内容虽然很少,但是也不能缺少。
最后我们就能像没有发什么事儿一样,以前如何写代码就如何写代码,完全不用关心会出什么问题,唯一不同的是我们的配置在ACM上集中管理起来,ACM的配置会覆盖本地配置文件的配置。开发/测试/生产三套不同的配置只需要一个dataId就可以满足,真是perfect!
最后的最后吐槽一句,网上关于nacos中间件的文章太少了,所以我也是摸爬滚打了大概三天才解决了这个问题。现在终于舒服了(#^.^#)!!!!还有就是很多都是直接把别人的文章抄过来,看了很多文章,内容都差不多甚至一模一样!ε=(´ο`*)))唉。