一个导出数据的功能模块的优化过程

业务场景:从学生数据库导出一张表为Excel

  1. Excel导出工具类

    /**
     * @author Cao Jiwen
     * @version V1.0
     */
    public class ExcelExportUtil {
        /**
         * @param outputStream 输出流
         * @param columnList 列名列表
         * @param dataList 数据列表
         */
        public static void export(OutputStream outputStream, List<String> columnList,List<List<String>> dataList) throws IOException {
            //excel文件主题对象
            SXSSFWorkbook wb = null;
            try{
                //内存的最大行数,防止内存溢出
                wb = new SXSSFWorkbook(1000);
                //创建一个sheet
                SXSSFSheet sheet1 = wb.createSheet("数据");
                //行索引
                int excelRow = 0;
                //创建第一行,列名行
                Row titleRow = sheet1.createRow(excelRow++);
                if (ObjectUtil.isNotEmpty(columnList)){
                    //遍历列名表
                    for (int i = 0; i < columnList.size(); i++) {
                        //创建一个单元格
                        Cell cell = titleRow.createCell(i);
                        //设置单元格的值
                        cell.setCellValue(columnList.get(i));
                    }
                }
                if (ObjectUtil.isNotEmpty(dataList)){
                    for (int i = 0; i < dataList.size(); i++) {
                        //创建数据行
                        Row dataRow = sheet1.createRow(excelRow++);
                        //遍历出单个数据
                        for (int j = 0; j < dataList.get(0).size(); j++) {
                            Cell cell = dataRow.createCell(j);
                            cell.setCellValue(dataList.get(i).get(j));
                        }
                    }
                }
                wb.write(outputStream);
            }catch (Exception e){
                e.printStackTrace();
                throw e;
            }finally {
                if (wb != null){
                    wb.close();
                }
                if (outputStream != null){
                    outputStream.close();
                }
            }
        }
    }
    
  2. 导出过程

    public static void main(String[] args) throws IOException {
        //调用方角度
        //查询数据
        Student student1 = new Student("luoxi", 18);
        Student student2 = new Student("jixian", 19);
        //创建输出流
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\Javaproject\\ myApp\\exportItem\\src\\main\\java\\学生信息表.xlsx");
        //创建列名列表
        List<String> columList = new ArrayList<>();
        columList.add("姓名");
        columList.add("年龄");
        //创建数据列表
        List<List<String>> dataList = new ArrayList<>();
        //创建单条数据列表
        List<String> data1 = new ArrayList<>();
        List<String> data2 = new ArrayList<>();
        data1.add(student1.getName());
        data1.add(student1.getAge().toString());
        data2.add(student2.getName());
        data2.add(student2.getAge().toString());
        dataList.add(data1);
        dataList.add(data2);
        ExcelExportUtil.export(fileOutputStream,columList,dataList);
    }
    
  3. 使用反馈

    最直接的感受就是,被调用方每次都需要一次一次的创建列表,再从对象中提取数据,非常麻烦

  4. 代码改进

    第一步:反射改进

    不传列表,那就直接传对象,那就要从对象中自己提取数据,对象我事先不知道的,所以要用到反射

    /**
     * @param outputStream 输出流
     * @param columList 列名列表
     * @param dataList 数据列表
     */
    public static void reflectExport(OutputStream outputStream,List<String> columList,List dataList) throws IllegalAccessException, IOException {
        //健壮性检验
        if (ObjectUtil.isEmpty(dataList)){
            return;
        }
        List<List<String>> allList = new ArrayList<>();
        //获取class文件
        Class<?> aClass = dataList.get(0).getClass();
        //通过class文件获取对象属性
        Field[] declaredFields = aClass.getDeclaredFields();
        for (int i = 0; i < dataList.size(); i++) {
            List<String> list = new ArrayList<>();
            for (int j = 0; j < declaredFields.length; j++) {
                //由于属性是private修饰,将可见性设为true
                Field declareField = declaredFields[j];
                declareField.setAccessible(true);
                Object o = declareField.get(dataList.get(i));
                if (o != null){
                    list.add(o.toString());
                }
            }
            allList.add(list);
        }
        export(outputStream,columList,allList);
    }
    

    继续优化:如果不想导出对象的全部数据呢

    第二步:注解改进

    • 不想导出全部属性,可以通过给属性标记
    • 将导出的属性排序,通过注解上的信息,标记属性的顺序
    //在运行时生效
    @Retention(RetentionPolicy.RUNTIME)
    //目标注解,标识属性
    @Target(ElementType.FIELD)
    public @interface Excel {
        int order();
    }
    
    public class Student {
        private String id;
        @Excel(order = 1)
        private String name;
        @Excel(order = 2)
        private Integer age;
    }
    
    public static void reflectExport(OutputStream outputStream,List<String> columList,List dataList) throws IllegalAccessException, IOException {
        //健壮性检验
        if (ObjectUtil.isEmpty(dataList)){
            return;
        }
        List<List<String>> allList = new ArrayList<>();
        //获取class文件
        Class<?> aClass = dataList.get(0).getClass();
        //通过class文件获取对象属性
        Field[] declaredFields = aClass.getDeclaredFields();
        //排除没有被标记的属性
        List<Field> fields = Arrays.asList(declaredFields).stream().filter(field -> field.isAnnotationPresent(Excel.class)).collect(Collectors.toList());
        //属性排序
        fields = fields.stream().sorted(Comparator.comparingInt(field -> field.getAnnotation(Excel.class).order())).collect(Collectors.toList());
    
        for (int i = 0; i < dataList.size(); i++) {
            List<String> list = new ArrayList<>();
            for (int j = 0; j < fields.size(); j++) {
                Field declareField = fields.get(j);
                declareField.setAccessible(true);
                Object o = declareField.get(dataList.get(i));
                if (o != null){
                    list.add(o.toString());
                }
            }
            allList.add(list);
        }
        export(outputStream,columList,allList);
    }