Java基于注解和反射导入导出Excel

1. 构建项目

使用Spring Boot快速构建一个Web工程,并导入与操作Excel相关的POI包以及一些常用的工具类包,pom文件中添加如下一些依赖:

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.9</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.9</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.google.collections</groupId>
            <artifactId>google-collections</artifactId>
            <version>1.0</version>
        </dependency>

2. 自定义Excel注解

使用注解的形式,自定义一些与操作Excel相关的基本信息,如生成Excel模板时,需要有哪些字段名、字段标题、字段之间的排序、字段中内容的位置、对齐方式等信息。然后通过在JavaBean中的需要的字段对应的getter方法上添加这些注解,就可以将其标记为Excel相关的字段。自定义注解内容主要如下(为了节省篇幅,一下代码中的注解已删除,详细代码可以看文章后面的链接)

@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelField {

    String value() default "";

    String title();
    
    int type() default 0;

    int align() default 0;

    int sort() default 0;

    String dictType() default "";

    Class<?> fieldType() default Class.class;

    int[] groups() default {};
}

3. 通过反射创建Excel模板

使用反射的方式获取类的信息、类中方法、属性,为了创建一个可供用户填写的Excel模板,我们需要在模板中定义系统需要收集的数据字段,即在JavaBean中,通过注解定义的相关字段。伪代码及关键代码如下:


 public ExportExcel(String title, Class<?> cls, int type, int... groups){
        // Get annotation field
        Field[] fs = cls.getDeclaredFields();
        for (Field f : fs){
            //获取字段上加的@Excel注解
            ExcelField ef = f.getAnnotation(ExcelField.class);
            if (ef != null && (ef.type()==0 || ef.type()==type)){
                //根据字段注解中配置的groups进行分组
                //....
                }else{
                //若无group属性,则直接将字段和对应的注解加入到一个全局的注解链表中,用于之后进行统一的排序
                    annotationList.add(new Object[]{ef, f});
                }
            }
        }
        // Get annotation method
        Method[] ms = cls.getDeclaredMethods();
        for (Method m : ms){
            //获取方法上的注解
            ExcelField ef = m.getAnnotation(ExcelField.class);
            if (ef != null && (ef.type()==0 || ef.type()==type)){
                  //操作同对字段的操作
                }else{
                    annotationList.add(new Object[]{ef, m});
                }
            }
        }
        // 对字段进行排序
        Collections.sort(annotationList, new Comparator<Object[]>() {
             //排序规则
        });
        // Initialize
        List<String> headerList = Lists.newArrayList();
        for (Object[] os : annotationList){
            //获取注解title属性值
            String t = ((ExcelField)os[0]).title();
            //将字段名称保存在一个list中,交给初始化方法使用
            headerList.add(t);
        }
        //初始化操作,创建Excel,设置文件名称,表格标题,表头内容及单元格的格式等信息
        initialize(title, headerList);
    }

4. 导入Excel文件

导入Excel文件,意味着需要将一个根据我们生成模板填好的Excel文件导入到系统中。在这个过程中,需要使用一个接口去接收文件,并对文件进行解析。在Excel文件中,每一行都对应着我们定义的一个实体对象,所以解析之后,我们得到的是一个存放着多个对象的List。
在解析文件的过程中,首先需要对文件格式校验,保证是一个有效的Excel文件,然后循环读取每一行的数据,并将其赋值给对象。

5. 导出Excel文件

导出Excel的原理同导出模板一样,只是需要将数据填充到Excel文件中。填充数据过程中,还是需要通过@Excel注解将JavaBean中的字段找出,并将值设置到单元格中

6. 测试

1. 定义实体类并为其中字段方法添加@Excel注解

public class User {
    private String userName;
    private String nickName;
    private Integer age;
    private Date birth;

