利用POI手搓一个简单的excel导出工具(含完整代码)
- POI简介
全称Apache POI,使用Java编写的免费开源的跨平台的Java API。 是创建和维护操作各种符合 Office Open XML(OOXML)标准和微软的 OLE 2 复合文档格式(OLE2)的 Java API。用它可以使用 Java 读取和创建, 修改 MS Excel 文件. 而且, 还可以使用 Java 读取和创建 MS Word 和 MSPowerPoint 文件。Apache POI 提供 Java 操作 Excel 解决方案(适用于 Excel97-2008)
下面我们来利用poi提供的方法,来写一个简单的Excel导出工具,由于部分方法中的方法调用较为频繁,这里就不一一写出来了,源码在文章末尾。
首先,我们创建一个注解,用于标注导出字段,代码如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Excel {
/**
* 列名
*/
String name();
/**
* 日期格式
*/
String dateFormat() default "";
/**
* 日期时间格式,用的jdk8的时间类,感觉两个格式字段有些多余了,但是Date类型操作起来没有LocalDate方便,先试试,不行就换
* @return
*/
String dateTimeFormat() default "";
/**
* 列高
*/
int height() default 20;
/**
* 列宽
*/
int width() default 20;
/**
* 当值为空时的默认值
*/
String defaultValue() default "";
/**
* 类型:IS_EXPORT导入,IS_IMPORT导出,ALL导入导出
*/
ExcelType excelType() default ExcelType.ALL;
/**
* 是否可选字段,如果为true时,会根据请求参数来判断该字段是否显示
*/
boolean isOptional() default false;
/**
* 当isOptional属性为true时,会根据这个字段值来判断该列是否导出
*/
String judgeStr() default "";
/**
* 值转换,将某些特定值转换为另一个值,如:0:女,1:男,2:未知
*/
String valueConvert() default "";
/**
* 前缀
*/
String prefix() default "";
/**
* 后缀
*/
String suffix() default "";
/**
* 是否只读
*/
boolean isReadOnly() default false;
/**
* 是否需要判空
*/
boolean isBlank() default false;
}
注解类上的两个注解意义分别是:
- @Target(ElementType.FIELD) 该注解只作用于字段
- @Retention(RetentionPolicy.RUNTIME) 注解生命周期,运行期存在
上面便是注解字段,只完善了部分必要功能。
接下来编写excel的工具类,分别为导入和导出两个功能
首先先创建一个工具类,再在工具类中增加部分需要的字段
public class ExcelUtil <T>{
/**
* sheet的最大行数
*/
private final static int SHEET_MAX_NUM=65536;
/**
* 数据导入/导出时开始的行数
*/
private final static int DATA_BEGIN_INDEX=2;
/**
* 默认的工作表下标
*/
private final static int DEFAULT_SHEET_INDEX=0;
/**
* 字段描述行下标
*/
private final static int FIELD_ROW_INDEX =1;
/**
* 实体类型
*/
private Class<T> clazz;
/**
* 工作蒲对象
*/
private Workbook wb;
/**
* 工作表对象
*/
private Sheet sheet;
/**
* 样式
*/
private Map<String, CellStyle> style;
/**
* 标题合并单元格的数量
*/
private int titleMergeCellNum;
/**
* excel中不显示的字段
*/
private Map<String,String> judgeStrMap=new HashMap<>();
/**
* 存储字段和注解的集合
*/
private List<Object[]> fieldAndExcelList;
/**
* 最大列高
*/
private int height;
}
再重写它的构造函数,确定导出类型
public ExcelUtil(Class<T> clazz){
this.clazz=clazz;
}
开始编写导出方法,首先进行初始化类中的工作簿等对象
/**
* 初始化
* @param request
* @param sheetName
*/
public void init(HttpServletRequest request,String sheetName) {
//创建工作蒲
wb = new SXSSFWorkbook(300);
//创建工作表
sheet=wb.createSheet(sheetName);
//初始化样式,具体样式和方法参考文档:http://poi.apache.org/apidocs/dev/org/apache/poi/hssf/usermodel/HSSFCellStyle.html
style=new HashMap<>(3);
CellStyle title=wb.createCellStyle();
//设置填充色
//设置边框样式
setBorderStyle(title);
//设置字体样式
Font titleFont=wb.createFont();
titleFont.setFontHeight((short)(16*20));
titleFont.setFontName("Arial");
title.setFont(titleFont);
//设置对齐方式
setBackgroundColorAndAlignment(title);
//标题
style.put("title",title);
CellStyle field=wb.createCellStyle();
setBorderStyle(field);
//设置字体样式
Font fieldFont=wb.createFont();
fieldFont.setFontHeight((short)(12*20));
fieldFont.setFontName("Arial");
fieldFont.setBold(false);
field.setFont(fieldFont);
//设置对齐方式
setBackgroundColorAndAlignment(field);
//字段标题
style.put("field",field);
CellStyle data=wb.createCellStyle();
setBorderStyle(data);
DataFormat dataFormat =wb.createDataFormat();
//默认为文本类型
data.setDataFormat(dataFormat.getFormat("@"));
//数据
style.put("data",data);
//获取类的字段信息
Field [] fields=clazz.getDeclaredFields();
//合并单元格时,由于是从0开始合并的,需要判断出第一个不用进行加减操作
boolean isTitleMergeCellNum=true;
fieldAndExcelList =new ArrayList<>();
for (Field field : fields) {
//获取字段上的Excel注解,如果没有,或者不是导出类型,就返回进行下一次循环
Excel excel = field.getAnnotation(Excel.class);
if (excel == null||excel.excelType()== ExcelType.IS_IMPORT) {
continue;
}
//获取单元格最大高度
height=excel.height()>height?excel.height():height;
//判断是否为可选字段,如果不是,添加完字段信息便返回
if (!excel.isOptional()) {
fieldAndExcelList.add(new Object[]{field, excel});
if (isTitleMergeCellNum) {
isTitleMergeCellNum = false;
continue;
}
//标题所占单元格数
titleMergeCellNum += 1;
continue;
}
//如果是可选字段,判断前端传的参数如果参数和judgeStr相同,就不导出该字段
String judgesStrValue = request.getParameter(excel.judgeStr());
if (judgesStrValue != null) {
//Excel中不显示的字段map
judgeStrMap.put(excel.judgeStr(), judgesStrValue);
continue;
}
fieldAndExcelList.add(new Object[]{field, excel});
titleMergeCellNum += 1;
}
}
创建工作表并导入数据
/**
* 导出excel
* @param request
* @param response
* @param dataList
* @param fileEnum
* @throws IllegalAccessException
*/
public void export(HttpServletRequest request,HttpServletResponse response, List<T> dataList,FileName fileEnum) throws IllegalAccessException {
String sheetName=fileEnum.getSheetName();
init(request,sheetName);
//数据导入开始下标
int rowBeginIndex=0;
//计算数据导出所需sheet数
double sheetNum=Math.ceil(dataList.size()/SHEET_MAX_NUM);
for (int i=0;i<=sheetNum;i++){
if(i>0){
sheet=wb.createSheet(sheetName+i);
rowBeginIndex+=SHEET_MAX_NUM;
}
//创建标题
if(titleMergeCellNum>0){
sheet.addMergedRegion(new CellRangeAddress(0,0,0, titleMergeCellNum));
}
Row titleRow=createRow(0);
titleRow.setHeight((short)(20*20));
Cell titleCell=titleRow.createCell(0);
titleCell.setCellValue(fileEnum.getTitleValue());
titleCell.setCellStyle(style.get("title"));
//创建字段行
Row fieldRow=createRow(1);
//单元格下标
int listSize= fieldAndExcelList.size();
CellStyle fieldStyle=style.get("field");
for (int i2=0;i2<listSize;i2++){
Excel excelObj=(Excel) fieldAndExcelList.get(i2)[1];
//如果字段是不需要导出的,就返回
if(judgeStrMap.get(excelObj.judgeStr())!=null){
continue;
}
//创建单元格
Cell fieldCell=createCell(fieldRow,i2,fieldStyle);
//向单元格内填充字段信息
fieldCell.setCellValue(excelObj.name());
//设置列宽
sheet.setColumnWidth(i2,excelObj.width()*256);
}
//开始导入数据
importData(dataList,rowBeginIndex);
}
ByteArrayOutputStream os = new ByteArrayOutputStream();
OutputStream out=null;
try {
wb.write(os);
byte[] bytes = os.toByteArray();
response.addHeader("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileEnum.getFileName()+".xlsx", "utf-8"));
out= response.getOutputStream();
out.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
wb.close();
if(out!=null){
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
接下来就是填充数据的方法
/**
* 导入数据到excel中
* @param dataList
* @param beginIndex
* @throws IllegalAccessException
*/
private void importData(List<T> dataList,int beginIndex) throws IllegalAccessException {
int dataListSize=dataList.size();
//获取数据单元格样式
CellStyle dataStyle=style.get("data");
//数据行开始下标
int rowIndex=DATA_BEGIN_INDEX;
//字段集合长度(用来创建列)
int fieldListLength= fieldAndExcelList.size();
for (int i=beginIndex;i<dataListSize && i<beginIndex+SHEET_MAX_NUM;i++,rowIndex++){
T dataObj=dataList.get(i);
Row row=createRow(rowIndex);
for (int i2=0;i2<fieldListLength;i2++){
//获取字段信息
Field field=(Field) fieldAndExcelList.get(i2)[0];
//设置字段的访问权限
field.setAccessible(true);
//获取对象内的这个字段信息
Object object=field.get(dataObj);
//创建单元格
Cell cell=createCell(row,i2,dataStyle);
//给单元格填充值
setCellValue(cell,object,(Excel) fieldAndExcelList.get(i2)[1]);
}
}
}
导出方法到这里就结束了,接下来便是导入的方法,写完了导出方法后,导入方法写起来就相对比较容易了,导出方法如下
/**
* 导入excel
* @param inputStream 输入流
* @param rowBeginIndex 读取数据的开始下标
* @return 结果集
*/
public List<T>importExcel(InputStream inputStream,int rowBeginIndex,int sheetIndex){
List<T> res=new ArrayList<>();
try {
//将流转换为工作簿
wb=WorkbookFactory.create(inputStream);
//获取工作簿的工作表
Sheet sheet=wb.getSheetAt(sheetIndex);
//获取有效数据行数
int rowNum=sheet.getPhysicalNumberOfRows();
if(rowNum<=rowBeginIndex){
throw new NullPointerException("数据不能为空");
}
//获取字段标题的一行数据
Row headRow=sheet.getRow(FIELD_ROW_INDEX);
//获取有效列数
int headCellNum=headRow.getPhysicalNumberOfCells();
//标题map,用来存储字段标题内容和下标
Map<String,Integer> headMap=new HashMap<>(headCellNum);
//循环单元格,并取出字段名
for (int i=0;i<headCellNum;i++){
Cell headCell=headRow.getCell(i);
headMap.put(String.valueOf(headCell.getStringCellValue()),i);
}
Field [] fields=clazz.getDeclaredFields();
//创建字段map,key为字段标题的下标
Map<Integer,Field>fieldMap=new HashMap<>(headCellNum);
for (Field field:fields) {
Excel excel=field.getAnnotation(Excel.class);
if(excel.excelType()==ExcelType.IS_EXPORT){
continue;
}
//设置私有属性可以访问
field.setAccessible(true);
if (!headMap.containsKey(excel.name())){
continue;
}
fieldMap.put(headMap.get(excel.name()),field);
}
//开始获取工作表中的数据
for (int i=rowBeginIndex;i<rowNum;i++){
//获取对应的行
Row row=sheet.getRow(i);
//创建实体对象
T t=clazz.newInstance();
//循环存储字段的map,用map的key来获取对应的单元格内容
for (Map.Entry<Integer,Field>entry:fieldMap.entrySet()){
Cell cell=row.getCell(entry.getKey());
setEntityFieldValue(t,cell,entry.getValue());
}
res.add(t);
}
} catch (IOException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return res;
}
/**
* 给实体字段赋值
* @param t
* @param cell
* @param field
*/
private void setEntityFieldValue(T t, Cell cell, Field field) {
Object value=getCellValue(cell,field);
//利用反射来给实体字段赋值
ReflectUtils.invokeSetter(t,field.getName(),value);
}
接下来便是获取单元格内的内容方法
/**
* 获取单元格值
* @param cell
* @param field
* @return
*/
private Object getCellValue(Cell cell, Field field){
Class<?> fieldType=field.getType();
Object value;
CellType cellType=cell.getCellType();
//判断单元格类型,目前只实现了数值和String类型的获取
//如果是数值型的话
if(cellType==CellType.NUMERIC){
if(Double.class==fieldType||Float.class==fieldType||BigDecimal.class==fieldType){
if(Double.class==fieldType){
value=cell.getNumericCellValue();
}else if(Float.class==fieldType){
value=Float.valueOf(String.valueOf(cell.getNumericCellValue()));
}else{
value=new BigDecimal(String.valueOf(cell.getNumericCellValue()));
}
}else{
value=cell.getNumericCellValue();
String val=new DecimalFormat("0").format(value);
if(Integer.class==fieldType){
value=Integer.valueOf(val);
}else if(Long.class==fieldType){
value=Long.valueOf(val);
}else if(Short.class==fieldType){
value=Short.valueOf(val);
}
}
//如果是String型的话
}else if(cellType==CellType.STRING){
if(String.class==fieldType){
value=cell.getStringCellValue();
}else if(LocalDate.class==fieldType){
Excel excel=field.getAnnotation(Excel.class);
value=DateUtil.strToLocalDate(cell.getStringCellValue(),excel.dateFormat());
}else if(LocalDateTime.class==fieldType){
Excel excel=field.getAnnotation(Excel.class);
value=DateUtil.strToLocalDateTime(cell.getStringCellValue(),excel.dateTimeFormat());
}else {
Excel excel=field.getAnnotation(Excel.class);
String val=cell.getStringCellValue();
if("".equals(excel.valueConvert())){
value=val;
}else {
value=valueConvert(val,excel,false);
}
}
}else {
value=null;
}
return value;
}
以上便是代码的大概实现思路了,需要源码的可以去我的GitHub上拉取源码,下面是项目地址: