若依框架 excel导出功能详解(一)

2,316 阅读3分钟

若依框架 excel导出功能详解(一)

若依框架作为被广泛应用的二次开发框架,其在excel导出方面用到了很多相关api,接下来就详细讲解一下若依框架的导出功能执行过程,以及老版本存在的bug(3.8.8及以前)。

调用方式

     ExcelUtil<类名> util = new ExcelUtil<RmsReport>(类名.class);
   util.exportExcel(response, list, 名称);
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName, String title)
    {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        this.init(list, sheetName, title, Type.EXPORT);
        exportExcel(response);
    }

第一段是导出的调用方式,类名就是所对应的实体类名。第二段是倒数第二步的背后调用,这一步分为两阶段,首先是初始化即 this.init方法,然后是将数据写入即exportExcel方法。

初始化 init方法

public void init(List<T> list, String sheetName, String title, Type type)
    {
        if (list == null)
        {
            list = new ArrayList<T>();
        }
        this.list = list;
        this.sheetName = sheetName;
        this.type = type;  //Type.EXPORT代表用来输出
        this.title = title;//StringUtils.EMPTY 默认为空
        createExcelField();//创建表中字段
        createWorkbook();//创建工作簿以及工作表
        createTitle();//填写标题(系统默认title参数为空,则此函数会判断为标题为空后直接跳过)
        createSubHead();//填写子标题(针对字段中包含子对象list)
    }

初始化即是上面的代码,通过注释可知初始化主要是做准备工作,这四个函数中最重要的是第一个函数,因为获得的字段中是否包含了子对象列表,决定了后续填写子标题函数是否执行,以及影响第二阶段数据的写入。所以重点介绍createExcelField()方法。

private void createExcelField()
    {
        this.fields = getFields();//获得字段
        this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());//根据excel注释中的sort属性进行排序
        this.maxHeight = getRowHeight();//根据excel注释中的height属性获得最高的高度作为表格高度。
    }
    
  public List<Object[]> getFields()
    {
        List<Object[]> fields = new ArrayList<Object[]>();
        List<Field> tempFields = new ArrayList<>();
        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        for (Field field : tempFields)
        {
            if (!ArrayUtils.contains(this.excludeFields, field.getName()))
            {
                // 单注解
                if (field.isAnnotationPresent(Excel.class))
                {
                    Excel attr = field.getAnnotation(Excel.class);
                    if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
                    {
                        field.setAccessible(true);
                        fields.add(new Object[] { field, attr });
                    }
                    if (Collection.class.isAssignableFrom(field.getType()))
                    {
                    //获得字段的get方法,用于后续写入数据时获得对应的值。
                        subMethod = getSubMethod(field.getName(), clazz);
          // 获取字段的泛型类型。如果字段是带泛型的集合(如List<String>),则返回 List<String>
                        ParameterizedType pt = (ParameterizedType) field.getGenericType() 
//获取泛型参数的实际类型。比如List<String>,这会返回String的Class对象。[0]表示第一个泛型参数(集合通常只有一个泛型参数,即元素类型)。                      
                       Class<?> subClass = (Class<?>) pt.getActualTypeArguments()[0];
                  //Apache Commons Lang的FieldUtils,用于获取类中带有Excel注解的字段列表。
            this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
                    }
                }

                // 多注解
                if (field.isAnnotationPresent(Excels.class))
                {
                    Excels attrs = field.getAnnotation(Excels.class);
                    Excel[] excels = attrs.value();
                    for (Excel attr : excels)
                    {
                        if (attr != null && (attr.type() == Type.ALL || attr.type() == type))
                        {
                            field.setAccessible(true);
                            fields.add(new Object[] { field, attr });
                        }
                    }
                }
            }
        }
        return fields;
    }

getFields()是创建excel字段数中的核心方法,即获得实体类的字段,这里面主要利用了java反射的相关方法,

  1. 新建一个object数组变量的list,这个list是函数的返回值,主要是用来存储字段和对应的excel注释,然后是新建一个存储字段的列表,用来存储实体类的字段,实体类的字段通过getDeclaredFields()获得。
  2. 遍历此列表中的字段,利用isAnnotationPresent方法判断是否含有excel注解,这里分为excel和excels两种注解,一般情况下是用excel即单注解,因此着重介绍单注解的判断。
  3. 单注解首先获得excel注解,然后对excel注解进行判断,包含非空判断和类型判断,其中类型判断十分重要,excel注解的类型是一个枚举类,枚举变量有三种分别为all,EXPORT,IMPORT。一般情况下excel注释默认为all,判断为true,或者为EXPORT也可以判断为true,如果只想将一个字段导入而不是导出,则 将type设为IMPORT。判断后则是将字段和excel注释存储到新建的object数组中,并添加到之前新建的list中。最后是判断字段是否为集合类型的变量,如果是的话则需要获得集合中的变量类型,并获得相应字段。if里的执行语句即获得过程,详细可以看代码上的注解。最后多注解跟单注解类似,只不过注释由excel转变成了由多个excel注释组成的excles注释。