一、功能描述
基于nacos管理配置服务,这里借助starter机制与nacos,mysql一起配合管理非核心业务的配置类数据记录,基于json存储,简化业务配置类数据的管理。
二、实现原理
通过自定义注解将实体数据与nacos配置数据进行绑定动态与nacos进行交互,并解析数据,通过接口访问。
2.1 配置说明
nacos:
#:存储于数据库中的配置数据
datajson:
#:扫描配置的枚举entity模型
entitypath: com.coderman.common.starter.entity
#:如果数据库里有配置表,则通过这个配置开启回调接口,见示例:com.coderman.common.starter.service.ConfigCallBackServiceImpl
#:进行回调之后则自动进行配置数据注册,后续直接在nacos中进行配置
usecallback: true
2.2 代码实现
- 配置实体属性注解
/**
* 标注entity的属性注解
* @author fanchunshuai
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ConfigField {
/**
* 字段描述
* @return
*/
String desc() default "";
/**
* 是否作为查询key
* @return
*/
boolean queryKey() default false;
}
- 配置实体注解
/**
* 标注entity注解
* @author fanchunshuai
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface NacosConfig {
/**
* 对应nacos的dataId
* @return
*/
String dataId() default "";
/**
* 对应nacos的groupId
* @return
*/
String groupId() default "";
/**
* 对应nacos的描述
* @return
*/
String desc() default "";
}
- 配置属性注解对应的bean
public class ConfigFieldBean {
/**
* 属性名
*/
private String fieldName;
/**
* 字段描述
* @return
*/
private String desc;
/**
* 是否作为查询key
* @return
*/
private boolean queryKey;
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public boolean isQueryKey() {
return queryKey;
}
public void setQueryKey(boolean queryKey) {
this.queryKey = queryKey;
}
}
- 配置实体注解对应的bean
public class NacosConfigBean {
/**
* 对应nacos的dataId
* @return
*/
private String dataId;
/**
* 对应nacos的groupId
* @return
*/
private String groupId;
/**
* 对应nacos的描述
* @return
*/
private String desc;
/**
* 简单类名
*/
private String classSimpleName;
/**
* 模型类class
*/
private Class<?> clazz;
public Class<?> getClazz() {
return clazz;
}
public void setClazz(Class<?> clazz) {
this.clazz = clazz;
}
private List<ConfigFieldBean> configFieldBeanList;
public String getClassSimpleName() {
return classSimpleName;
}
public void setClassSimpleName(String classSimpleName) {
this.classSimpleName = classSimpleName;
}
public String getDataId() {
return dataId;
}
public void setDataId(String dataId) {
this.dataId = dataId;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public List<ConfigFieldBean> getConfigFieldBeanList() {
return configFieldBeanList;
}
public void setConfigFieldBeanList(List<ConfigFieldBean> configFieldBeanList) {
this.configFieldBeanList = configFieldBeanList;
}
}
- 配置类
@Configuration
@ConfigurationProperties(prefix = "nacos.datajson")
public class NacosDataJsonProperties {
/**
* 指定扫描类路径减少扫描时间
*/
@Value("{entitypath}")
private String entitypath;
/**
* 是否需要进行回调兼容之前的配置
*/
@Value("{usecallback}")
private String usecallback;
public String getEntitypath() {
return entitypath;
}
public void setEntitypath(String entitypath) {
this.entitypath = entitypath;
}
public String getUsecallback() {
return usecallback;
}
public void setUsecallback(String usecallback) {
this.usecallback = usecallback;
}
}
- 启动listener监听器
@Order(value = 1000)
@Component
public class NacosDataJsonConfigListener implements ApplicationListener<ApplicationStartedEvent> {
private Logger logger = LoggerFactory.getLogger(NacosDataJsonConfigListener.class);
@Autowired
private DataJsonHelper dataJsonHelper;
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
try {
logger.info("应用启动成功,准备初始化业务配置数据:DataJsonConfig.......................");
dataJsonHelper.init();
logger.info("应用启动成功,初始化业务配置数据成功:DataJsonConfig.......................");
} catch (Exception e) {
logger.error("初始化远程配置失败",e);
}
}
}
- 定义DataJsonHelper处理类
@Service
public class DataJsonHelper {
@Autowired
private MetaDataService metaDataService;
private static ExpressionParser expressionParser = new SpelExpressionParser();
@Autowired
private NacosDataJsonProperties nacosDataJsonProperties;
private Logger logger = LoggerFactory.getLogger(DataJsonHelper.class);
private static ConcurrentHashMap<String,List> configDataMap = new ConcurrentHashMap<>();
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
/**
* 配置json初始化
* @throws Exception
*/
public synchronized void init() throws Exception{
ConfigService configService = getConfigService();
metaDataService.scanNacosJSONEntity();
List<NacosConfigBean> nacosConfigBeanList = metaDataService.getNacosConfigBeanList();
Map<String,List> initConfigMap = new HashMap<>();
//已有数据进行接入
if(nacosDataJsonProperties.getUsecallback().equals("true")){
ConfigCallBackService configCallBackService = (ConfigCallBackService)SpringContextUtil.getBean(ConfigCallBackService.class);
initConfigMap = configCallBackService.getAllConfigList();
}
for (NacosConfigBean nacosConfigBean : nacosConfigBeanList){
regist(nacosConfigBean,initConfigMap,configService);
addListener(nacosConfigBean.getDataId(),nacosConfigBean.getGroupId(),configService);
}
}
/**
* 向nacos注册数据
* @param nacosConfigBean
* @param configMap
* @throws NacosException
*/
private void regist(NacosConfigBean nacosConfigBean, Map<String, List> configMap,ConfigService configService) throws NacosException {
if(configMap == null || configMap.isEmpty()){
configMap = new HashMap<>();
}
String dataId = nacosConfigBean.getDataId();
String groupId = nacosConfigBean.getGroupId();
logger.info("dataId = {},groupId = {}",dataId,groupId);
String content = configService.getConfig(dataId, groupId, 3000);
if(StringUtils.isEmpty(content)){
String metaDataJsonStr = geteMetaDataJson(nacosConfigBean)+"\n";
StringBuilder builder = new StringBuilder(metaDataJsonStr);
List configList = configMap.get(nacosConfigBean.getClassSimpleName());
if(CollectionUtils.isNotEmpty(configList)){
for (Object obj : configList){
builder.append(JSON.toJSONString(obj)+"\n");
}
}
//发布配置到nacos
boolean b = configService.publishConfig(dataId,groupId,builder.toString());
logger.info("发布配置到nacos结果:dataId={},groupId={},result={}",dataId,groupId,b);
return;
}
//更新远程数据到本地
updateConfig(content, nacosConfigBean);
logger.info("获取nacos数据并存储本地,dataId={},groupId={},",dataId,groupId);
}
/**
* 获取注解元数据-json
* @param nacosConfigBean
* @return
*/
private String geteMetaDataJson(NacosConfigBean nacosConfigBean){
return JSON.toJSONString(nacosConfigBean);
}
/**
* 针对不同的dataid,groupid添加listener
*
* @param dataId
* @param groupID
* @throws NacosException
*/
private void addListener(String dataId, final String groupID,ConfigService configService) throws NacosException {
configService.addListener(dataId, groupID, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
logger.info("收到远程配置更新信息,dataId={},groupId={},准备更新",dataId,groupID);
exeConvert(configInfo, dataId, groupID);
logger.info("收到远程配置更新信息,dataId={},groupId={},更新完成",dataId,groupID);
}
@Override
public Executor getExecutor() {
return null;
}
});
}
/**
* 进行远程数据更新
* @param content
* @param dataID
* @param groupID
*/
private void exeConvert(String content, String dataID, String groupID) {
List<NacosConfigBean> nacosConfigBeanList = metaDataService.getNacosConfigBeanList();
NacosConfigBean nacosConfigBean = nacosConfigBeanList.stream()
.filter(o->o.getDataId().equals(dataID) && o.getGroupId().equals(groupID))
.collect(Collectors.toList()).get(0);
updateConfig(content,nacosConfigBean);
}
/**
* 更新远程配置到本地
* @param content
* @param nacosConfigBean
*/
private void updateConfig(String content, NacosConfigBean nacosConfigBean){
String[] array = content.split("\n");
List configList = Lists.newArrayListWithCapacity(array.length);
for (int i = 1;i < array.length;i ++ ){
Object object = JSON.parseObject(array[i],nacosConfigBean.getClazz());
configList.add(object);
}
configDataMap.put(nacosConfigBean.getClassSimpleName(),configList);
}
/**
* 通过原生Java方式获取ConfigService
* @return
* @throws NacosException
*/
private ConfigService getConfigService() throws NacosException {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, nacosDiscoveryProperties.getServerAddr());
return NacosFactory.createConfigService(properties);
}
/**
* 通过entity类名获取对应配置数据列表,获取多条记录
* @param simpleClassName
* @return
*/
public List getByClassName(String simpleClassName){
return configDataMap.get(simpleClassName);
}
/**
* 通过查询key获取对应的单条记录
* @param o
* @return
*/
public Object getByQuery(Object o){
logger.info("查询单条记录,queryObj={}",JSON.toJSONString(o));
if(o == null){
return null;
}
List<NacosConfigBean> nacosConfigBeanList = metaDataService.getNacosConfigBeanList();
AtomicReference<NacosConfigBean> nacosConfigBeanReference = new AtomicReference<>();
nacosConfigBeanList.forEach(configBean -> {
configBean.getClassSimpleName().equals(o.getClass().getSimpleName());
nacosConfigBeanReference.set(configBean);
});
if(nacosConfigBeanReference.get() == null){
return null;
}
NacosConfigBean nacosConfigBean = nacosConfigBeanReference.get();
List<ConfigFieldBean> configFieldBeanList = nacosConfigBean.getConfigFieldBeanList().stream().filter(configFieldBean -> configFieldBean.isQueryKey()==true).collect(Collectors.toList());
List list = configDataMap.get(o.getClass().getSimpleName());
AtomicReference<Object> result = new AtomicReference<>();
list.forEach(obj->{
AtomicBoolean b = new AtomicBoolean(true);
configFieldBeanList.forEach(configFieldBean -> {
Object value1 = expressionParser.parseExpression(configFieldBean.getFieldName()).getValue(obj);
Object value2 = expressionParser.parseExpression(configFieldBean.getFieldName()).getValue(o);
if(!value1.equals(value2)){
b.set(false);
}
});
if(b.get()){
result.set(obj);
}
});
return result.get();
}
}
- 定义DataJsonService接口
public interface DataJsonService<T> {
/**
* 根据配置entity获取配置数据列表
* @param clazz
* @return
*/
List<T> getDataConfigList(Class<T> clazz);
/**
* 根据查询的字段判断是否存在指定配置数据
* @param t
* @return
*/
T getByQueryKey(T t);
}
- 定义回调接口
public interface ConfigCallBackService {
/**
* 获取项目工程中可以用来作为nacos配置服务的配置数据
* @return
*/
public Map<String, List> getAllConfigList();
}
- 定义源数据处理服务
/**
* Description:
* date: 2020/9/29 9:04 上午
*
* @author fanchunshuai
* @version 1.0.0
* @since JDK 1.8
*/
@Service
@Configuration
public class MetaDataService {
/**
* 指定扫描类路径减少扫描时间
*/
@Value("{nacos.datajson.entitypath}")
private String entitypath;
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Autowired
private NacosDataJsonProperties nacosDataJsonProperties;
private static List<NacosConfigBean> nacosConfigBeanList = new ArrayList<>();
public List<NacosConfigBean> getNacosConfigBeanList() {
return nacosConfigBeanList;
}
/**
* 根据配置的类路径获取配置的entity元数据
*
* @return
*/
public synchronized void scanNacosJSONEntity() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
List<Class<?>> classList = ClassUtils.getClass(nacosDataJsonProperties.getEntitypath(), true);
List<NacosConfigBean> list = new ArrayList<>();
for (Class clazz : classList) {
NacosConfig nacosConfig = AnnotationUtils.findAnnotation( Class.forName(clazz.getName()),NacosConfig.class);
if(nacosConfig == null){
continue;
}
NacosConfigBean nacosConfigBean = getBean(clazz,nacosConfig);
nacosConfigBean.setClazz(clazz);
List<ConfigFieldBean> configFieldBeanList = getConfigFieldBeanList(clazz);
nacosConfigBean.setConfigFieldBeanList(configFieldBeanList);
list.add(nacosConfigBean);
}
if(CollectionUtils.isEmpty(nacosConfigBeanList)){
this.getNacosConfigBeanList().addAll(list);
}
}
/**
* 获取NacosConfigBean
* @param clazz
* @return
*/
private NacosConfigBean getBean(Class<?> clazz,NacosConfig nacosConfig){
NacosConfigBean nacosConfigBean = new NacosConfigBean();
String dataId = nacosConfig.dataId();
//使用默认方式构造dataId
if (StringUtils.isEmpty(dataId)) {
dataId = clazz.getName();
}
nacosConfigBean.setDataId(dataId);
String groupId = nacosConfig.groupId();
//使用默认方式构造groupId
if (StringUtils.isEmpty(groupId)) {
groupId = nacosDiscoveryProperties.getService() + "-" +clazz.getSimpleName();
}
nacosConfigBean.setGroupId(groupId);
String desc = nacosConfig.desc();
if (StringUtils.isEmpty(desc)) {
desc = "NacosJSON configService:"+clazz.getSimpleName();
}
nacosConfigBean.setDesc(desc);
nacosConfigBean.setClassSimpleName(clazz.getSimpleName());
return nacosConfigBean;
}
/**
* 获取getConfigFieldBeanList
* @param clazz
* @return
*/
private List<ConfigFieldBean> getConfigFieldBeanList(Class<?> clazz) throws ClassNotFoundException {
Field [] fieldArr = Class.forName(clazz.getName()).getDeclaredFields();
List<ConfigFieldBean> list = new ArrayList<>();
for (Field field : fieldArr){
if(!field.isAnnotationPresent(ConfigField.class)){
continue;
}
ConfigField configField = field.getAnnotation(ConfigField.class);
ConfigFieldBean configFieldBean = new ConfigFieldBean();
configFieldBean.setFieldName(field.getName());
configFieldBean.setDesc(configField.desc());
configFieldBean.setQueryKey(configField.queryKey());
list.add(configFieldBean);
}
return list;
}
}
- 定义DataJsonService实现
@Service
public class DataJsonServiceImpl implements DataJsonService {
@Autowired
private DataJsonHelper dataJsonHelper;
private static ExpressionParser expressionParser = new SpelExpressionParser();
@Override
public List getDataConfigList(Class clazz) {
return dataJsonHelper.getByClassName(clazz.getSimpleName());
}
@Override
public Object getByQueryKey(Object o) {
return dataJsonHelper.getByQuery(o);
}
}