    @NotNull(message = "User Name 不能为空")
    @ExcelField(title="User Name", align=2, sort=1)
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @ExcelField(title="Nick Name", align=2, sort=2)
    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    @ExcelField(title="Age", align=2, sort=3)
    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @JsonFormat(pattern = "mm/dd/YYYY")
    @NotNull(message="Birth Day不能为空")
    @ExcelField(title="Birth Day", align=2, sort=4)
    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }
}

2. 定义接口方法
1. 下载输入数据的模板
    @RequestMapping("import/template")
    public void importFileTemplate(HttpServletResponse response){
        try {
            //定义文件名称
            String fileName = "User_Data_import_template.xlsx";
            List<User> list = Lists.newArrayList();
            new ExportExcel("User Data", User.class, 1).setDataList(list).write(response, fileName).dispose();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
2. 导入Excel文件到系统
  @RequestMapping(value = "import",method = RequestMethod.POST)
    public void importFile(MultipartFile multipartFile){
        try {
            int successNum = 0;
            int failureNum = 0;
            StringBuilder failureMsg = new StringBuilder();
            ImportExcel ei = new ImportExcel(multipartFile, 1, 0);
            List<User> list = ei.getDataList(User.class);
            for (User user : list){
                try{
                    //to do: 保存/处理数据
                    //userService.save(user);
                    logger.info(user.toString());
                    successNum++;
                }catch(ConstraintViolationException ex){
                    failureNum++;
                }catch (Exception ex) {
                    failureNum++;
                }
            }
            if (failureNum>0){
                failureMsg.insert(0, ", Failures: "+failureNum);
            }
            logger.info("Had Operation "+successNum+" Data;"+" "+"Failure "+failureNum);
        } catch (Exception e) {
            logger.error("导入失败",e);
        }
    }
3. 导出Excel文件
 @RequestMapping("export")
    public void export(HttpServletResponse response){
        try {
            String fileName = "User Data"+ DateUtils.getDate("yyyyMMddHHmmss")+".xlsx";
            List<User> users=new ArrayList<>();
            User user1=new User();
            user1.setUserName("小明");
            user1.setNickName("猪小明");
            user1.setAge(20);
            user1.setBirth(DateUtils.parseDate("1992-10-10"));
            users.add(user1);
            User user2=new User();
            user2.setUserName("小红");
            user2.setNickName("小小红");
            user2.setAge(18);
            user2.setBirth(DateUtils.parseDate("1998-11-09"));
            users.add(user2);
            new ExportExcel("Test Over View Define", User.class,2).setDataList(users).write(response, fileName).dispose();
        } catch (Exception e) {
        }
    }
3. 演示

端口号可以自己通过在application.properties文件中,添加server.port=8000进行定义

通过浏览器访问接口http://localhost:8000/user/import/template,下载模板:**

下载模板1.png

下载模板2.png

编辑Excel文件,并通过接口测试工具Postman访问接口localhost:8000/user/import:**

上传1.png

接口测试工具中,上传文件,并访问:


上传2.png

上传之后,通过日志输出文件解析的结果:

2017-11-24 19:56:15.186  INFO 37428 --- [nio-8000-exec-5] com.shexd.Controller.UserController      : User{userName='小明', nickName='猪小明', age=18, birth=1992-10-09}
2017-11-24 19:56:15.187  INFO 37428 --- [nio-8000-exec-5] com.shexd.Controller.UserController      : User{userName='蘑菇头', nickName='小蘑菇', age=21, birth=1996-09-25}
2017-11-24 19:56:15.187  INFO 37428 --- [nio-8000-exec-5] com.shexd.Controller.UserController      : Had Operation 2 Data; Failure 0

访问接口http://localhost:8000/user/export,从系统导出Excel文件**

导出1.png
导出2.png

7. 小结

本文简单介绍了利用Java注解和反射对Excel进行操作的基本原理,并实例进行详细说明。本文中项目代码已上传至Github,别忘了Star一个吆,传送门:ExcelHandle

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容