引言
使用java注解可以实现一些共通的功能,假设有几种格式的csv文件,编码,分隔符,头部行数之类的定义各不相同,但我们想统一的处理他们,那就需要一个共通的方法。
也许有人说,不用注解,只用个共通工具类不就行了吗?但是注解让代码更优雅,而且当你增加其他一些需求,比如其他csv格式的时候,只需要加几个注解就能轻松的扩张你的功能。
那么看代码吧。
1. 定义注解
定义一个csv格式的注解,包含文件的分隔符,编码等等信息。如果业务需求增多,可以继续添加功能,比如换行符之类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FileFormat {
// 分隔符
char delimiter() default ',';
// 引用符
char encloseChar() default Character.MIN_VALUE;
// 编码
String fileEncoding() default "UTF-8";
// 头部行数
int headerLineCount() default 0;
// 输出文件是否覆盖
boolean overWriteFlg() default false;
}
2. 使用注解
FreeTextCSVFormat使用了FileFormat的注解,分隔符,编码等都使用默认值,并没有进行特别的设置。
@Data
@FileFormat()
public class FreeTextCSVFormat {
private String nexcoNumber;
private String msgNumber;
private String cmdMode;
private String text;
}
3. 处理注解,读取文件中的一行数据
根据注解的设置,读取一行数据。不管是什么编码,或者换行符,都是用通用的readDataLine()方法。
@Data
public class CSVFileLineIterator<T> implements AutoCloseable {
private Class<T> format;
private File file;
// 分隔符
private char delimiter;
// 编码
private String encoding;
// 头部行数
private int headerLineCount;
private int count;
private BufferedReader reader;
public CSVFileLineIterator(File file, Class<T> format) {
FileFormat fileformat = checkParams(file, format);
this.file = file;
this.format = format;
// 从注解中获取分隔符
this.delimiter = fileformat.delimiter();
// 从注解中获取编码
this.encoding = fileformat.fileEncoding();
// 从注解中获取头部行数
this.headerLineCount = fileformat.headerLineCount();
}
// 检查参数
private FileFormat checkParams(File file, Class<T> format) {
Optional.ofNullable(file).orElseThrow(
() -> new FileException("nullArgument"));
Optional.ofNullable(format).orElseThrow(
() -> new FileException("nullArgument"));
OptionalBoolean.of(!file.exists()).orElseThrow(
() -> new FileException("fileNoFound"));
FileFormat fileformat = format.getAnnotation(FileFormat.class);
Optional.ofNullable(fileformat).orElseThrow(
() -> new FileException("noFormatAnnotation"));
OptionalBoolean.of(fileformat.delimiter() == Character.MIN_VALUE).orElseThrow(
() -> new FileException("illegalDelimiter"));
return fileformat;
}
public void open() {
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), this.encoding));
} catch (UnsupportedEncodingException | FileNotFoundException e) {
throw new FileException("csvOpenFailure", e);
}
}
public String[] readDataLine() {
String line = null;
try {
// 读取一行
while ((line = reader.readLine()) != null) {
count++;
if (count <= this.headerLineCount) {
// 跳过头部行数
continue;
}
break;
}
} catch (IOException e) {
throw new FileException("csvReadFailure", e);
}
// 按照指定的分隔符把行内容分隔成字符串数组
return line == null ? null : line.split(String.valueOf(this.delimiter));
}
public void close() {
try {
this.reader.close();
this.count = 0;
} catch (IOException e) {
throw new FileException("csvCloseFailure", e);
}
}
测试一下
public static void main(String[] args) {
try(CSVFileLineIterator c = new CSVFileLineIterator(new File("C:\\SiLED\\FRU20191209151700.csv"), FreeTextCSVFormat.class)) {
c.open();
String[] s;
while ((s = c.readDataLine()) != null) {
System.out.println("直接readline: " + Arrays.asList(s));
}
}
}
测试结果
直接readline: [001, 301, 001, 内容1]
直接readline: [001, 302, 002, 内容2, 303, 003, 内容3, 304, 004, 内容4]
CSV文件-FRU20191209151700.csv
001,301,001,内容1
001,302,002,内容2,303,003,内容3,304,004,内容4
4. 功能扩展-读取数据,并封装到类中
刚才只是读取一行,返回字符串数组。但是我们有时候想把数据封装到类里,比如上述的FreeTextCSVFormat类。那么可以再定义一个文件内容的注解。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FileColumn {
// 列index
int columnIdex() default 0;
// 是不是循环列
boolean isLoop() default false;
}
给FreeTextCSVFormat,添加FileColumn注解。
@Data
@FileFormat()
public class FreeTextCSVFormat {
@FileColumn(columnIdex = 0)
private String nexcoNumber;
@FileColumn(columnIdex = 1, isLoop = true)
private String msgNumber;
@FileColumn(columnIdex = 2, isLoop = true)
private String cmdMode;
@FileColumn(columnIdex = 3, isLoop = true)
private String text;
}
最后,可以使用反射获取columnIdex,并把读取的内容封装进去。具体实现就不贴出来了。
结语
使用注解能够提升扩展性,比如添加一种新的csv样式,并不需要修改读取文件的方法,只需要添加使用注解的类就可以了。这样做能够更优雅,还能帮你了解java反射,毕竟平时用框架的注解很多,自己写的机会却很少。