- 摘要:实现思路利用Spring中BeanFactoryPostProcessor的机制,在Spring加载完配置文件完成Bean定义之后,通过读取zk中配置内容,覆盖BeanDefiniton中的propertyvalues实现过程配置相关注解定义@Config注解用于说明该类为配置类用于存储相关配置信息@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceC
- 实现思路
利用Spring中BeanFactoryPostProcessor的机制,在Spring加载完配置文件完成Bean 定义之后,通过读取zk中配置内容,覆盖Bean Definiton中的property values
配置相关注解定义
@Config 注解用于说明该类为配置类用于存储相关配置信息
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Config { } 复制代码
@ConfigField 注解用于标注类的属性字段对应配置中的配置项
自定义BeanFactoryPostProcessor, 此处实现了接口beanDefinitionRegistryPostProcessor@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ConfigField { String key(); } 复制代码
该类主要用于实现方法 postProcessBeanDefinitionRegistry;其核心代码如下
// 获取spring解析后的Bean 定义信息 for (String beanName : beanDefinitionRegistry.getBeanDefinitionNames()) { BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(beanName); String className = beanDefinition.getBeanClassName(); Class<?> clazz = Class.forName(className); // 判断该类是否为配置类 if (isConfigBean(clazz)) { // path 为 zk中应用对应节点 /config/app/env String configPath = path + "/" + className; Stat stat = curatorFramework.checkExists().forPath(configPath); if (stat != null) { String value = new String(curatorFramework.getData().forPath(configPath)); JSONObject jsonObject = JSONObject.parseObject(value); // 获取配置类中字段与配置项key的对应 Map configFieldMap = findConfigFieldAnnation(clazz); for (String key : jsonObject.keySet()) { String propertyValue = jsonObject.getString(key); String property = configFieldMap.get(key); // 采用zk中的值覆盖到 Bean Definiton propertyValue beanDefinition.getPropertyValues().add(property, propertyValue); } log.info("Hxlzp config register bean : {}, property values : {}", className, value); } } } 复制代码
判断类是否为配置类
private boolean isConfigBean(Class<?> clazz) { return clazz.getAnnotation(Config.class) != null; } 复制代码
获取配置类的属性与配置项的对应关系
zk 节点定义private Map findConfigFieldAnnation(Class<?> clazz) { Map map = new HashMap (); for (Field field : clazz.getDeclaredFields()) { ConfigField configField = field.getAnnotation(ConfigField.class); if (configField != null) { map.put(configField.key(), field.getName()); } } return map; }复制代码
如下图所示:二级为应用节点, 三级为应用的所属环境节点,四级为应用的各个环境下的配置节点,配置节点约束节点名必须与配置类的类名保持一致
zk 配置节点的数据格式
配置节点数据采用json格式;如下
{
"db.username" : "",
"db.password" : "",
"db.url" : ""
}
使用示例
定义DB配置类
定义类 DbConfig, 用于记录Db相关配置
定义DataSource@Data @Config @Component("dbConfig") public class DbConfig { @ConfigField(key = "db.url") private String url; @ConfigField(key = "db.username") private String username; @ConfigField(key = "db.password") private String password; } 复制代码
此处采用 Druid作为数据源
Spring 配置@Slf4j @Data public class ExampleDatasource extends DruidDataSource { @Resource private DbConfig dbConfig; /** * 重写init;实现db配置后初始化 */ public void init () throws SQLException { if (!this.dbConfig.getUrl().equals(this.getUrl()) || !this.dbConfig.getUsername().equals(this.getUsername()) || !this.dbConfig.getPassword().equals(this.getPassword())) { // 說明首次初始化 或 db配置發生變化 此处也即实现了在无需重启的情况下完成db数据源的动态切换 this.inited = false; } if (inited) { // 因 getConnection 的時候 再次調用了init 需判斷是否已初始化過 return; } if (dbConfig != null) { log.info("datasource set db config , {}", dbConfig.getUrl()); this.setUrl(dbConfig.getUrl()); this.setUsername(dbConfig.getUsername()); this.setPassword(dbConfig.getPassword()); super.init(); } } } 复制代码
配置文件中需定义 上文中 BeanDefinitionRegistryPostProcessor; 示例如下
init-method="init" destroy-method="close">
测试
可以通过从ApplicationContext 中获取dao执行简单的操作验证; 也可以修改zk中节点配置数据 验证数据源的动态切换
以上是ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); classPathXmlApplicationContext.start(); classPathXmlApplicationContext.getBean(UserDao.class).getById(1); 复制代码
转载于:https://juejin.im/post/5c4808c2e51d452ec621b457