使用注解和反射优化系统配置参数
1、 引言
相信大多数业务系统都是有一张配置参数或者字典表来维护系统中一些常用的业务特殊处理配置信息。例如在我现在负责的几个系统中就存在这样一张配置表,其中使用的较多的是一类商户相关的配置,例如这个商户的放款或者还款是否要发送短信,是否要发送MQ,或者其他一些特殊的处理或过滤,并且各个商户的放还款短信业务码配置也是有所差异的,像这种多维度的配置往往需要多个字段来维护配置的,我今天要介绍的就是这种多维度配置的方法以及我对其进行优化的过程。
2、 使用数据库配置表属性字段
这种多维度的配置最简单的解决方案就是建立一张配置表,这张表的属性字段有任何含义,例如attribute1、attribute2… 这种,然后将我们的各个维度的配置值写到这些attribute中。这里可能有人会问为什么不用具体含义的字段呢??这里涉及到了一个表重用的问题,系统中可能会有多种类型的配置,而且每一类的配置维度都是不太一样的,如果使用具体特定含义字段的表的话,那么每一类配置可能都得多加一张表。这样做麻烦不说,还很浪费空间,所以使用不具含义字段的通用表是一个比较合理的选择。具体表设计字段如下:
配置类型config_type字段表示了是哪一类配置,可以根据这个字段对配置数据进行分类,dict_key字段则表示具体这一类配置里面的配置键,由配置类型和这个键就可以唯一确定一条配置;找到这条配置之后就可以在系统中通过attribute属性字段访问具体的配置项,从而打到了对业务特殊处理的目的。
然而,这种方式虽然整合了系统中各类配置,也整合了某一类配置的多个配置维度。但是万物有利皆有弊。使用通用配置表确实方便了各类配置整合;但是通用表字段意义不明确也是一个令人头痛的问题,定位问题查找具体配置项的时候需要看代码看数据库,并且人肉进行匹配,这样做起来很麻烦而且也容易出错;写代码过程中也容易出错,因为所有字段都长的差不多,万一手抖就选错了那就完蛋了。不过想要克服这个问题还是有办法的,就是我后面即将介绍的优化过程。
3、 通过注解和反射机制进行优化
虽然不能通过建多张表来记录不同类型的配置,但是我们可以在代码层面进行优化。针对不同类型的配置,我们可以建立对应的Java类,这个Java类中的字段就是具有具体含义的,分别代表了不同维度的配置,这样我们在系统中直接使用这个Java类来读取配置进行特殊业务处理,这样既不会出错,定位问题起来也是很方便有木有。然而,这个Java类和我们的数据库表配置是没有任何关联的;所以,解决这个问题的核心点就是将我们建立的Java类和对应的数据库配置关联起来。
由于数据库表字段没有具体含义,只是attribute+数字组成的字段名称,而我们新加的Java类型是有具体含义的。所以,我们可以通过在新加的Java类型各个字段上加一个自定义的注解来表示这个字段对应的是数据库中的哪一个attribute字段。
自定义注解如下:
1 @Target(ElementType.FIELD) 2 @Retention(RetentionPolicy.RUNTIME) 3 public @interface Attribute { 4 int number() default 1; 5 }
number表示的便是对应到数据库中的属性attribute后面的数字。具体Java类字段定义如下:
1 @Attribute(number = 1) 2 private String loanSucc; 3 4 @Attribute(number = 2) 5 private String loanFail; 6 7 @Attribute(number = 3) 8 private String perRepay; 9 10 @Attribute(number = 4) 11 private String advanceRepay;
意思就是loanSucc字段对应的是数据库中的attribute1字段,loanFail对应的是attribute2字段,依次类推......
完成类型映射之后,还要讲数据进行转换,将数据库中数据转换到Java对象。以下便是Java类和数据库配置数据转换步骤:
- 读取具体类型数据库配置数据列表;
- 遍历配置列表,分别对每一条配置进行处理,获取该类配置对应的Java类型字段列表;
- 实例化该类配置对应的Java类型对象;
- 对Java类型字列表进行遍历,读取字段上Attribute注解,确定具体number值,从而可以确定数据库表映射对象对应attribute的get方法名,通过反射即可获得属性值;
- 将前一步通过反射获取到的attribute值,再使用反射设置到新建Java对象的对应字段上。
- 将dict_key和Java对象以键值对形式存储到Map中;这样,通过dict_key就可以找到具体含义的配置对象了。
以下是具体转换的实现代码:
1 private static final String GET_ATTRIBUTE_METHOD = "getAttribute"; 2 3 public static <T> Map<String, T> transformDict(List<DictionaryConfig> dictList, Class<T> clazz) throws Exception { 4 // 新建一个Map用于存放字典Key和具体对象的映射 5 Map<String, T> resultMap = new HashMap<>(); 6 // 遍历数据库中对象列表,依次进行转换 7 for (DictionaryConfig dictConf : dictList) { 8 // 反射获取转换的目标对象字段(有具体含义的) 9 Field[] clazzFields = clazz.getDeclaredFields(); 10 // 将类型T实例化一个对象出来 11 T obj = clazz.newInstance(); 12 // 遍历字段列表逐个进行赋值处理 13 for (Field field : clazzFields) { 14 if (!field.isAccessible()) { 15 field.setAccessible(true); 16 } 17 // 字段上面存在Attribute注解 18 if (field.isAnnotationPresent(Attribute.class)) { 19 // 获取注解的number,确定对应到字典表哪个字段 20 Attribute attrAnno = field.getAnnotation(Attribute.class); 21 int number = attrAnno.number(); 22 Method getAttribute = DictionaryConfig.class.getDeclaredMethod(GET_ATTRIBUTE_METHOD + number); 23 // 将字典表对应属性字段设置到转换后的映射字段上 24 Object attrVal = getAttribute.invoke(dictConf); 25 field.set(obj, attrVal); 26 } 27 } 28 resultMap.put(dictConf.getDictKey(), obj); 29 } 30 return resultMap; 31 }
4、 效果测试
在测试过程中,我省去了从数据库取数的过程,直接将数据写入到了DictionaryConfig的列表中,列表中每一个对象均表示数据库中一条配置。为了有对比效果,我给出了两个类型对应的分别两条配置:
mer0001商户的sms配置:
1 // 类型为MerSmsConfig的配置列表 2 List<DictionaryConfig> merSmsConfList = new ArrayList<>(); 3 // mer0001商户的sms配置 4 DictionaryConfig merSmsConf1 = new DictionaryConfig(); 5 merSmsConf1.setDictKey("mer0001"); 6 merSmsConf1.setAttribute1("L1001"); 7 merSmsConf1.setAttribute2("L1002"); 8 merSmsConf1.setAttribute3("R1001"); 9 merSmsConf1.setAttribute4("R1002"); 10 merSmsConf1.setAttribute5("R1003"); 11 merSmsConf1.setAttribute6("R1004"); 12 merSmsConf1.setAttribute7("R1005"); 13 merSmsConf1.setAttribute8("R1006"); 14 merSmsConfList.add(merSmsConf1);
mer0002商户的sms配置:
1 DictionaryConfig merSmsConf2 = new DictionaryConfig(); 2 merSmsConf2.setDictKey("mer0002"); 3 merSmsConf2.setAttribute1("L2001"); 4 merSmsConf2.setAttribute2("L2002"); 5 merSmsConf2.setAttribute3("R2001"); 6 merSmsConf2.setAttribute4("R2002"); 7 merSmsConf2.setAttribute5("R2003"); 8 merSmsConf2.setAttribute6("R2004"); 9 merSmsConf2.setAttribute7("R2005"); 10 merSmsConf2.setAttribute8("R2006"); 11 merSmsConfList.add(merSmsConf2);
mer0001商户的Filter配置:
1 // 类型为MerFilterConfig的配置列表 2 List<DictionaryConfig> merFilterConfList = new ArrayList<>(); 3 // mer0001商户的Filter配置 4 DictionaryConfig merFilterConf1 = new DictionaryConfig(); 5 merFilterConf1.setDictKey("mer0001"); 6 merFilterConf1.setAttribute1("1"); 7 merFilterConf1.setAttribute2("0"); 8 merFilterConf1.setAttribute3("1"); 9 merFilterConf1.setAttribute4("1"); 10 merFilterConf1.setAttribute5("0"); 11 merFilterConf1.setAttribute6("0"); 12 merFilterConf1.setAttribute7("1"); 13 merFilterConfList.add(merFilterConf1);
mer0002商户的Filter配置:
1 DictionaryConfig merFilterConf2 = new DictionaryConfig(); 2 merFilterConf2.setDictKey("mer0002"); 3 merFilterConf2.setAttribute1("0"); 4 merFilterConf2.setAttribute2("0"); 5 merFilterConf2.setAttribute3("1"); 6 merFilterConf2.setAttribute4("0"); 7 merFilterConf2.setAttribute5("1"); 8 merFilterConf2.setAttribute6("1"); 9 merFilterConf2.setAttribute7("0"); 10 merFilterConfList.add(merFilterConf2);
两类配置中的两条配置记录转换后的结果:
转换成了具有具体含义字段的对象之后,是不是看起来舒服了很多~~~用起来也很爽有木有!!
好啦,今天要介绍的就这么些内容,如有任何错误,欢迎各位指正,感激不尽~~