最近项目中遇到需要excle导出的需求,百度搜索关键词,excle和spring mvc出来的结果都是代码及其长,并且还是直接设置response的原始方法
不得不说国内的程序员大都只追求能够勉强实现功能,很少想到优雅,我在bing上搜索(没有vpn哭)同样关键词,结果至少是返回modelandview对象,但是需要自己实现视图的基础上,还要实现视图解析器
话不多说,上正菜:
本文使用注解和自定义视图的方式实现excle导出功能,不需要实现自定义视图解析器,只要用默认视图解析器InternalResourceViewResolver就可以,
现在网上还没有看到使用注解来简化excle导出的文章,本文算是首创,是我目前水平下想到的最好的实现方式,希望大家可以多多指教
本文是在使用Spring MVC和Poi的前提
- 先定义注解,大部分注释内容我直接放在代码中
1 @Target(ElementType.FIELD)//标注用来注解field类型 2 @Retention(RetentionPolicy.RUNTIME)//标注运行时注解 3 public @interface Export { 4 //不设置default表示必填 5 String title();//title表示导出时的标题 6 int index();//inde表示导出时改字段的列号 7 8 Class format() default String.class;//默认使用String.class只是标注,自定义的不可能是String.class 9 }
其中 :title表示导出时的标题,inde表示导出时改字段的列号,format用于格式化一些字段,由于是非必填项,所以要指定默认值,我这里指定了String.class。
2.自定义一个需要导出的model类,也就是bean类,在需要导出的字段上加入注解
public class Company extends BaseModel{ @Export(title = "企业名称",index = 1) private String name; @Export(title = "统一社会信用代码",index = 2) private String uniformSocialCreditCode; //1:规模以上;0:规模以下 @Export(title = "规模",index = 3,format = Scale.class) private Byte scale; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getUniformSocialCreditCode() { return uniformSocialCreditCode; } public void setUniformSocialCreditCode(String uniformSocialCreditCode) { this.uniformSocialCreditCode = uniformSocialCreditCode; } public Byte getScale() { return scale; } public void setScale(Byte scale) { this.scale = scale; } }
解析注解需要用到反射
//生成标题列,生成内容列是一样的,只是多出一个解析字段值
private void generateHead(Field[] fields) { currentSheetHeadRow = currentSheet.createRow(0); //这里我学习lambda表达式,读者也可以直接用for循环 Arrays.stream(fields)
.filter(field -> field.isAnnotationPresent(Export.class))//找出有Export注解的field,也就是需要导出的字段
.forEach((field) -> { String title = field.getAnnotation(Export.class).title(); int index = field.getAnnotation(Export.class).index(); createHeadTitle(title, index); }); } //指定的index位置加入标题 private void createHeadTitle(String name, int index) { Cell c1 = currentSheetHeadRow.createCell(index); c1.setCellValue(name); }
3.我的例子中有一个需要规模字段,它是比较典型的一类,就是导出的时候与实际的值并不相同,需要转换,此时定义一个枚举类最为合适,这里只是供大家参考
我的Scale枚举类,用来解析字段值,
public enum Scale { ABOVE("规模以上",1),BLOW("规模以下",0),NULL("",-1); private String name; private Integer index; Scale(String name, Integer index) { this.name = name; this.index = index; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getIndex() { return index; } public void setIndex(Integer index) { this.index = index; } }
此时导出功能的基本的注解部分已经实现
接下来自定义一个exle的视图,到时候在Controller中返回modelAndView的时候加入这个视图类即可
/**
*AbstractXlsxView是Spring内置的类是2007版之后的excle版本
*如果你想要2003版本,这里只需要继承AbstractXlsView即可,其他不需要任何改动
*/
public class ExcleView extends AbstractXlsxView{
//导出文件名 private String fileName; public ExcleView(String excleName){ super(); this.fileName = excleName; } @Override protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception { List<BaseModel> list = (List<BaseModel>)model.get("list"); Sheet currentSheet = workbook.createSheet(); for (BaseModel baseModel : list) { //....省略具体代码
//可以循环的添加cell } String name = URLEncoder.encode(fileName+".xlsx", "UTF-8"); response.setHeader("Content-Disposition", "attachment; filename="+name); } }
Controller中的方法示例
public ModelAndView exportExcel(@ModelAttribute CompanyBasic model) throws IOException{ List<Company> list = new ArrayList<>(); //示例创建一个list列表 for (int i = 0; i <3 ; i++) { Company company = new Company(); company.setName("a"); company.setUniformSocialCreditCode("124521"); company.setScale((byte)1); list.add(company); }
//将list放入Map类型的对象中,即可 Map<String, Object> map = new HashMap<>(); map.put("list",list); return new ModelAndView(new ExcleView("企业列表"),map); }
只要返回的时候 return new ModelAndView(new ExcleView("企业列表"),map); 其中企业列表是导出的文件名
自此,以后再有其他需要导出的类,只要在对应的字段加上titie和index注解,在对应Controller中,查询中list,再new ExcleVIew(filename)的时候传入文件名即可
大功告成