使用java注解可以实现一些共通的功能,假设有几种格式的csv文件,编码,分隔符,头部行数之类的定义各不相同,但我们想统一的处理他们,那就需要一个共通的方法。
也许有人说,不用注解,只用个共通工具类不就行了吗?但是注解让代码更优雅,而且当你增加其他一些需求,比如其他csv格式的时候,只需要加几个注解就能轻松的扩张你的功能。
那么看代码吧。
定义一个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; } 复制代码
这里为了扩展性先定义了一个空的接口。如果需要扩展就实现该接口。
public interface CSVFormat { } 复制代码
FreeTextCSVFormat实现了CSVFormat接口,并使用了FileFormat的注解,分隔符,编码等都使用默认值,并没有进行特别的设置。
@Data @FileFormat() public class FreeTextCSVFormat implements CSVFormat { private String nexcoNumber; private String msgNumber; private String cmdMode; private String text; } 复制代码
根据注解的设置,读取一行数据。不管是什么编码,或者换行符,都是用通用的readDataLine()方法。
@Data public class CSVFileLineIterator { // 格式类 private Class<? extends CSVFormat> format; // 対象文件 private File file; // 分隔符 private char delimiter; // 文字编码 private String encoding; // 头部行数 private int headerLineCount; // 总共读取行数 private int count; // reader private BufferedReader reader; // msg private MessageSource msg; /** * 构造器。把注解的数据代入成员变量。 */ public CSVFileLineIterator(File file, Class<? extends CSVFormat> format) throws IllegalArgumentException, FileException { if (file == null && format == null) { throw new IllegalArgumentException(msg.getMessage("nullArgument", null, null)); } if (!file.exists()) { throw new FileException(msg.getMessage("fileNoFound", null, null)); } this.file = file; this.format = format; FileFormat fileformat = format.getAnnotation(FileFormat.class); if (fileformat == null) { throw new FileException(msg.getMessage("noFormatAnnotation", null, null)); } this.delimiter = fileformat.delimiter(); this.encoding = fileformat.fileEncoding(); this.headerLineCount = fileformat.headerLineCount(); if (this.delimiter == Character.MIN_VALUE) { throw new FileException(msg.getMessage("illegalDelimiter", new String[] {file.getName()}, null)); } } /** * 用注解指定的编码打开reader */ public void open() throws UnsupportedEncodingException, FileNotFoundException { reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), this.encoding)); } /** * 跳过注解的头部行数,并读取一行,并按照注解的分隔符分割成字符串 */ public String[] readDataLine() throws IOException { String line = null; while ((line = reader.readLine()) != null) { count++; if (count <= this.headerLineCount) { continue; } break; } return line == null ? null : line.split(String.valueOf(this.delimiter)); } /** * 关闭reader */ public void close() throws IOException { this.reader.close(); this.count = 0; } } 复制代码
刚才只是读取一行,返回字符串数组。但是我们有时候想把数据封装到类里,比如上述的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 implements CSVFormat { @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反射,毕竟平时用框架的注解很多,自己写的机会却很少。