• 软工项目->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;
        }
    
    
    
    }
    
  • 相关阅读:
    JavaScript event对象
    JavaScript Date对象
    JavasSript函数
    break语句补充:判断当前系统时间与2021年元旦相聚的天数
    JavaScript控制流程
    初识JavaSript-JavaScript主要特点,运用在HTML中的三种用法,基本语法
    一个简单的Java程序
    什么是Java语言规范、API、JDK和IDE
    Vue渐变淡入淡出的轮播图
    js获取窗口滚动条高度、窗口可视范围高度、文档实际内容高度、滚动条离浏览器底部的高度
  • 原文地址:https://www.cnblogs.com/semiprimenumber/p/13926717.html
Copyright © 2020-2023  润新知