Java注解作为一种特殊配置信息,在springboot自动配置中大放异彩。这种配置信息直接与类相关联,即配置发生在使用的地方,显得非常直观。另外,从jdk1.8以后,注解可以重复标记,因此对于一些分组的逻辑控制方便了许多。
最近在项目中有很多通过定长报文与银行交互的场景,这些报文就包含大量的配置信息,比如类型、长度、精度、填充字符等等。结合注解可以重复标记,设计了一个使用注解提供配置信息,创建定长报文的实现。
/**
* 数据定义标签,当某个属性被多个业务对象所公用时,且数据类型,长度与顺序一致时,只需定义一个标记
*
* @author xiangyj
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(DataDefines.class)
public @interface DataDefinition {
/**
* 数据项名称
*
* @return
*/
String name();
/**
* 数据项描述
* @return
*/
String dataDes() default "";
/**
* 数据类型
*
* @return
*/
DataType dataType();
/**
* 汇总字段标识,方便数据文件转换为pojo逻辑控制
* @return
*/
boolean isGatherField() default false;
/**
* 数据长度
*
* @return
*/
int dataLen() default 0;
/**
* 是否为空
*
* @return
*/
boolean nullable() default false;
/**
*字段序号,生成文件或解析文件控制排序
* @return
*/
int order() default 0;
/**
* 数据集分类
* @return
*/
RsBusines bus() default RsBusines.B0101;
}
其中@Repeatable(DataDefines.class)就是注解重复标记的写法。
定义交互数据对象,比如DataInfo
@DataDefinition(name = "Appseriono", dataDes = "交易序列号", dataType = CHARACTER, dataLen = 24, order = 1)
protected String appseriono;
@DataDefinition(name = "TransType", dataDes = "交易类别", dataType = CHARACTER, dataLen = 2, bus = B1101, order = 2)
@PropertyMapping("transType")
protected String transtype;
@DataDefinition(name = "PlanId", dataDes = "计划编码", dataType = CHARACTER, dataLen = 30, bus = B1101, order = 3)
@DataDefinition(name = "PlanId", dataDes = "计划编码", dataType = CHARACTER, dataLen = 30, bus = B1102, order = 3)
protected String planid;
@DataDefinition(name = "PlanName", dataDes = "年金计划名称", dataType = CHARACTER, dataLen = 100, bus = B1101, order = 5)
@DataDefinition(name = "PlanName", dataDes = "年金计划名称", dataType = CHARACTER, dataLen = 100, bus = B1102, order = 5)
protected String planname;
@DataDefinition(name = "LastUpdDate", dataDes = "最后更新日期", dataType = CHARACTER, dataLen = 8, bus = B1101, order = 32)
@DataDefinition(name = "LastUpddate", dataDes = "最后更新日期", dataType = CHARACTER, dataLen = 8, bus = B1102, order = 34)
@DataDefinition(name = "LastUpddate", dataDes = "最后更新日期", dataType = CHARACTER, dataLen = 8, bus = B1104, order = 9)
protected String lastupddate;
@DataDefinition(name = "Memo", dataDes = "摘要", dataType = CHARACTER, dataLen = 100, bus = B1101, order = 34)
@DataDefinition(name = "Memo", dataDes = "摘要", dataType = CHARACTER, dataLen = 100, bus = B1102, order = 35)
protected String memo;
其中有属性定义基本上是protected类型,这是当前对象为其他业务对象的共同父类。当前的分组逻辑使用的是bus字段。到此,配置阶段基本完成,下面为控制逻辑。
DataDefinUtil类
@Slf4j
public class DataDefinUtil {
/** 文件内容属性列表 */
private static final ConcurrentHashMap> proDefins = new ConcurrentHashMap<>();
private static ConcurrentHashMap fieldDef = new ConcurrentHashMap<>();
private static ConcurrentHashMap> fieldNames = new ConcurrentHashMap<>();
/**
* 获取带DataDefinition注解的属性列表,并根据业务号分组排序
*
* @param clazz
* @param dataSet 业务功能号
* @return
*/
public static List getDefinFields(Class> clazz, String dataSet) {
String key = clazz.getName() + dataSet;
List result = proDefins.get(key);
if (result == null) {
List allFieldsList = Tool.getAllFieldsList(clazz);
result = allFieldsList.stream().filter(e -> {
DataDefinition[] annotationsByType = e.getAnnotationsByType(DataDefinition.class);
if(annotationsByType.length == 0) {
return false;
} else if(annotationsByType.length == 1){
fieldDef.put(e, annotationsByType[0]);
return true;
} else {
// 循环annotationsByType取出指定的标签
for(DataDefinition def : annotationsByType) {
fieldDef.put(e, def);
String code = def.bus().getCode();
if(code.equals(dataSet)) {
return true;
}
}
return false;
}}).sorted((c1, c2) -> {
DataDefinition annC1 = fieldDef.get(c1);
DataDefinition annC2 = fieldDef.get(c2);
int c1Order = annC1.order();
int c2Order = annC2.order();
return c1Order - c2Order;
}).collect(Collectors.toList());
proDefins.put(key, result);
List fieldNames = result.stream().map(e -> {
DataDefinition[] def = e.getAnnotationsByType(DataDefinition.class);
return def[0].name();
}).collect(Collectors.toList());
DataDefinUtil.fieldNames.put(key, fieldNames);
}
return result;
}
/**
* 获取dataSet分组下的field名列表
* @param clazz
* @param dataSet
* @return
*/
public static List getFieldNames(Class> clazz, String dataSet) {
String key = clazz.getName() + dataSet;
List result = fieldNames.get(key);
if(result == null) {
getDefinFields(clazz, dataSet);
}
return fieldNames.get(key);
}
/**
* 获取dataSet分组下field与数据标签映射关系
* @param clazz
* @param dataSet
* @return
*/
public static ConcurrentHashMap getFieldDef(Class> clazz, String dataSet) {
String key = clazz.getName() + dataSet;
List result = proDefins.get(key);
if(result == null) {
getDefinFields(clazz, dataSet);
}
return fieldDef;
}
/**
* 根据DataDefinition标注信息实例化对象
* @param init
* @param datas
* @throws Exception
*/
public static void init(Object init, String dataSet, byte[] datas, String charSet) throws Exception {
List definFields = getDefinFields(init.getClass(), dataSet);
int pos = 0;
Object value = null;
BeanMap beanMap = ToString.getBeanMap(init);
for(Field field : definFields) {
String key = field.getName();
DataDefinition dataDefin = fieldDef.get(field);
// 获取长度配置
int dataLen = dataDefin.dataLen();
// 精度类型设置为默认值0
if (dataLen == 0) {
dataLen = dataDefin.dataType().getLength();
}
byte[] data = Tool.subBytes(datas, pos, dataLen);
pos += dataLen;
// 根据数据类型实例化
DataType dataType = dataDefin.dataType();
switch (dataType) {
case CHARACTER:
value = new String(data, charSet).trim();
break;
case NUMBER:
// 数字类型左补0
value = Integer.valueOf(new String(data, charSet));
break;
case DECIMAL_8_4:
case DECIMAL_8_6:
case DECIMAL_17_2:
case DECIMAL_21_6:
// 带精度的数据
int precision = dataType.getPrecision();
// 整数部分长度
int intLen = dataType.getLength() - precision;
byte[] intData = Tool.subBytes(data, 0, intLen);
String intNum = new String(intData, charSet);
if(StringUtils.isBlank(intNum))
intNum = "0";
// 小数部分
String decimalNum = new String(Tool.subBytes(data, intLen, precision), charSet);
if(StringUtils.isBlank(decimalNum))
decimalNum = "0";
String decimalStr = intNum + "." + decimalNum;
log.info("Decimal Str = " + decimalStr);
value = Tool.getBigDecimal(decimalStr).toString();
break;
default:
break;
}
log.info("实例化" + dataDefin.order() + ":" + key + "=" + value);
beanMap.put(key, value);
}
}