一、maven依赖jar包
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.14</version> </dependency>
二、导出view
public class ExportMemberVo { private String name; private Integer gender; private String idCard; private String bankNo; private String bankName; private String phone; /** * 性别处理 */ public String getGender() { return gender == 0 ? "男" : "女"; } /****为了节省篇幅,省略setter/getter/constructor****/ }
三、导出Excel核心处理代码,继承自AbstractXlsView ,并实现buildExcelDocument
import export.entity.ExportMemberVo; import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.ss.usermodel.*; import org.springframework.web.servlet.view.document.AbstractXlsView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.net.URLEncoder; import java.util.List; import java.util.Map; /** * @Author Kent.Wang * @Date 2017/6/26 */ public abstract class ExcelView extends AbstractXlsView { @Override protected void buildExcelDocument(Map<String, Object> map, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception { String excelName = map.get("name").toString() + ".xls"; response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(excelName,"utf-8")); response.setContentType("application/ms-excel; charset=UTF-8"); response.setCharacterEncoding("UTF-8"); @SuppressWarnings("unchecked") List<ExportMemberVo> list = (List<ExportMemberVo>) map.get("members"); Sheet sheet = workbook.createSheet("User Detail"); sheet.setDefaultColumnWidth(30); CellStyle style = workbook.createCellStyle(); Font font = workbook.createFont(); font.setFontName("Arial"); style.setFillForegroundColor(HSSFColor.BLUE.index); style.setFillPattern((short) 1); font.setBold(true); font.setColor(HSSFColor.WHITE.index); style.setFont(font); Row header = sheet.createRow(0); header.createCell(0).setCellValue("姓名"); header.getCell(0).setCellStyle(style); header.createCell(1).setCellValue("性别"); header.getCell(1).setCellStyle(style); header.createCell(2).setCellValue("手机号"); header.getCell(2).setCellStyle(style); header.createCell(3).setCellValue("身份证号"); header.getCell(3).setCellStyle(style); header.createCell(4).setCellValue("银行卡号"); header.getCell(4).setCellStyle(style); int rowCount = 1; for (ExportMemberVo user : list) { Row userRow = sheet.createRow(rowCount++); userRow.createCell(0).setCellValue(user.getName()); userRow.createCell(1).setCellValue(user.getGender()); userRow.createCell(2).setCellValue(user.getPhone()); userRow.createCell(3).setCellValue(user.getIdCard()); userRow.createCell(4).setCellValue(user.getBankNo()); } } }
四、controller代码
@RequestMapping(value = "", method = RequestMethod.GET) public ModelAndView download() { List<ExportMemberVo> list = new ArrayList<ExportMemberVo>(); for (int i = 0; i < 5; i++) { ExportMemberVo exportMemberVo = new ExportMemberVo(); exportMemberVo.setName("Kent" + i); @SuppressWarnings("unchecked") int gender = ThreadLocalRandom.current().nextInt(0, 2); exportMemberVo.setGender(gender); exportMemberVo.setPhone("182xxxxxxxx"); exportMemberVo.setBankName("建设银行"); list.add(exportMemberVo); } Map<String, Object> map = new HashMap<String, Object>(); map.put("members", list); map.put("name", "魅力城市"); ExcelView excelView = new UserInfoExcelView(); return new ModelAndView(excelView, map); }
运行,访问download结果如下:
这是网络上一般的导出方法,没什么特别的,拿来即用,在使用的过程中也碰到一些问题,和疑问,下面谈谈我自己的理解。
五、火狐浏览器导出excel中文乱码问题
这个问题是因浏览器的不同所造成的,那只要对response.setHeader做些处理就可以了。我只测试了chrome和firefox,其他浏览器或许还有些差异,在此不一一枚举。
String Agent = request.getHeader("User-Agent"); if (null != Agent) { Agent = Agent.toLowerCase(); if (Agent.indexOf("firefox") != -1) { response.setHeader("content-disposition", String.format("attachment;filename*=utf-8'zh_cn'%s", URLEncoder.encode(excelName, "utf-8"))); } else { response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(excelName, "utf-8")); } }
六、buildExcelDocument是怎么被调用的
我们看到继承自AbstractXlsView 的buildExcelDocument方法是protected的,只能被同一包下面和子类调用,理论上controller不会去继承ExcelView ,也不会在同一包下,那我们如何去调用他,在springMVC和spring boot下,我们需要用到ModelAndView。
我通过两幅图来列举下buildExcelDocument调用的过程:
download方法执行return之后会大致执行如下过程,我们可以看到buildExcelDocument是如何被调用的,具体过程有兴趣可以自己debug。
其中比较重要的一个环节是view.render,这里用到了Java的多态特性,AbstractView是继承自View的,ExcelView自然也继承View,所以获取到的ModelAndView中的view执行view.render实际上会去调用AbstractView的render方法,然后AbstractView中有个抽象方法renderMergeOutputModel,供子类实现不同的输出模型,输出Excel文件就是其中的一个子类,还有输出PDF文件也是同理实现该方法。
具体handle方法返回ModelAndView的过程如下:
七、包装一下Excel输出模版
我们可能需要输出许多Excel,有成员信息,商品信息,规格信息等等。所以将所有设置Excel的代码全放在ExcelView类中有点不合时宜,很显然至少列名和值的绑定都写死了,难以扩展。
仿照AbstractView中用到的模板方法模式,我们也可以将具体设置Sheet提取出来,写个抽象方法,由子类去实现具体设置Sheet.
修改过的ExcelView如下:
public CellStyle cellStyle; /** * 设置样式 * * @param workbook */ protected abstract void setStyle(Workbook workbook); /** * 设置Row,由子类实现 * * @param sheet * @param map */ protected abstract void setRow(Sheet sheet, Map<String, Object> map); @Override protected void buildExcelDocument(Map<String, Object> map, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception { String excelName = map.get("name").toString() + ".xls"; String Agent = request.getHeader("User-Agent"); if (null != Agent) { Agent = Agent.toLowerCase(); if (Agent.indexOf("firefox") != -1) { response.setHeader("content-disposition", String.format("attachment;filename*=utf-8'zh_cn'%s", URLEncoder.encode(excelName, "utf-8"))); } else { response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(excelName, "utf-8")); } } response.setContentType("application/ms-excel; charset=UTF-8"); Sheet sheet = workbook.createSheet("User Detail"); sheet.setDefaultColumnWidth(30); this.setStyle(workbook); setRow(sheet, map); }
我们写个导出成员信息UserInfoExcelView ,所有Sheet设置都在这里完成:
public class UserInfoExcelView extends ExcelView { @Override public void setRow(Sheet sheet, Map<String, Object> map) { // create header row Row header = sheet.createRow(0); header.createCell(0).setCellValue("姓名"); header.getCell(0).setCellStyle(super.cellStyle); header.createCell(1).setCellValue("性别"); header.getCell(1).setCellStyle(super.cellStyle); header.createCell(2).setCellValue("手机号"); header.getCell(2).setCellStyle(super.cellStyle); header.createCell(3).setCellValue("身份证号"); header.getCell(3).setCellStyle(super.cellStyle); header.createCell(4).setCellValue("银行卡号"); header.getCell(4).setCellStyle(super.cellStyle); @SuppressWarnings("unchecked") List<ExportMemberVo> list = (List<ExportMemberVo>) map.get("members"); int rowCount = 1; for (ExportMemberVo user : list) { Row userRow = sheet.createRow(rowCount++); userRow.createCell(0).setCellValue(user.getName()); userRow.createCell(1).setCellValue(user.getGender()); userRow.createCell(2).setCellValue(user.getPhone()); userRow.createCell(3).setCellValue(user.getIdCard()); userRow.createCell(4).setCellValue(user.getBankNo()); } } @Override protected void setStyle(Workbook workbook) { DefaultCellStyle defaultCellStyle = new DefaultCellStyleImpl(); super.cellStyle = defaultCellStyle.setCellStyle(workbook); } }
如果还需要导出其他excel,同样继承下ExcelView并实现setRow和setStyle就可以了。
由于样式可能会设置通用的,但又有扩展的可能性,所以可以实现一个默认样式接口。
DefaultCellStyle.java
public interface DefaultCellStyle { CellStyle setCellStyle(Workbook workbook); }
DefaultCellStyleImpl.java
public class DefaultCellStyleImpl implements DefaultCellStyle { @Override public CellStyle setCellStyle(Workbook workbook) { // create style for header cells CellStyle cellStyle = workbook.createCellStyle(); Font font = workbook.createFont(); font.setFontName("Arial"); cellStyle.setFillForegroundColor(HSSFColor.BLUE.index); cellStyle.setFillPattern((short) 1); font.setBold(true); font.setColor(HSSFColor.WHITE.index); cellStyle.setFont(font); return cellStyle; } }
https://blog.csdn.net/wang124454731/article/details/73850645