POI之自定义注解生成文档-yellowcong
2017-07-16 00:21
148 查看
到处数据如果是成条的,而且是批量处理的情况下,我们可以通过对数据模型使用注解,来解决这种问题。
环境搭建
<!-- excel --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.17</version> </dependency> <!-- word --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-scratchpad</artifactId> <version>3.17</version> </dependency> <!-- xlsx --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.17</version> </dependency> <!-- xlsx 依赖这个包 --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml-schemas</artifactId> <version>3.17</version> </dependency>
实现思路
在数据模型对象下,添加一个注解类,说明抽出的字段在excel中所对应的表头,然后通过工具类,通过反射,获取每个字段所对应的标题,同时设定每个字段对应的数据POI自定义注解类(HFFSAlias.java)
HFFSAlias.java这个注解类是基于file字段的,用于说明字段在xls中的中文名称
package com.yellowcong.utils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 通过注解来操作别名,判断的是字段上的别名 * 通过这个注解类和我们的PoiUtils 可以很好的解决文档导出的问题 * * @author yellowcong * @date 2016年1月7日 */ @Target({ java.lang.annotation.ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface HFFSAlias{ //设定别名,通过value 这个函数,就不需要写参数了 //@HFFSAlias("xx") 就可以了 public String value() default ""; }
模型类对象(User.java)
这个模型对象,使用的是Hibernate的框架,通过Hibernate做ORM框架,我们只需要调用自定义的HFFSAlias.java这个annotation,就可以设定标题了
package com.yellowcong.model; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.UniqueConstraint; import org.hibernate.annotations.Index; import org.hibernate.validator.constraints.Email; import com.yellowcong.utils.HFFSAlias; /** * yellowcong 的新网站,以后也会一直建立下去 * * @author yellowcong * * * QQ * 新浪微博借口 * 微信接口 数据推送 * github 接口 * * * 用户登录的 email * 用户登录的手机号 * 用户的名称 * 必须是唯一的 * * * 网名和头像随机生成,如果用户不填写的情况下 * * * 手机和email登录 * 三方登录 * */ @Entity @Table(name="ycg_user") public class User { @HFFSAlias("编号") private int id; //用户名称 @HFFSAlias("用户名称") private String username; //别名 //别名不能存在邮箱@ 不然会导致问题,我们需要做js验证 @HFFSAlias("别名") private String nickname; //密码 // @HFFSAlias("密码") private String password; //创建日期 @HFFSAlias("创建日期") private Date createDate; //用户头像 @HFFSAlias("用户头像") private String imgUrl; //用户的邮箱地址 @HFFSAlias("邮箱") private String email; //电话 @HFFSAlias("电话号") private String phone; //是否邮箱激活 // 0 表示未激活 // 1 表示激活 @HFFSAlias("是否激活") private int isActive; public User() { super(); // TODO Auto-generated constructor stub } //用于验证用户是否登录 public User(int id) { super(); this.id = id; } @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } @Index(name="username") @Column(name="username",length=16) public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } //我们不需要设定我们的nickname 为唯一的 @Index(name="nickname") @Column(name="nickname",length=16,unique=false) public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } @Column(length=32) public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Column(name="create_date") public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } @Column(name="img_url") public String getImgUrl() { return imgUrl; } public void setImgUrl(String imgUrl) { this.imgUrl = imgUrl; } @Email @Column(unique=true,length=64) public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Column(name="phone",length=11,unique=true) public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Column(name="is_active",length=1) public int getIsActive() { return isActive; } public void setIsActive(int isActive) { this.isActive = isActive; } }
工具类
工具类中,主要可供使用的是将获取的List<?>数据转化为Excel文档,同时也可以 将转化的Excel文档转化为
List<?>,这几个方法都有一个特点,他们是有表头,而且还有一点缺点,对于时间类型需要指定,不然有问题,同时逆向工程中,读取的表的数据 少一行
package com.yellowcong.utils; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.apache.commons.beanutils.BeanUtils; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFCellStyle; import org.apache.poi.hssf.usermodel.HSSFFont; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.util.HSSFColor; /** * 报表工具包,用来操作报表中的数据 * @author yellowcong * @date 2016年1月6日 * 依赖 poi-2.5.1.jar * beanutil * * 重要的方法 * 创建xls文件 * * 基于annotation * 类对象需要使用@HHFSAlias 注解 给字段设定别名 * createHSSFByAnnotation(List<?>) * * 自己配置好映射关系,然后传 List<?>对象 * createHSSF(LinkedHashMap<String, String>, List<?>) * * 逆向生成 java对象 * 类对象需要使用@HHFSAlias 注解 给字段设定别名 * reverseHSSFByAnnotation(File, Class<?>) * * 对于文档直接使用,使用String[]来配置我们的 映射关系,boolena 设定是否有表头 * reverseHSSF(File, String[], Class<?>, boolean) * * 对于有表头的可以使用这个,也可以使用上一个,上一个的效率高一点 * reverseHSSF(File, Map<String, String>, Class<?>) */ public class PoiUtils { private PoiUtils(){} /** * 将HSSFWorkbook 对象转化为 文件 * @param wb HSSFWorkbook 对象 * @param file 文件 */ public static void copyHSSFToFile(HSSFWorkbook wb,File file){ FileOutputStream out = null; try { out = new FileOutputStream(file); } catch (Exception e) { // TODO: handle exception }finally{ try { if(wb != null){ wb.write(out); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ try { if(out != null){ out.close(); } } catch (Exception e2) { // TODO: handle exception } } } } /** * 获取一个不带有输入流的HSSFWorkbook 对象 * @return */ public static HSSFWorkbook getHSSFWorkbook(){ return getHSSFWorkbook(null); } /** * 获取我们的一个HSSFWorkbook * @return */ public static HSSFWorkbook getHSSFWorkbook(File file){ HSSFWorkbook workbook= null; try { if(file != null){ workbook = new HSSFWorkbook(new FileInputStream(file)); }else{ workbook = new HSSFWorkbook(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return workbook; } //判断数据 /** * 通过 结合注解,来生成我们的poi文档操作 * 通过注解调用的时候,我们的类中必须要有注解写出,如果没有就创建不了 * 需要结合@HFFSAlias 这个类 * @param data 我们的数据集合 * @return */ public static HSSFWorkbook createHSSFByAnnotation(List<?> data) { LinkedHashMap<String, String> title = null; //获取到我们的title数据 if(data != null && data.size() >0){ title = new LinkedHashMap<String, String>(); /*Field [] fields = data.get(0).getClass().getDeclaredFields(); for(Field field:fields){ HFFSAlias alias = field.getAnnotation(HFFSAlias.class); if(alias != null){ title.put(field.getName(), alias.value()); } }*/ title = PoiUtils.getAnnotationAlias(data.get(0).getClass()); //然后调用已经存在的方法来生成表 return PoiUtils.createHSSF(title, data); } return null; } /** * 逆向生成,我们需要的数据,将xls中的数据转化为字符串数据 * @param clazz 映射的类对象 * @param file xls文件对象 * @return */ public static List<?> reverseHSSFByAnnotation(File file,Class<?> clazz ){ //获取field中的信息 return PoiUtils.reverseHSSF(file, PoiUtils.getAnnotationAlias(clazz), clazz); } /** * 通过clazz 来获取到里面的title数据 * @param clazz * @return */ public static LinkedHashMap<String,String> getAnnotationAlias(Class<?> clazz){ Field [] fields = clazz.getDeclaredFields(); LinkedHashMap<String,String> title = new LinkedHashMap<String, String>(); for(Field field:fields){ HFFSAlias alias = field.getAnnotation(HFFSAlias.class); if(alias != null){ title.put(field.getName(), alias.value()); } } return title; } /** * 没有行标题的数据,需要通过这种方法来确定数据 * 这个方法使用起来 比通过Map 更加的简便 * @param file 逆向工程的xls文件爱你 * @param title 这个 需要设定 映射关系,而且顺序不能错 * @param clazz 映射的类对象 * @param hasHead 是否包含表头,有表头的数据就 需要从1 开始 * @return */ public static List<?> reverseHSSF(File file,String[] title,Class<?> clazz ,boolean hasHead){ try { //创建work HSSFWorkbook work = PoiUtils.getHSSFWorkbook(file); //获取第一页 HSSFSheet sheet = work.getSheetAt(0); //row是从0开始的,获取列和行的数目 HSSFRow row = sheet.getRow(0); List list =null; List<String> dateFields = null; int rownum = sheet.getLastRowNum(); int cellnum = row.getLastCellNum(); // System.out.println(rownum); String [] ids = title; if(rownum >0 && cellnum >0){ list = new ArrayList(); dateFields = PoiUtils.getDataTimeFiled(clazz); //遍历别的数据,、第一行数据 是需要的 int start = 0; if(hasHead){ start = 1; } for(int i=start;i<rownum;i++){ row = sheet.getRow(i); //实例化对象 Object obj = clazz.newInstance(); //设定属性 for(int j=0;j<cellnum;j++){ HSSFCell cell = row.getCell((short)j); String result =""; //当时数字类型的数据,不可以转化 if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){ result = cell.getNumericCellValue()+""; }else{ result =cell.getStringCellValue(); } //日期类型的数据不好注入,需要判断,和设定数据的类型 if(dateFields.contains(ids[j])){ BeanUtils.setProperty(obj, ids[j], new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(result)); }else{ BeanUtils.setProperty(obj, ids[j], result); } } list.add(obj); } } return list; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } /** * 通过配置的方法来生成逆向工程 * 给定文件,将对象转化为类 * 设定中,sheeet 就一页 * @param file xls文件名称 * @param title 标题,表头关系 * @param clazz 类对象 * @return */ public static List<?> reverseHSSF(File file,Map<String,String> title,Class<?> clazz ){ try { //创建work HSSFWorkbook work = PoiUtils.getHSSFWorkbook(file); //获取第一页 HSSFSheet sheet = work.getSheetAt(0); //row是从0开始的,获取列和行的数目 HSSFRow row = sheet.getRow(0); List list =null; List<String> dateFields = null; int rownum = sheet.getLastRowNum(); int cellnum = row.getLastCellNum(); // System.out.println(rownum); String [] ids = new String[cellnum]; if(rownum >0 && cellnum >0){ title = PoiUtils.reverseMap(title); list = new ArrayList(); dateFields = PoiUtils.getDataTimeFiled(clazz); //这个可以 提取为 获取第一行数据的信息 //获取地一行,标题行 获取标题对应信息 for(int j=0;j<cellnum;j++){ HSSFCell cell = row.getCell((short)j); String result = ""; //当时数字类型的数据,不可以转化 if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){ // ids[j]= title.containsValue(value); result = cell.getNumericCellValue()+""; }else{ result = cell.getStringCellValue(); } //获取的数据进行注入操作 //需要反转map集合 if(title != null && result != null){ if(!title.containsKey(result)){ throw new RuntimeException("内容不匹配"); } ids[j] = title.get(result); } } //遍历别的数据 for(int i=1;i<rownum;i++){ row = sheet.getRow(i); //实例化对象 Object obj = clazz.newInstance(); //设定属性 for(int j=0;j<cellnum;j++){ HSSFCell cell = row.getCell((short)j); String result =""; //当时数字类型的数据,不可以转化 if(cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC){ result = cell.getNumericCellValue()+""; }else{ result =cell.getStringCellValue(); } //日期类型的数据不好注入,需要判断,和设定数据的类型 if(dateFields.contains(ids[j])){ BeanUtils.setProperty(obj, ids[j], new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(result)); }else{ BeanUtils.setProperty(obj, ids[j], result); } } list.add(obj); } } return list; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } /** * 通过给定类,来获取到日期类型的字段 * 解决日期类型 注入不进去的问题 * @param clazz * @return */ public static List<String> getDataTimeFiled(Class<?> clazz){ Field [] fields = clazz.getDeclaredFields(); List<String> fieldNames = new ArrayList<String>(); for(Field field :fields){ if(field.getType().getSimpleName().equals("Date")){ fieldNames.add(field.getName()); } } return fieldNames; } /** * 创建我们的HSSFwork 通过配置别名属性 * @param tiles 文档的title Map<属性名称, 别名> * @param date List<?> 是一个集合的数据 * @return */ public static HSSFWorkbook createHSSF(LinkedHashMap<String, String> title,List<?> data) { try { //一个汉字的宽度是 500 //英文260 HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet(); //获取字符的长度 // sheet.setColumnGroupCollapsed(columnNumber, collapsed); // sheet.setColumnWidth(column, width); //写title中的数据 if(title!= null && title.size() >0){ //创建样式 HSSFCellStyle style = PoiUtils.getHeadStyle(workbook); int index = 0; HSSFRow row = sheet.createRow(0); //获取宽度 Map<String, Short> columns = PoiUtils.getColumnWidth(title, data); //获取数据 for(Map.Entry<String, String> entry:title.entrySet()){ String val = entry.getValue(); HSSFCell cell = row.createCell((short)index); //设置单元格的类型 cell.setCellType(HSSFCell.CELL_TYPE_STRING); //设置编码格式 cell.setEncoding(HSSFCell.ENCODING_UTF_16); //设定样式 cell.setCellStyle(style); cell.setCellValue(val); sheet.setColumnWidth((short)index, columns.get(entry.getKey())); index++; } //创建样式 style = PoiUtils.getBodyStyle(workbook); //设定数据 if(data != null && data.size() >0){ //获取我们的 data中的数据 //内容从第二行开始 int rownum =1; for(Object obj:data){ row = sheet.createRow(rownum); //列从第一行开始 int colnum = 0; for(Map.Entry<String, String> entry:title.entrySet()){ String key = entry.getKey(); //获取装的对象数据 String val = BeanUtils.getProperty(obj, key); if(val == null || "".equals(val.trim())){ val = "-"; } HSSFCell cell = row.createCell((short)colnum); //设置单元格的类型 cell.setCellType(HSSFCell.CELL_TYPE_STRING); //设置编码格式 cell.setEncoding(HSSFCell.ENCODING_UTF_16); //设定样式 cell.setCellStyle(style); cell.setCellValue(val); //增加列 colnum ++; } rownum ++; } } } return workbook; } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } /** * 计算每个数据列的最适合的宽度 * 通过数据来获取到我们的数据的宽度 * @param title 数据标题 * @param data 数据几何 * @return */ public static Map<String, Short> getColumnWidth(Map<String, String> title,List<?> data){ try { //存储我们的每个属性的宽度 //Map<属性明,宽度> Map<String,Short> map = new HashMap<String, Short>(); if(title!= null && title.size() >0){ //计算我们的第一行的宽度 for(Map.Entry<String, String> entry:title.entrySet()){ int max = PoiUtils.countColumnLength(entry.getValue()); //计算下面的所有 同一字段的长度 for(Object obj:data){ String val = BeanUtils.getProperty(obj, entry.getKey()); int temp = PoiUtils.countColumnLength(val); max = max >temp?max:temp; } //设定字段的值 map.put(entry.getKey(), (short)max); } } return map; } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } /** * * 根据字符的数量,来计算列的宽度 * 不同的数据他的数据长度是不一样的,所以我们需要计算总体的长度 * @param str 字符串 * @return int 计算出列宽 * */ private static int countColumnLength(String str){ int len =0; if(str != null && !"".equals(str.trim())){ //一个汉字的宽度是 500 //英文260 char [] chars = str.toCharArray(); //z int chines = 0; int other =0; for(char val:chars){ if(PoiUtils.isChineseChar(val)){ chines ++; }else{ other++; } } //获取到了数据然后计算长度 len = chines*500+other*260+1000; } return len; } /** * 创建样式 * @return */ public static HSSFCellStyle getHeadStyle(HSSFWorkbook workbook){ HSSFCellStyle style = workbook.createCellStyle(); //居中显示 style.setAlignment(HSSFCellStyle.ALIGN_CENTER); style.setFont(PoiUtils.getSongRed10(workbook)); return style; } /** * 创建样式 * @return */ public static HSSFCellStyle getBodyStyle(HSSFWorkbook workbook){ HSSFCellStyle style = workbook.createCellStyle(); style.setAlignment(HSSFCellStyle.ALIGN_CENTER); style.setFont(PoiUtils.getSong10(workbook)); return style; } /** * 创建字体 * @param fontFamly 字体 * @param size 大小 字号 14 13 * @param color HSSFColor.BLACK.index 这个HSSFColor中 的数据 * @param isBold 是否加粗 操作 * @return */ //字号 是14号 -->对应的short 280宽度 是 14好字体 public static HSSFFont creatFont(HSSFWorkbook workbook,String fontFamly,Integer size,Short color,boolean isBold){ HSSFFont font = workbook.createFont(); //字号 是14号 -->对应的short 宽度 是 14好字体 font.setFontHeight((short)((size== null || size ==0 )?280:size*20)); font.setBoldweight(isBold?HSSFFont.BOLDWEIGHT_BOLD:HSSFFont.BOLDWEIGHT_NORMAL); //将字体颜色变红色 // short colors = HSSFColor.RED.index; font.setColor(color== null?HSSFColor.BLACK.index:color); return font; } /** * 判断我们的数据 中,是否有中文字符 * @param str * @return */ public static boolean isChineseChar(char str){ boolean flag = false; Pattern pattern = Pattern.compile("[\u4e00-\u9fa5]"); if(pattern.matcher(str+"").find()){ flag = true; } return flag; } /** * 得到宋体10 号 黑色 * 默认字体的大小就是10 号 * @return */ public static HSSFFont getSong10(HSSFWorkbook workbook){ return creatFont(workbook,"宋体", 10, null,false); } /** * 得到宋体10 号 红色加粗 * @return */ public static HSSFFont getSongRed10(HSSFWorkbook workbook){ return creatFont(workbook,"宋体", 10, HSSFColor.RED.index,true); } /** * 将Map * @param map * @return */ public static Map<String,String> reverseMap(Map<String,String> map){ Map<String,String> newMap = null; //将便利的结果重新装填一下 if(map != null && map.size() >0){ newMap = new LinkedHashMap<String, String>(); for(Map.Entry<String, String> entry:map.entrySet()){ newMap.put(entry.getValue(), entry.getKey()); } } return newMap; } }
测试类
主要测试了,自己手动编写表头和自动配置生成表头两种方法,同时还测试了Excel转List数据package com.yellowcong.test; import java.io.File; import java.lang.reflect.Field; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Resource; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.yellowcong.model.Pager; import com.yellowcong.model.SystemContext; import com.yellowcong.model.User; import com.yellowcong.service.UserService; import com.yellowcong.utils.HFFSAlias; import com.yellowcong.utils.PoiUtils; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:ApplicationContext.xml") public class UserServiceTest { private UserService userService; @Resource(name="userService") public void setUserService(UserService userService) { this.userService = userService; } @Test public void testList(){ List<User> users= userService.listByPager().getData(); //可以做成annotation的 Map<String,String> map = new HashMap<String, String>(); map.put("id", "编号"); map.put("username", "用户名"); map.put("nickname", "别名"); map.put("password", "密码"); map.put("createDate", "创建日期"); map.put("imgUrl", "头像路径"); map.put("email", "邮箱"); map.put("phone", "电话"); map.put("isActive", "是否激活"); HSSFWorkbook work = PoiUtils.createHSSF(map, users); PoiUtils.copyHSSFToFile(work, new File("D:/users.xls")); } @Test public void testList2Anotation(){ List<User> users= userService.listByPager().getData(); HSSFWorkbook work = PoiUtils.createHSSFByAnnotation(users); PoiUtils.copyHSSFToFile(work, new File("D:/users.xls")); } }
测试结果
相关文章推荐
- Spring Boot如何让Web API自动生成文档,并解决swagger-annotations的API注解description属性废弃的问题
- 将项目打包成jar与生成对应的api帮助文档(也就是注解)
- 将Android项目打包成jar与生成对应的api帮助文档(也就是注解)
- ndoc2007,生成注释文档,支持泛型,2.0,中文注解,部分汉化
- 将C#文档注释生成.chm帮助文档
- Wisdom RESTClient支持自动化测试并可以生成API文档
- 使用apidoc 生成Restful web Api文档
- 根据word文档格式使用JAVA生成WORD原来这么好用
- 如何让java生成复杂的Word文档
- cxf生成的webService,注解/注释自动生成的webService
- 使用appledoc生成说明文档
- PowerDesigner将PDM导出生成WORD文档
- 使用ASP.NET生成Word文档并直接下载
- 使用DocFX生成文档
- System.InvalidOperationException: 生成 XML 文档时出错 这样的错误 序列化类型 xxx 的对象时检测到循环引用
- Eclipse中 java 注释文档 的生成方法
- 数据库文档生成工具——word2chm,SqlSpec
- AS生成工程文档需要注意编码格式
- word模板生成word文档并下载
- SpringBoot + mybatis + Swagger快速构建REST API并生成优美的API文档