软工项目->java 注解与反射机制实战:springboot+mybatis项目中的excel工具类

契机

这次开发的软工教学助手中,涉及excel文件的导入问题。在此之前的类似项目中,都是写一些复用性很差、很繁琐的代码来处理excel表格。此次软工课上系统地规划了一下时间,发现按照时间规划可以好好打磨一下这部分代码,写出一个方便快捷复用性高的工具类,就决定这样做了。

思路

之前在看到一位大神,写的工具类可以使用注解来标注实体类的属性,再利用反射机制很方便地把excel中的数据映射成对象或者把对象存储到excel中。正巧前段时间钻研了一下java的注解和反射机制,于是乎决定仿照大神的写法,写一个功能更轻量化的excel工具类。
自定义一个注解,来标注实体类的列名和列序号。
然后编写一个excel工具类,工具类中实现一个解析注解的方法,并且根据解析的结果,读取excel数据或者将excel数据导出。

代码

首先是自定义注解。由于我们需要利用反射机制来解析注解标注的实体类,所以我们自定义的注解一定要在运行时存活,所以其生命周期应当设置为@Retention(RetentionPolicy.RUNTIME)
除此之外,我们的自定义注解的标注对象只有实体类的属性,所以其标注对象应当设置为@Target(ElementType.FIELD)
而标注的内容则比较简单,只有列名和列序号。所以最终注解的代码为

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelAnnotation {
    String name();
    int col();

}

而使用起来也很简单,下面是student实体类

public class Student implements Serializable {
    private Integer id;

    @ExcelAnnotation(name = "学号",col = 0)
    private Long stuID;

    @ExcelAnnotation(name = "姓名",col = 1)
    private String name;

    @ExcelAnnotation(name = "密码",col= 2)
    private String password;

    @ExcelAnnotation(name = "方向id",col = 3)
    private Integer p_field_id;
}

只要在属性上方使用注解标注列名和列号就可以了。
自定义注解结束后,我们要开始编写excel工具类。
首先工具类解析的目标实体类在整个处理流程是唯一不变的,所以我们在声明工具类时应当采用来限制操作的对象

public class ExcelUtil<T> {
}

除此之外,如果想利用反射机制来解析目标实体类,就要获取该实体类在JVM中的class对象,这个class对象可以作为我们工具类的成员来方便后续调用

public class ExcelUtil<T> {
    private Class<T> tClass;
    //构造函数
    public ExcelUtil(Class<T> tClass){
        this.tClass = tClass;
    }
}

然后不管是excel解析的结果,还是可能用于导出的实体数据,我们都将其存入链表中,这时候就需要List了,而这些数据也可以作为我们工具类的成员来方便后续调用

public class ExcelUtil<T> {
    private Class<T> tClass;
    private List<T> data;
    //构造函数
    public ExcelUtil(Class<T> tClass){
        this.tClass = tClass;
        this.data = new ArrayList<T>();
    }
}

紧接着我们还有可能解析出的属性集。这个我们也可以将其存入链表中,并作为一个成员来方便后续调用

public class ExcelUtil<T> {
    private Class<T> tClass;
    private List<T> data;
    private List<Field> fields;

    //构造函数
    public ExcelUtil(Class<T> tClass){
        this.tClass = tClass;
        this.data = new ArrayList<T>();
        this.fields = new ArrayList<Field>();
    }
}

接下来,则是我们需要对注解进行解析的方法,而解析的结果可以存入成员变量fields中

    //解析注解获取属性集的方法
    public void parseAnnotation(Class cClass){
        if(this.fields==null){
            this.fields = new ArrayList<Field>();
        }
        Field[] fieldsArray = cClass.getDeclaredFields();
        for(Field f:fieldsArray){
            //将有excelAnnotation注解的属性加入属性列表
            boolean hasAnnotation = f.isAnnotationPresent(ExcelAnnotation.class);
            if(hasAnnotation){
                //设置私有成员可以通过反射访问
                f.setAccessible(true);
                this.fields.add(f);
            }
        }
        //如果本类存在超类(并且超类的类型为class),则递归解析超类
        if(cClass.getSuperclass()!=null&&cClass.getSuperclass().equals(Object.class)){
            parseAnnotation(cClass.getSuperclass());
        }
    }

然后我们对这个方法进行一下包装,因为我们最初传入的参数肯定是构造函数中初始化的class对象tClass,所以我们可以将这个成员作为默认参数

    //重载parseAnnotation方法设置默认参数
    public void parseAnnotation(){
        parseAnnotation(this.tClass);
    }

然后这个解析注解的方法可以放在构造函数中直接将成员fields的内容解析出来。所以整理后的代码如下

public class ExcelUtil<T> {
    private Class<T> tClass;
    private List<T> data;
    private List<Field> fields;

    //构造函数
    public ExcelUtil(Class<T> tClass){
        this.tClass = tClass;
        this.data = new ArrayList<T>();
        this.fields = new ArrayList<Field>();
        //解析注解获取属性集
        parseAnnotation();
    }

    public List<T> getData() {
        return data;
    }

    //解析注解获取属性集的方法
    public void parseAnnotation(Class cClass){
        if(this.fields==null){
            this.fields = new ArrayList<Field>();
        }
        Field[] fieldsArray = cClass.getDeclaredFields();
        for(Field f:fieldsArray){
            //将有excelAnnotation注解的属性加入属性列表
            boolean hasAnnotation = f.isAnnotationPresent(ExcelAnnotation.class);
            if(hasAnnotation){
                //设置私有成员可以通过反射访问
                f.setAccessible(true);
                this.fields.add(f);
            }
        }
        //如果本类存在超类(并且超类的类型为class),则递归解析超类
        if(cClass.getSuperclass()!=null&&cClass.getSuperclass().equals(Object.class)){
            parseAnnotation(cClass.getSuperclass());
        }
    }
    //重载parseAnnotation方法设置默认参数
    public void parseAnnotation(){
        parseAnnotation(this.tClass);
    }
}

到目前为止,这个工具类可以做到解析实体类注解了。如此我们便可以把注意力集中在excel的处理上。
对于读入excel文件,poi需要输入流和sheet名,所以我们的导入工具类也需要这两个参数。这里需要注意的地方是,对于读出来的excel数据我们需要构造成实体类的对象,而对象的属性的类型对我们来说是未知的,所以选择的处理办法如下:

  • 先将excel的原始数据全部处理成字符串
  • 再利用反射判断目标属性的类型,然后对字符串进行强制类型转换
//导入excel文件
    public void importExcel(InputStream inputStream,String sheetName){
        try {
            Workbook workbook = WorkbookFactory.create(inputStream);
            Sheet sheet;
            //如果不设置表名默认获取第一张表
            if(sheetName==null){
                sheet = workbook.getSheetAt(0);
            }else {
                sheet = workbook.getSheet(sheetName);
            }
            //获取行数
            int rows = sheet.getPhysicalNumberOfRows();
            //不为空表时才进行处理
            if(rows>0){
                //认为第一行是表头,所以从第二行开始获取
                for(int i = 1;i<rows;i++){
                    //获取本行数据
                    Row row = sheet.getRow(i);
                    T entity = null;
                    //获取各属性数据
                    for(Field f : this.fields){
                        //若属性为空,不予处理
                        if(f==null){
                            continue;
                        }
                        //读取对应列的单元格
                        Cell cell = row.getCell( f.getAnnotation(ExcelAnnotation.class).col() );
                        //如果单元格为空,不予处理
                        if(cell==null){
                            continue;
                        }
                        //根据单元格的类型设置值
                        String str;
                        if(cell.getCellTypeEnum()== CellType.NUMERIC){
                            str = String.valueOf(cell.getNumericCellValue());
                        }else if(cell.getCellTypeEnum()==CellType.BOOLEAN){
                            str = String.valueOf(cell.getBooleanCellValue());
                        }else {
                            str = cell.getStringCellValue();
                        }
                        //如果str为空,则不予处理
                        if( str==null || str.equals("")){
                            continue;
                        }
                        //存在实例则继续使用实例,不存在实例则实例化(首次循环无实例)
                        entity = (entity==null?this.tClass.newInstance():entity);
                        Class<?> fieldType = f.getType();
                        //根据当前属性的类型赋值
                        if(fieldType == String.class){
                            f.set(entity,str);
                        }else if(fieldType==Integer.TYPE||fieldType==Integer.class){
                            f.set(entity,new Integer((int)Double.parseDouble(str)));
                        }else if(fieldType==Float.TYPE||fieldType==Float.class){
                            f.set(entity,Float.parseFloat(str));
                        }else if(fieldType==Double.TYPE||fieldType==Double.class){
                            f.set(entity,Double.parseDouble(str));
                        }else if(fieldType==Short.TYPE||fieldType==Short.class){
                            f.set(entity,Short.parseShort(str));
                        }else if(fieldType==Long.TYPE||fieldType==Long.class){
                            f.set(entity,Long.parseLong(str));
                        }else if(fieldType==Byte.TYPE||fieldType==Byte.class){
                            f.set(entity,Byte.parseByte(str));
                        }else if(fieldType==Character.TYPE||fieldType==Character.class){
                            f.set(entity,str.charAt(0));
                        }

                    }
                    //记录本行数据
                    if(entity!=null){
                        this.data.add(entity);
                    }

                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvalidFormatException e) {
            e.printStackTrace();
        }
    }

由于我们大多数时候的数据源是springboot框架传入的MultipartFile对象,所以对于这个导入方法我们可以进行重载来适应MultipartFile对象

//重载importExcel方法改变参数
public void importExcel(MultipartFile file){
    try {
        importExcel(file.getInputStream(),null);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

既然已经使用了自定义注解,那么导出也变得很方便了,因为我们可以利用反射机制获取注解内容来构造导出的表头。

public HSSFWorkbook exportExcel(){
        HSSFWorkbook workbook =  new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet();
        HSSFRow row;
        HSSFCell cell;
        row = sheet.createRow(0);
        for(Field f:this.fields){
            //属性为空,不予处理
            if(f==null){
                continue;
            }
            //获取列
            int col = f.getAnnotation(ExcelAnnotation.class).col();
            //生成单元格
            cell = row.createCell(col);
            //设置单元格值
            cell.setCellValue(f.getAnnotation(ExcelAnnotation.class).name());
        }
        for(int i=0;i<this.data.size();i++){
            row = sheet.createRow(i+1);
            for(Field f:this.fields){
                int col = f.getAnnotation(ExcelAnnotation.class).col();
                HSSFCellStyle textStyle = workbook.createCellStyle();
                HSSFDataFormat format = workbook.createDataFormat();
                textStyle.setDataFormat(format.getFormat("@"));
                cell = row.createCell(col);
                cell.setCellStyle(textStyle);//设置单元格格式为"文本"
                cell.setCellType(CellType.STRING);
                try {
                    cell.setCellValue( f.get(this.data.get(i))==null?"":String.valueOf(f.get(this.data.get(i))) );
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return workbook;
    }

整理后的完整工具类代码如下

public class ExcelUtil<T> {
    private Class<T> tClass;
    private List<T> data;
    private List<Field> fields;

    //构造函数
    public ExcelUtil(Class<T> tClass){
        this.tClass = tClass;
        this.data = new ArrayList<T>();
        this.fields = new ArrayList<Field>();
        //解析注解获取属性集
        parseAnnotation();
    }

    public List<T> getData() {
        return data;
    }

    public void setData(List<T> data) {
        this.data = data;
    }

    //解析注解获取属性集的方法
    public void parseAnnotation(Class cClass){
        if(this.fields==null){
            this.fields = new ArrayList<Field>();
        }
        Field[] fieldsArray = cClass.getDeclaredFields();
        for(Field f:fieldsArray){
            //将有excelAnnotation注解的属性加入属性列表
            boolean hasAnnotation = f.isAnnotationPresent(ExcelAnnotation.class);
            if(hasAnnotation){
                //设置私有成员可以通过反射访问
                f.setAccessible(true);
                this.fields.add(f);
            }
        }
        //如果本类存在超类(并且超类的类型为class),则递归解析超类
        if(cClass.getSuperclass()!=null&&cClass.getSuperclass().equals(Object.class)){
            parseAnnotation(cClass.getSuperclass());
        }
    }

    //重载parseAnnotation方法设置默认参数
    public void parseAnnotation(){
        parseAnnotation(this.tClass);
    }

    //导入excel文件
    public void importExcel(InputStream inputStream,String sheetName){
        try {
            Workbook workbook = WorkbookFactory.create(inputStream);
            Sheet sheet;
            //如果不设置表名默认获取第一张表
            if(sheetName==null){
                sheet = workbook.getSheetAt(0);
            }else {
                sheet = workbook.getSheet(sheetName);
            }
            //获取行数
            int rows = sheet.getPhysicalNumberOfRows();
            //不为空表时才进行处理
            if(rows>0){
                //认为第一行是表头,所以从第二行开始获取
                for(int i = 1;i<rows;i++){
                    //获取本行数据
                    Row row = sheet.getRow(i);
                    T entity = null;
                    //获取各属性数据
                    for(Field f : this.fields){
                        //若属性为空,不予处理
                        if(f==null){
                            continue;
                        }
                        //读取对应列的单元格
                        Cell cell = row.getCell( f.getAnnotation(ExcelAnnotation.class).col() );
                        //如果单元格为空,不予处理
                        if(cell==null){
                            continue;
                        }
                        //根据单元格的类型设置值
                        String str;
                        if(cell.getCellTypeEnum()== CellType.NUMERIC){
                            str = String.valueOf(cell.getNumericCellValue());
                        }else if(cell.getCellTypeEnum()==CellType.BOOLEAN){
                            str = String.valueOf(cell.getBooleanCellValue());
                        }else {
                            str = cell.getStringCellValue();
                        }
                        //如果str为空,则不予处理
                        if( str==null || str.equals("")){
                            continue;
                        }
                        //存在实例则继续使用实例,不存在实例则实例化(首次循环无实例)
                        entity = (entity==null?this.tClass.newInstance():entity);
                        Class<?> fieldType = f.getType();
                        //根据当前属性的类型赋值
                        if(fieldType == String.class){
                            f.set(entity,str);
                        }else if(fieldType==Integer.TYPE||fieldType==Integer.class){
                            f.set(entity,new Integer((int)Double.parseDouble(str)));
                        }else if(fieldType==Float.TYPE||fieldType==Float.class){
                            f.set(entity,Float.parseFloat(str));
                        }else if(fieldType==Double.TYPE||fieldType==Double.class){
                            f.set(entity,Double.parseDouble(str));
                        }else if(fieldType==Short.TYPE||fieldType==Short.class){
                            f.set(entity,Short.parseShort(str));
                        }else if(fieldType==Long.TYPE||fieldType==Long.class){
                            f.set(entity,Long.parseLong(str));
                        }else if(fieldType==Byte.TYPE||fieldType==Byte.class){
                            f.set(entity,Byte.parseByte(str));
                        }else if(fieldType==Character.TYPE||fieldType==Character.class){
                            f.set(entity,str.charAt(0));
                        }

                    }
                    //记录本行数据
                    if(entity!=null){
                        this.data.add(entity);
                    }

                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvalidFormatException e) {
            e.printStackTrace();
        }
    }

    //重载importExcel方法改变参数
    public void importExcel(MultipartFile file){
        try {
            importExcel(file.getInputStream(),null);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    

    //使用实体类表头导出excel
    public HSSFWorkbook exportExcel(){
        HSSFWorkbook workbook =  new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet();
        HSSFRow row;
        HSSFCell cell;
        row = sheet.createRow(0);
        //构造表头
        for(Field f:this.fields){
            //属性为空,不予处理
            if(f==null){
                continue;
            }
            //获取列
            int col = f.getAnnotation(ExcelAnnotation.class).col();
            //生成单元格
            cell = row.createCell(col);
            //设置单元格值
            cell.setCellValue(f.getAnnotation(ExcelAnnotation.class).name());
        }
        //填充数据
        for(int i=0;i<this.data.size();i++){
            row = sheet.createRow(i+1);
            for(Field f:this.fields){
                int col = f.getAnnotation(ExcelAnnotation.class).col();
                HSSFCellStyle textStyle = workbook.createCellStyle();
                HSSFDataFormat format = workbook.createDataFormat();
                textStyle.setDataFormat(format.getFormat("@"));
                cell = row.createCell(col);
                cell.setCellStyle(textStyle);//设置单元格格式为"文本"
                cell.setCellType(CellType.STRING);
                try {
                    cell.setCellValue( f.get(this.data.get(i))==null?"":String.valueOf(f.get(this.data.get(i))) );
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return workbook;
    }



}
posted @ 2020-11-10 16:58  SemiprimeNumber  阅读(248)  评论(0编辑  收藏  举报