写在前面
为了满足项目SrpingBoot服务部署的时候可以不修改代码而快速切换服务内部配置如:数据源、rpc等配置,所以独自为项目开发了一套动态修改bean配置或者依赖的微服务,这里做个简单记录
正文
假如有如下需求:
- 动态配置的结构如下,想要按照配置的方案切换某个功能的存储方式,只需要改动配置文件并重启服务即可完成。
首先分析需要动态配合元数据结构,如下:
那么可以根据上面的思路来写代码,具体代码(因时间关系,未完全版)如下
首先是配置:application.yml
使用方法如下:
一步一步往下:
/**EnableModuleConfiguration.java * 私有化部署模块引入配置项 */ (ElementType.TYPE) (RetentionPolicy.RUNTIME) ({ModuleConfigSelector.class,ModuleConfigAfterProcess.class}) public @interface EnableModuleConfiguration { String[] plans() default {}; } /**ModuleUtils.java * 工具类 * @author yyt */ 4j public class ModuleUtils { public static String getStringPropertiesByEnv(Environment env,String preix, String name){ if (env == null || StringUtils.isEmpty(preix) || StringUtils.isEmpty(name)) { return ""; } return env.getProperty(new StringBuilder(preix).append(name).toString()); } public static <T> List<T> parsePropertiesByEnv(Environment env, Class<T> cls, String prefix) { if (env == null || cls == null || StringUtils.isEmpty(prefix)) { return Collections.emptyList(); } List<T> ls = new ArrayList<>(); Boolean hasNext = false; int order = 0; do { hasNext=false; try { T item = cls.newInstance(); Field[] fields = item.getClass().getDeclaredFields(); for (Field f : fields) { StringBuilder stringBuilder = new StringBuilder(prefix); stringBuilder.append("[").append(order).append("]"); stringBuilder.append(".").append(f.getName()); String value = env.getProperty(stringBuilder.toString()); if(StringUtils.isNotEmpty(value)){ f.setAccessible(true); String type = f.getType().toString(); if (type.endsWith("String")) { f.set(item,value) ; }else if(type.endsWith("int") || type.endsWith("Integer")){ f.set(item,Integer.parseInt(value)) ; }else if(type.endsWith("boolean") || type.endsWith("Boolean")){ f.set(item,Boolean.valueOf(value)) ; }else { //需要再补充 } hasNext=true; } } if(hasNext){ ls.add(item); } order++; } catch (InstantiationException e) { log.error("InstantiationException error", e); return Collections.emptyList(); } catch (IllegalAccessException e) { log.error("IllegalAccessException error", e); return Collections.emptyList(); } } while (hasNext);
return ls;
}
}
/**ModuleConfigSelector.java
* 根据yml配置加载指定bean
*/
@Component
public class ModuleConfigSelector implements ImportSelector, EnvironmentAware {
public static final String modulePrefix="module.config.";
public static final String templatePrefix=".templateMetaDataList";
private ModuleConfigProperties moduleConfigProperties;
private Environment environment;
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
createMeatDataMap(importingClassMetadata);
if(MapUtils.isEmpty(ModuleConfigProperties.metaDataMap)){
return new String[0];
}
List<String> clss=new ArrayList<>();
ModuleConfigProperties.metaDataMap.forEach((k,v)->{
v.getBeanMetaDataList().forEach(e->{
clss.add(e.getBeanClassName());
});
});
if(CollectionUtils.isEmpty(clss)){
return new String[0];
}
return clss.toArray(new String[clss.size()]);
}
/**
* 解析yml
* @param importingClassMetadata
*/
public void createMeatDataMap(AnnotationMetadata importingClassMetadata){
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableModuleConfiguration.class.getName()));
String[] modules= annoAttrs.getStringArray("modules");
if(modules!=null){
ModuleConfigProperties moduleConfigProperties=new ModuleConfigProperties();
List<ModuleConfigProperties.ModuleMetaData> moduleMetaDataList=new ArrayList<>();
moduleConfigProperties.setModuleMetaDataList(moduleMetaDataList);
for (String module:modules){
ModuleConfigProperties.ModuleMetaData moduleMetaData=new ModuleConfigProperties.ModuleMetaData();
moduleMetaDataList.add(moduleMetaData);
moduleMetaData.setModuleName(module);
String classNames=ModuleUtils.getStringPropertiesByEnv(environment,modulePrefix,module);
List<ModuleConfigProperties.BeanMetaData> beanMetaDataList=new ArrayList<>();
moduleMetaData.setBeanMetaDataList(beanMetaDataList);
if(StringUtils.isEmpty(classNames)){
continue;
}
for(String className:classNames.split(",")){
StringBuilder tpre=new StringBuilder(modulePrefix).append(module).append(".").append(className).append(templatePrefix);
ModuleConfigProperties.BeanMetaData beanMetaData=new ModuleConfigProperties.BeanMetaData();
beanMetaData.setBeanClassName(className);
List<ModuleConfigProperties.TemplateMetaData> templateMetaDatas= ModuleUtils.parsePropertiesByEnv(environment,ModuleConfigProperties.TemplateMetaData.class,tpre.toString());
beanMetaData.setTemplateMetaDataList(templateMetaDatas);
beanMetaDataList.add(beanMetaData);
}
}
moduleConfigProperties.fillMetaMap();
}
}
@Override
public void setEnvironment(Environment environment) {
this.environment=environment;
}
}
/**
* ModuleConfigProperties.java
* yml配置元数据
*/
@Data
public class ModuleConfigProperties {
private List<ModuleMetaData> moduleMetaDataList;
public static Map<String, ModuleMetaData> metaDataMap;
@Data
public static class ModuleMetaData{
private String moduleName;
private List<BeanMetaData> beanMetaDataList;
}
@Data
public static class BeanMetaData {
private String beanClassName;
private List<TemplateMetaData> templateMetaDataList;
}
@Data
public static class TemplateMetaData {
private String fieldName;
private String beanName;
}
public void fillMetaMap() {
if (CollectionUtils.isNotEmpty(moduleMetaDataList)) {
metaDataMap = moduleMetaDataList.stream().collect(Collectors.toMap(ModuleMetaData::getModuleName, Function.identity()));
}
}
}
/**
* ModuleConfigAfterProcess.java
* 进一步解析yml配置,为bean添加配置好的依赖
*/
@Slf4j
public class ModuleConfigAfterProcess implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
List<ModuleConfigProperties.BeanMetaData> beanMetaDataList = new ArrayList<>();
ModuleConfigProperties.metaDataMap.forEach((k, v) -> {
List<ModuleConfigProperties.BeanMetaData> item = v.getBeanMetaDataList();
if (CollectionUtils.isNotEmpty(item)) {
beanMetaDataList.addAll(item);
}
});
if (CollectionUtils.isNotEmpty(beanMetaDataList)) {
for (ModuleConfigProperties.BeanMetaData beanMetaData : beanMetaDataList) {
if (CollectionUtils.isEmpty(beanMetaData.getTemplateMetaDataList())) {
continue;
}
BeanDefinition bd = beanFactory.getBeanDefinition(beanMetaData.getBeanClassName());
MutablePropertyValues mpv = bd.getPropertyValues();
for (ModuleConfigProperties.TemplateMetaData templateMetaData : beanMetaData.getTemplateMetaDataList()) {
String fieldName = templateMetaData.getFieldName();
String beanName = templateMetaData.getBeanName();
if (StringUtils.isEmpty(fieldName) || StringUtils.isEmpty(beanName)) {
continue;
}
try{
mpv.add(fieldName,new RuntimeBeanReference(beanName));
}catch (BeansException e){
log.error("BeansException error",e);
continue;
}
}
}
}
return;
}
}
```
代码到这里就差不多结束了,需要两个注意点:1、需要动态加载的bean不能放在和启动类同一目录或者子目录下,不然会被springboot自动加载的;2、bean内需要动态依赖bean的不使用注解注入,直接为其添加set方法即可。
就是这样简单,当然我们可以根据业务需求改变ModuleConfigProperties的结构,其他的不怎么需要变,就可以了~