根据业务需求,需要每天从服务器拿数据,数据格式以“|”分割,类似zhang san|18,这样的。然后需要读取文件,将其转成Object。因为数据格式不是json或者xml等,所以无法使用fastjson等直接转换成Object。但是数据是有序的,因此可以使用反射或者setter方法进行构建Object。
一、反射
@Slf4j
public class EBBSDataMapper {
private static volatile Field[] fields;
public static CustomerInfo convertToCustomer(String line) throws Exception {
String[] strings = line.split("\\|", -1);
return arrayToJson(strings);
}
public static CustomerInfo arrayToJson(String[] array) throws Exception {
String[] trimArray = Arrays.stream(array).map(s -> StringUtils.trimToNull(s)).toArray(String[]::new);
CustomerInfo customerInfo = CustomerInfo.class.newInstance();
fields = CustomerInfo.class.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
// bypass $jacocoData
// Maven integrates jacoco to count the code coverage of unit tests
// Jacoco weaves an additional field named $jacocoData into the compiled classes
// So using java reflect will find this boolean[] field
if (field.isSynthetic()) {
continue;
}
String fieldName = field.getName();
field.setAccessible(true);
String fieldClassName = field.getType().getSimpleName();
log.debug(fieldName + " [" + fieldClassName + "," + trimArray[i] + "]");
TargetExecutorFactory.getTargetExecutor(fieldClassName).setData(field, trimArray[i], customerInfo);
}
return customerInfo;
}
}
这里,首先通过String的split将字段分割出来,然后将空字符串“”转成null,接着通过反射将数据存入对象中。
有关反射的知识:Java高级特性——反射
值得注意的是,这里利用多态和简单工厂模式将反射的set重构了,这样以后有利于扩展,而且代码没有if、else if,看起来干净整洁。
public interface TargetExecutor {
void setData(Field field, String data, CustomerInfo output) throws Exception;
}
@Slf4j
public class LocalDateTargetExecutor implements TargetExecutor {
@Override
public void setData(Field field, String data, CustomerInfo output) throws Exception {
try {
LocalDate localDate = StringUtils.isEmpty(data) ? null :
LocalDate.parse(data, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
field.set(output, localDate);
} catch (IllegalAccessException | DateTimeParseException e) {
log.error(e.getMessage());
throw e;
}
}
}
public class TargetExecutorFactory {
public static TargetExecutor getTargetExecutor(String fieldClassName) {
TargetExecutor targetExecutor;
switch (fieldClassName) {
case "LocalDate":
targetExecutor = new LocalDateTargetExecutor();
break;
case "String":
targetExecutor = new StringTargetExecutor();
break;
case "BigDecimal":
targetExecutor = new BigDecimalTargetExecutor();
break;
case "EBBSRelStatusEnum":
targetExecutor = new EBBSRelStatusEnumTargetExecutor();
break;
case "GenderEnum":
targetExecutor = new GenderEnumTargetExecutor();
break;
case "RiskLevelEnum":
targetExecutor = new RiskLevelEnumTargetExecutor();
break;
default:
throw new BatchProcessingException("No TargetExecutor for [" + fieldClassName + "]");
}
return targetExecutor;
}
}
二、Jacoco导致的反射错误
问题描述:当时在本地直接跑测试,是一切正常的。然后上传到Jenkins上面,发现Unit Test Step build失败了。经过打断点,查log,最后才发现是Maven test有问题。Maven test集成了Jacoco做代码覆盖率,而Jacoco在编译时会动态织入一个boolean[] $jacocoData字段,因此在DataMapper中无法找到该boolean数组如何set数据,导致错误。
解决办法:只要把该字段跳过就好。
if (field.isSynthetic()) {
continue;
}
三、多线程下反射资源冲突
问题描述:因为数据量比较多,因此采用Java的线程池去多线程处理数据转换。但是,实际运行时发现,有些数据无法set进去。
解决办法:采用volatile
private static volatile Field[] fields;
volatile学习:Java中Volatile关键字详解