一、前言
监听者并不在23种设计模式之中,有点类似于观察者模式,但又不完全相同。
实际项目中,特别在与用户交互的前端设计或UI设计中使用的非常广泛。
最近的项目中需要加载分析excel中的数据并存入DB,在easyexcel的代码中也运用了监听者模式。
写了一个简单的demo,来理解它的工作原理。
之前很多excel分析工具Apache poi,jxl等,是将整个文件读到内存进行解析的,所以非常耗内存。easyexcel不同,他是一行一行进行解析的,所以即便是几百万行的excel文件,也完全能够搞定。
这个时候就会遇到一个问题,我们一行一行解析,数据放在哪?还是内存里吗?那就失去了一行一行解析的意义。
但easyexcel也不知道解析完之后,用户到底要干什么,那之后把这个问题抛给使用者。
二、Listener
easyexcel不知道解析完数据,使用者要干什么,因此他设计并让分析对象持有了一个Listener接口
接口很简单,只有两个方法:
1.invoke()方式是这一行解析完了,我给你封装成了T对象,你要不要干点啥?
2.doAfterAllAnalysed()方法是整个文件都解析完了,你爱干啥就干点啥吧。
public interface Listener<T> { void invoke(T t); void doAfterAllAnalysed(); }
三、事件
有监听者就有被监听的事件,在excel解析这个过程中,事件显然就是文件的每一行被解析得到的对象T t。
产生事件的自然就是excel分析器,下面是分析器的实现
分析器的输入是一个文件,因此一定要持有一个File对象,当然InputStream也可以
每一行需要被解析成一个java对象,构造对象需要相应的Class,所以也需要传入一个Class<T>对象
每解析完以后都需要通知监听者,因此还需要持有监听者对象
核心的实现是doAnalysis()方法,每解析成功一行,就调用监听者的invoke方法,所有行解析完了调用doAfterAllAnalysed()方法。
public class ExcelAnalyzer<T> { private File file; private Class<T> clazz; private Listener<T> listener; public ExcelAnalyzer<T> setListener(Listener<T> listener) { if (listener != null) { this.listener = listener; } return this; } void doAnalysis() throws FileNotFoundException { BufferedReader reader = new BufferedReader(new FileReader(file)); reader.lines().forEach(line -> { T t = process(line); if (this.listener != null) { this.listener.invoke(t); } }); if (this.listener != null) { this.listener.doAfterAllAnalysed(); } } /** * 实际要处理更多类型 * * @param line * @return */ public T process(String line) { T t = null; try { t = clazz.newInstance(); String[] split = line.split(","); Field[] declaredFields = clazz.getDeclaredFields(); int length = declaredFields.length; for (int i = 0; i < length; i++) { Field field = declaredFields[i]; field.setAccessible(true); if (field.getType().getName().equals(Integer.class.getName())) { field.set(t, Integer.valueOf(split[i])); } else { field.set(t, split[i]); } } } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } return t; } public ExcelAnalyzer<T> read(File file, Class<T> clazz) { this.file = file; this.clazz = clazz; return this; } }
四、主程序
在主程序执行之前,还需要定义一个java对象类User,它是每一行对应的java对象,这里我们简单定义一下,当然实际的对象可能会更复杂。
@Data public class User { private String name; private Integer age; }
主程序实现:
主程序模仿了easyExcel的实现,看起来还是很像的。
public class Test { public static void main(String[] args) throws FileNotFoundException { File file = new File("C:\Users\G007112\Desktop\excel.txt"); ExcelAnalyzer<User> userExcelAnalyzer = new ExcelAnalyzer<User>().read(file,User.class).setListener(new Listener<User>() { @Override public void invoke(User user) { System.out.format("I get line=%s", user); System.out.println(); } @Override public void doAfterAllAnalysed() { System.out.println("Excel file analysis success!!!"); } }); userExcelAnalyzer.doAnalysis(); } }
当然只是想执行一遍程序,啥也不干也是可以的,分析器不传入Listener也完全可以很健壮的运行。只是没有啥意义而已。
以上代码是模仿了easyExcel,源代码中的实现更复杂。
输出:
I get line=User(name=line, age=1) I get line=User(name=line, age=2) I get line=User(name=line, age=3) I get line=User(name=end, age=4) Excel file analysis success!!!