learn and grow up

快速切换服务bean配置或依赖的简单框架实现样例

字数统计: 1k阅读时长: 5 min
2021/02/28 Share

写在前面

​ 为了满足项目SrpingBoot服务部署的时候可以不修改代码而快速切换服务内部配置如:数据源、rpc等配置,所以独自为项目开发了一套动态修改bean配置或者依赖的微服务,这里做个简单记录

正文

  1. 假如有如下需求:

    1. 动态配置的结构如下,想要按照配置的方案切换某个功能的存储方式,只需要改动配置文件并重启服务即可完成。
  2. 首先分析需要动态配合元数据结构,如下:

    1. 需求结构
  3. 那么可以根据上面的思路来写代码,具体代码(因时间关系,未完全版)如下

    1. 首先是配置:application.yml

      1. yml
    2. 使用方法如下:

      1. enable
    3. 一步一步往下:

      1. /**EnableModuleConfiguration.java
         * 私有化部署模块引入配置项
         */
        @Target(ElementType.TYPE)
        @Retention(RetentionPolicy.RUNTIME)
        @Import({ModuleConfigSelector.class,ModuleConfigAfterProcess.class})
        public @interface EnableModuleConfiguration {
            String[] plans() default {};
        }
        
        /**ModuleUtils.java
         * 工具类
         * @author yyt
         */
        @Slf4j
        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的结构,其他的不怎么需要变,就可以了~

CATALOG
  1. 1. 写在前面
  2. 2. 正文