Java注解配置的定长报文生成

Java注解作为一种特殊配置信息,在springboot自动配置中大放异彩。这种配置信息直接与类相关联,即配置发生在使用的地方,显得非常直观。另外,从jdk1.8以后,注解可以重复标记,因此对于一些分组的逻辑控制方便了许多。

最近在项目中有很多通过定长报文与银行交互的场景,这些报文就包含大量的配置信息,比如类型、长度、精度、填充字符等等。结合注解可以重复标记,设计了一个使用注解提供配置信息,创建定长报文的实现。

  1. 定义配置信息注解类:@DataDefinition
    /**
     * 数据定义标签,当某个属性被多个业务对象所公用时,且数据类型,长度与顺序一致时,只需定义一个标记
     * 
     * @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)就是注解重复标记的写法。

  2. 定义交互数据对象,比如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字段。到此,配置阶段基本完成,下面为控制逻辑。

  3. 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);
    		}
    	}

     

你可能感兴趣的:(Java注解配置的定长报文生成)