POI解析Excel之应用反射等技术实现动态读取
Posted wind-june
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了POI解析Excel之应用反射等技术实现动态读取相关的知识,希望对你有一定的参考价值。
目录树
- 背景
- 技术选型
- 问题分析
- 技术要点及难点分析
- 源码分析
- 测试用例
背景
Tip:因为产品提的需求我都开发完了,进行了项目提测;前天老大走过来说:你用spring-boot开发一个解析Excel的jar包.....详细对话如下:
A:世生,你用spring-boot开发一个解析Excel的jar包。
B:为什么不在原来的项目上进行开发呢?(很纳闷,这个东西不是一般用于前端上传数据的嘛,这是后端层,咋搞这个)
A:因为xxxx这个需求有比较多的数据需要初始化,这个jar是作为一个上线的数据初始化脚本
B:嗯,好的
技术选型
毕竟是第一次写解析Excel代码,问了度娘说是有两种方式可以做到。一是利用wso2的jxl解析,二是利用apache的poi解析;我去maven repository官网搜索了这两个jar包,对比了下jml的最新版本是2.6.12竟然是2011.05月更新的,而poi的最新版本是4.0.x是2018.08更新的;个人觉得jml最新版本都是2011.05更新的,相对于apache的poi包来说更不靠谱;不断持续更新的开源项目或者开源jar包不管是bug还是兼容性相对来说是越来越好。所以最终选定用apache大佬的poi进行开发。
问题分析
解析Excel的关键点是在于从Excel表格中读取数据到内存(解析Excel),然后可能是校验数据、通过业务逻辑分析数据、最终持久化到数据库中;其实这其中最重要的不是解析Excel,而是将解析出的数据持久化到数据库中以备有用之需。而解析Excel的这块功能只能算是一个Util类,不能与业务代码耦合一起;然后我看到很多的Excel解析相关的代码都是在解析数据中混淆业务代码逻辑,其实这些都是不可取的,这会导致解析Excel逻辑与业务逻辑相耦合也就是冗杂、代码重用率低、可扩展性低等问题。因为之前在做项目的时候遇到过一个问题:我负责的模块是一个中间模块(通讯采用Dubbo),其他系统要依赖我这个接口进行请求的转发可我调用其他系统返回的结果对象却各个都不同,我叫其他系统负责人说要统一调用接口返回的对象,但是其他系统的负责人都不是同一个人执行起来效率太低了,在历经一个星期都无果的情况下我只能撒下自己的杀手锏了;在这种极端条件下最终我不管其他数据的接口返回的对象是什么,我直接用Object接收返回类型,通过反射获取决定请求成功与否的属性值(欣慰的是当时必传属性值倒是一样的)。通过这种方法我可以少些很多的代码(当时其他系统有15+),不然的话每调用不同系统的接口我都需要进行逻辑判断或者是干脆对于调用他们不同的系统我采用不同接口进行转发,但选用这种方法却便利多了。
以上问题分析及一个场景的描述很好理解,但是通过Object接收返回信息得这个场景事实上却有所欠佳;返回对象不同的这个问题最好的处理方案就是统一接口,我那个方案是在需求推动但别人无法及时配合的极端条件下使用的,是没办法中的办法,但这也是一个没有办法中的一个最优的处理方案,兼容性比较强。以下我就用图来分析这两种情况的比较:
1.非动态模式:将Excel数据加载到内存与业务代码逻辑混合,数据在解析期间交叉传递。弊端:每新增一个需要解析的Excel,解析Excel代码块就需要重新开发,代码复用率底下、可扩展性也低。
2.动态模式:将Excel数据加载到内存与业务代码逻辑分开;Excel数据加载到内存之后才将数据传递给业务代码逻辑处理,解析Excel与业务代码之间分开;优点:将解析Excel的这部分代码封装为一个ExcelUtil,代码复用率明显提高,而且解析与业务代码间实行解耦,可扩展性增强。
技术要点及难点分析
要实现动态解析,实现解析与业务代码逻辑相解耦;那么我们不难会想起一个Java的一个关键技术-Reflection(反射原理),Python、Ruby等是动态语言而理论上Java是一门静态语言,但是Java引入了Reflection技术实现了动态性。反射原理我们都比较熟悉,就是在运行期间动态获取类的所有属性及其方法,可以对这些数据进行相关的操作。以上动态解析Excel的实现就需要用到Java这项的高级技术了,通过这项技术可以实现动态解析、解析与业务逻辑解耦等。为了实现动态解析的目的我应用了Java反射技术,但是在开发的过程我发现反射执行一行数据结束的时候如何保存呢?换句话说就是:解析的时候一行的Excel数据封装后就是一个bean,一个Excel表格就是多个bean 即“beans”;如果我们直接将反射执行后的结果保存至List中,当解析整个Excel结束后我们会发现,整个List里面的对象的值完全一样的?what?这是什么原因导致的呢?这就是类似于:Object obj=new Object(),我们每次解析都只是把 obj 放在List中,List中的每一个对象都是同一个 obj(引用不变,实例也不变),所以自然也就相同了;因为当一个类执行反射的时候其实它的运行时状态只有一个,也就是类似于只有一个实例,而传统的解析Excel是解析出一条数据就new一个对象进行封装数据,然后将bean存放至List。然而有什么方法能够解决这一类问题呢?那就是Object 的native clone()方法了,clone()这个大佬是比较牛逼的一个人物,在不创建对象的情况下将属性值复制给另一个对象,具体实现需要实现Cloneable接口并重写clone()。而解决这个问题的方式就是在每解析完一行Excel数据的时候,反射调用该对象的clone方法。动态解析具体实现应用了Apache POI、 LRUCache(LRU缓存)、Reflection(反射)、java的Clone等技术。如果以上技术没有了解过的朋友可以去自行了解,这里不加赘述。
前提条件:因为要实现动态解析,动态设置值,那么我们在反射执行set操作的时候就需要知道相应的setMethod(),那么我们可以在Excel规定第一行就是属性字段,并且字段名称跟bean的名称一样,读取的时候先把第一行的数据放在一个String []数组中。具体实现请参照以下源码。我已经把相关代码打包成Jar,需要的朋友可以自行下载;Jar包下载链接:https://pan.baidu.com/s/1fKCCh54S3ZtHfv66T2pk2w 密码:nur8
使用方法:新建bean用于存储Excel数据信息,每个属性需要有get、set操作,属性与Excel首行相同,最重要的一点是要实现Clonesble接口重写clone方法。Excel使用Office编辑,亲测Wps编辑的Excel某些属性值有问题。在new ReadExcelUtil 的时候只需要将对象类型与Excel文件路径传入构造函数即可,然后调用 ReadExcelUtil的getObjectList即可得到解析后的所有对象。至于这个对象你可以用任何的对象,你可以换成Teacher、OrderInfo、UserInfo......但是前面提到的:Excel第一行的属性字段需要与bean的属性字段一致,否则无法调用目标方法,具体可参见ReflectionInitValue的方法。具体实现请参见:文章末尾的Test类测试。
源码分析
- 前提条件:引入Apache POI 的Maven仓库坐标,我这里使用的是3.25版本的。
1 <!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
2 <dependency> 3 <groupId>org.apache.poi</groupId> 4 <artifactId>poi</artifactId> 5 <version>3.15</version> 6 </dependency> 7 <dependency> 8 <groupId>org.apache.poi</groupId> 9 <artifactId>poi-ooxml</artifactId> 10 <version>3.15</version> 11 </dependency>
- 主要类:Common.java、LRUCache.java、LRUCacheException.java、ResolveFileException.java、ReadExcelUtil.java、ReflectionInitValue.java、Student、Test
- Common.java:基础常量池,主要用于反射执行Method方法时判断Method的参数类型的常量。
1 package com.hdbs.common; 2 3 /** 4 * @author :cnblogs-WindsJune 5 * @version :1.1.0 6 * @date :2018年9月20日 下午6:33:54 7 * @comments :解析Excel公共类常量类 8 */ 9 10 public class Common { 11 12 public static final String OFFICE_EXCEL_2003_POSTFIX_xls = "xls"; 13 public static final String OFFICE_EXCEL_2010_POSTFIX_xlsx = "xlsx"; 14 public static final String DATA_TYPE_long ="long"; 15 public static final String DATA_TYPE_boolean ="boolean"; 16 public static final String DATA_TYPE_int ="int"; 17 public static final String DATA_TYPE_float ="float"; 18 public static final String DATA_TYPE_double ="double"; 19 public static final String DATA_TYPE_Long ="class java.lang.Long"; 20 public static final String DATA_TYPE_Integer ="class java.lang.Integer"; 21 22 23 }
- LRUCacheException.java;LRU缓存自定义异常类。
1 package com.hdbs.exceptions; 2 3 /** 4 * Creater: cnblogs-WindsJune 5 * Date: 2018/9/21 6 * Time: 10:04 7 * Description: No Description 8 */ 9 public class LRUCacheException extends Exception{ 10 /** 11 * 错误码 12 */ 13 private String errorCode; 14 15 /** 16 * 错误描述 17 */ 18 private String errorMessage; 19 20 public LRUCacheException(String errorCode, String errorMessage) { 21 this.errorCode = errorCode; 22 this.errorMessage = errorMessage; 23 } 24 25 public LRUCacheException(String message) { 26 super(message); 27 this.errorMessage = errorMessage; 28 } 29 30 public String getErrorCode() { 31 return errorCode; 32 } 33 34 public void setErrorCode(String errorCode) { 35 this.errorCode = errorCode; 36 } 37 38 public String getErrorMessage() { 39 return errorMessage; 40 } 41 42 public void setErrorMessage(String errorMessage) { 43 this.errorMessage = errorMessage; 44 } 45 }
- ResolveFileException.java;解析Excel自定义异常类。
1 package com.hdbs.exceptions; 2 /** 3 * Creater: cnblogs-WindsJune 4 * Date: 2018/9/20 5 * Time: 19:44 6 * Description: 解析Excel的公共异常类 7 */ 8 9 public class ResolveFileException extends RuntimeException{ 10 11 /** 12 * 错误码 13 */ 14 private String errorCode; 15 16 /** 17 * 错误描述 18 */ 19 private String errorMessage; 20 21 public ResolveFileException(String errorCode, String errorMessage) { 22 this.errorCode = errorCode; 23 this.errorMessage = errorMessage; 24 } 25 26 public ResolveFileException(String message) { 27 super(message); 28 this.errorMessage = errorMessage; 29 } 30 31 public String getErrorCode() { 32 return errorCode; 33 } 34 35 public void setErrorCode(String errorCode) { 36 this.errorCode = errorCode; 37 } 38 39 public String getErrorMessage() { 40 return errorMessage; 41 } 42 43 public void setErrorMessage(String errorMessage) { 44 this.errorMessage = errorMessage; 45 } 46 }
- LRUCache.java:LRU缓存池,主要用于不同线程反射获取的Methods,减少相同线程反射执行次数,减轻应用的负载、提高执行效率。我这里是基于LinkedHashMap实现的LRU缓存,你也可以用数组或者其他方式实现该算法。以下代码逻辑如果不能理解的可以先去了解LinkedHashSet的源码。
1 package com.hdbs.common; 2 3 import com.hdbs.exceptions.LRUCacheException; 4 import org.slf4j.Logger; 5 import org.slf4j.LoggerFactory; 6 7 import java.lang.reflect.Method; 8 import java.util.LinkedHashMap; 9 import java.util.Map; 10 11 /** 12 * Creater: cnblogs-WindsJune 13 * Date: 2018/9/20 14 * Time: 19:44 15 * Description: LinkedHashMap实现LRU缓存不同线程反射获取的Method方法 16 */ 17 public class LRUCache { 18 private static final Logger LOGGER=LoggerFactory.getLogger(LRUCache.class); 19 //缓存容量 20 private static final int cacheSize = 10; 21 22 private static final Map<Integer,Method[]> cacheMap = new LinkedHashMap<Integer, Method[]>((int) Math.ceil(cacheSize / 0.75f) + 1, 0.75f, true){ 23 @Override 24 protected boolean removeEldestEntry(Map.Entry<Integer,Method[]> eldest){ 25 26 return size()> cacheSize; 27 28 } 29 }; 30 31 /** 32 * 设置缓存 33 * @param key 34 * @param methods 35 * @return boolean 36 */ 37 public static boolean set (Integer key,Method [] methods) throws LRUCacheException { 38 try { 39 cacheMap.put(key,methods); 40 return true; 41 } 42 catch ( Exception e ){ 43 throw new LRUCacheException("Set LRU缓存异常!"); 44 } 45 } 46 47 /** 48 * 获取缓存的Method 49 * @param key 50 * @return Method 51 */ 52 public static Method[] get(Integer key) throws LRUCacheException { 53 Method[] methods=null; 54 try { 55 methods=cacheMap.get(key); 56 }catch ( Exception e ){ 57 throw new LRUCacheException("Get LRU缓存异常!{}"); 58 } 59 return methods; 60 } 61 }
- ReadExcelUtil.java;解析Excel数据工具类(将Excel加载到内存)
1 package com.hdbs.resolver; 2 3 import com.hdbs.common.Common; 4 import com.hdbs.exceptions.ResolveFileException; 5 import org.apache.commons.lang3.StringUtils; 6 import org.apache.poi.hssf.usermodel.HSSFCell; 7 import org.apache.poi.hssf.usermodel.HSSFRow; 8 import org.apache.poi.hssf.usermodel.HSSFSheet; 9 import org.apache.poi.hssf.usermodel.HSSFWorkbook; 10 import org.apache.poi.xssf.usermodel.XSSFCell; 11 import org.apache.poi.xssf.usermodel.XSSFRow; 12 import org.apache.poi.xssf.usermodel.XSSFSheet; 13 import org.apache.poi.xssf.usermodel.XSSFWorkbook; 14 import org.slf4j.Logger; 15 import org.slf4j.LoggerFactory; 16 17 import java.io.File; 18 import java.io.FileInputStream; 19 import java.io.IOException; 20 import java.io.InputStream; 21 import java.lang.reflect.InvocationTargetException; 22 import java.util.ArrayList; 23 import java.util.HashMap; 24 import java.util.List; 25 import java.util.Map; 26 27 /** 28 * @author :cnblogs-WindsJune 29 * @version :1.1.0 30 * @date :2018年9月20日 下午6:13:43 31 * @comments : 32 */ 33 34 public class ReadExcelUtil { 35 36 private static final Logger LOGGER = LoggerFactory.getLogger(ReadExcelUtil.class); 37 //存放属性集 38 private Map<Integer,String []> fieldsMap=new HashMap<>(); 39 //存放解析后的对象List 40 private List<Object> objectsList = new ArrayList<>(); 41 //反射运行时对象 42 private Object object=null; 43 //Excel文件路径 44 private String path =null; 45 //获取解析后的对象集 46 public List<Object> getObjectsList() { 47 return this.objectsList; 48 } 49 50 public ReadExcelUtil(Object object,String path) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException { 51 this.object=object; 52 this.path=path; 53 readExcel(); 54 } 55 56 /** 57 * 添加Object到List中 58 * @param object 59 * @return 60 */ 61 public boolean addListObject(Object object){ 62 boolean isSucceed=this.objectsList.add(object); 63 return isSucceed; 64 } 65 66 /** 67 * 读取excel,判断是xls结尾(2010之前);还是xlsx结尾(2010以后)的Excel 68 * 69 * @return 70 * @throws IOException 71 */ 72 public boolean readExcel() throws IOException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { 73 if (StringUtils.isEmpty(path)) { 74 return false; 75 } else { 76 // 截取后缀名,判断是xls还是xlsx 77 String postfix = path.substring(path.lastIndexOf(".") + 1); 78 if (!StringUtils.isEmpty(postfix)) { 79 if (Common.OFFICE_EXCEL_2003_POSTFIX_xls.equals(postfix)) { 80 return readXls(); 81 } else if (Common.OFFICE_EXCEL_2010_POSTFIX_xlsx.equals(postfix)) { 82 return readXlsx(); 83 } 84 } else { 85 LOGGER.error("文件后缀名有误!"); 86 throw new ResolveFileException("文件后缀名有误!" + "[" + path + "]"); 87 } 88 } 89 return false; 90 } 91 92 /** 93 * 读取xls(2010)之后的Excel 94 * 95 * @return 96 * @throws IOException 97 */ 98 public boolean readXlsx() throws IOException{ 99 File file = new File(path); 100 InputStream is = new FileInputStream(file); 101 XSSFWorkbook xssfWorkbook = new XSSFWorkbook(is); 102 // 遍历sheet页 103 for (int numSheet = 0; numSheet < xssfWorkbook.getNumberOfSheets(); numSheet++) { 104 XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(numSheet); 105 String [] fields=null; 106 if (xssfSheet == null) { 107 continue; 108 } 109 // 循环行 110 for (int rowNum = 0; rowNum <= xssfSheet.getLastRowNum(); rowNum++) { 111 XSSFRow xssfRow = xssfSheet.getRow(rowNum); 112 int cloumns=xssfRow.getLastCellNum(); 113 int i=0; 114 //获取第一行的所有属性 115 if (rowNum == 0){ 116 fields=getFields(xssfRow,cloumns); 117 fieldsMap.put(numSheet,fields); 118 continue; 119 } 120 //遍历数据,反射set值 121 while (i<cloumns){ 122 XSSFCell field=xssfRow.getCell(i); 123 String value=getValue(field); 124 try { 125 ReflectionInitValue.setValue(object,fields[i],value); 126 }catch ( Exception e ){ 127 throw new ResolveFileException(e.getMessage()); 128 } 129 i++; 130 } 131 //通过反射执行clone复制对象 132 Object result=ReflectionInitValue.invokeClone(object,"clone"); 133 this.addListObject(result); 134 // System.out.println(object.toString()); 135 } 136 } 137 return true; 138 } 139 140 /** 141 * 读取xls(2010)之前的Excel 142 * 143 * @return 144 * @throws IOException 145 */ 146 public boolean readXls() throws IOException, ResolveFileException { 147 InputStream is = new FileInputStream(path); 148 HSSFWorkbook hssfWorkbook = new HSSFWorkbook(is); 149 // 遍历sheet页 150 for (int numSheet = 0; numSheet < hssfWorkbook.getNumberOfSheets(); numSheet++) { 151 HSSFSheet hssfSheet = hssfWorkbook.getSheetAt(numSheet); 152 String[] fields = null; 153 if (hssfSheet == null) { 154 continue; 155 } 156 // 循环行Row 157 for (int rowNum = 0; rowNum <= hssfSheet.getLastRowNum(); rowNum++) { 158 HSSFRow hssfRow = hssfSheet.getRow(rowNum); 159 int cloumns=hssfRow.getLastCellNum(); 160 int i=0; 161 //获取第一行的所有属性 162 if (rowNum == 0){ 163 //获取属性字段 164 fields=getFields(hssfRow,cloumns); 165 fieldsMap.put(numSheet,fields); 166 continue; 167 } 168 //遍历数据,反射set值 169 while (i<cloumns){ 170 HSSFCell field=hssfRow.getCell(i); 171 String value=getValue(field); 172 try { 173 ReflectionInitValue.setValue(object,fields[i],value); 174 }catch ( Exception e ){ 175 throw new ResolveFileException(e.getMessage()); 176 } 177 i++; 178 } 179 //通过反射执行clone复制对象 180 Object result=ReflectionInitValue.invokeClone(object,"clone"); 181 this.addListObject(result); 182 } 183 } 184 return true; 185 } 186 187 /** 188 * xlsx -根据数据类型,获取单元格的值 189 * @param xssfRow 190 * @return 191 */ 192 @SuppressWarnings({ "static-access" }) 193 private static String getValue(XSSFCell xssfRow) { 194 String value=null; 195 try { 196 if (xssfRow.getCellType() == xssfRow.CELL_TYPE_BOOLEAN) { 197 // 返回布尔类型的值 198 value=String.valueOf(xssfRow.getBooleanCellValue()).replace(" ",""); 199 } else if (xssfRow.getCellType() == xssfRow.CELL_TYPE_NUMERIC) { 200 // 返回数值类型的值 201 value= String.valueOf(xssfRow.getNumericCellValue()).replace(" ",""); 202 } else { 203 // 返回字符串类型的值 204 value= String.valueOf(xssfRow.getStringCellValue()).replace(" ",""); 205 } 206 } catch (Exception e) { 207 //单元格为空,不处理 208 value=null; 209 LOGGER.error("单元格为空!"); 210 } 211 return value; 212 } 213 214 /** 215 * xls-根据数据类型,获取单元格的值 216 * @param hssfCell 217 * @return 218 */ 219 @SuppressWarnings({ "static-access" }) 220 private static String getValue(HSSFCell hssfCell) { 221 String value=null; 222 try { 223 if (hssfCell.getCellType() == hssfCell.CELL_TYPE_BOOLEAN) { 224 // 返回布尔类型的值 225 value=String.valueOf(hssfCell.getBooleanCellValue()).replaceAll(" ",""); 226 } else if (hssfCell.getCellType() == hssfCell.CELL_TYPE_NUMERIC) { 227 // 返回数值类型的值 228 value=String.valueOf(hssfCell.getNumericCellValue()).replaceAll(" ",""); 229 } else { 230 // 返回字符串类型的值 231 value=String.valueOf(hssfCell.getStringCellValue()).replaceAll(" ",""); 232 } 233 } catch (Exception e) { 234 //单元格为空,不处理 235 value=null; 236 LOGGER.error("单元格为空!"); 237 } 238 return value; 239 } 240 241 /** 242 * xls Excel文件类型获取属性(2010之前) 243 * @param cloumns 244 * @return String[] 245 */ 246 private static String[] getFields (HSSFRow hssfRow,int cloumns){ 247 String [] fields=new String[cloumns]; 248 int i=0; 249 try { 250 while (i<cloumns){ 251 HSSFCell field=hssfRow.getCell(i); 252 String value=getValue(field); 253 fields[i]=value.trim(); 254 i++; 255 } 256 }catch ( Exception e){ 257 throw new ResolveFileException("获取属性集失败!"); 258 } 259 return fields; 260 } 261 262 /** 263 * xlsx Excel文件类型获取属性(2010之后) 264 * @param cloumns 265 * @return String[] 266 */ 267 private static String[] getFields(XSSFRow xssfRow,int cloumns){ 268 String [] fields=new String[cloumns]; 269 int i=0; 270 try { 271 while (i<cloumns){ 272 XSSFCell field=xssfRow.getCell(i); 273 String value=getValue(field); 274 fields[i]=value.trim(); 275 i++; 276 } 277 }catch ( Exception e ){ 278 throw new ResolveFileException("获取属性集失败!"); 279 } 280 return fields; 281 } 282 283 }
- ReflectionInitValue.java;
1 package com.hdbs.resolver; 2 3 import com.hdbs.common.Common; 4 import com.hdbs.common.LRUCache; 5 import com.hdbs.exceptions.LRUCacheException; 6 import com.hdbs.exceptions.ResolveFileException; 7 8 import java.lang.reflect.InvocationTargetException; 9 import java.lang.reflect.Method; 10 import java.lang.reflect.Type; 11 12 /** 13 * Creater: cnblogs-WindsJune 14 * Date: 2018/9/21 15 * Time: 9:54 16 * Description: No Description 17 */ 18 public class ReflectionInitValue { 19 20 private static int threadHashCodeKey=Thread.currentThread().toString().hashCode(); 21 22 /** 23 * 通过反射动态将Excel读取的信息设置到对应的bean中 24 * 25 * @param object-存储对象bean 26 * @param key-属性参数名 27 * @param value-属性值 28 * @throws Exception 29 */ 30 public static void setValue(Object object, String key, String value) throws LRUCacheException { 31 String methodName = null; 32 String paramType = null; 33 Method[] methods = null; 34 if (LRUCache.get(threadHashCodeKey) == null) { 35 Class<?> clazz = object.getClass(); 36 methods = clazz.getDeclaredMethods(); 37 LRUCache.set(threadHashCodeKey, methods); 38 } else { 39 methods = LRUCache.get(threadHashCodeKey); 40 } 41 for (Method method : methods) { 42 methodName = method.getName(); 43 if (methodName.startsWith("set") && methodName.toLowerCase().equals("set" + key.toLowerCase())) { 44 Type[] types = method.getGenericParameterTypes(); 45 for (Type type : types) { 46 paramType = type.toString(); 47 // 根据参数类型转化value,并进行set操作 48 excuteInvokeSetvalue(object, method, paramType, value, 0); 49 } 50 // 该属性已经执行setValue操作,无需循环 51 break; 52 } 53 } 54 } 55 56 /** 57 * 初始化对象bean 58 * 59 * @param object 60 * @throws Exception 61 */ 62 public static void initBeans(Object object) throws ResolveFileException, LRUCacheException { 63 // Class<?> clazz = object.getClass(); 64 String methodName = null; 65 String paramType = null; 66 Method[] methods = LRUCache.get(threadHashCodeKey); 67 try { 68 for (Method method : methods) { 69 methodName = method.getName(); 70 if (methodName.startsWith("set")) { 71 Type[] types = method.getGenericParameterTypes(); 72 for (Type type : types) { 73 paramType = type.getClass().getName(); 74 } 75 // 根据参数类型转化value,并进行set初始化属性值 76 excuteInvokeSetvalue(object, method, paramType, "", 1); 77 } 78 } 79 } catch (Exception e) { 80 throw new ResolveFileException("初始化bean错误!Method:[ " + methodName + " ]"); 81 } 82 } 83 84 /** 85 * 根据参数类型转化value,并进行set操作 86 * 87 * @param object-存储对象bean 88 * @param method-执行的set对应属性的方法 89 * @param paramType-属性参数类型 90 * @param value-属性值 91 * @param operationType-操作类型(0-设置属性,1-初始化bean) 92 * @throws Exception 93 */ 94 public static void excuteInvokeSetvalue(Object object, Method method, String paramType, String value, 95 int operationType){ 96 try { 97 switch (paramType) { 98 case Common.DATA_TYPE_long: {// 参数属性long 99 if (value !=null && value.contains(".")){ 100 value=value.substring(0,value.lastIndexOf(".")); 101 } 102 Long temp = Long.valueOf(operationType == 0 && value !=null ? value : "0"); 103 method.invoke(object, temp); 104 break; 105 } 106 case Common.DATA_TYPE_boolean: {// 参数属性boolean 107 boolean temp = (operationType == 0 ? (Boolean.valueOf(value != null ? value:"false")) : false); 108 method.invoke(object, temp); 109 break; 110 } 111 case Common.DATA_TYPE_int: {// 参数属性int 112 if (value !=null && value.contains(".")){ 113 value=value.substring(0,value.lastIndexOf(".")); 114 } 115 int temp = Integer.valueOf(operationType == 0 && value!=null ? value : "0"); 116 method.invoke(object, temp); 117 break; 118 } 119 case Common.DATA_TYPE_float: {// 参数属性float 120 if (value !=null && value.contains(".")){ 121 value=value.substring(0,value.lastIndexOf(".")); 122 } 123 float temp = Float.valueOf(operationType == 0 && value !=null ? value : "0"); 124 method.invoke(object, temp); 125 break; 126 } 127 case Common.DATA_TYPE_double: {// 参数属性double 128 double temp = Double.valueOf(operationType == 0 && value !=null ? value : "0"); 129 method.invoke(object, temp); 130 break; 131 } 132 case Common.DATA_TYPE_Long: {// 参数属性Long 133 if (value !=null && value.contains(".")){ 134 value=value.substring(0,value.lastIndexOf(".")); 135 } 136 Long temp = Long.valueOf(operationType == 0 && value!=null ? value : "0"); 137 method.invoke(object, temp); 138 break; 139 } 140 case Common.DATA_TYPE_Integer: {// 参数属性Integer 141 if (value !=null && value.contains(".")){ 142 value=value.substring(0,value.lastIndexOf(".")); 143 } 144 int temp = Integer.valueOf(operationType == 0 && value!=null ? value : "0"); 145 method.invoke(object, temp); 146 break; 147 } 148 default: {// 参数属性String 149 if (value !=null && value.contains(".")){ 150 value=value.substring(0,value.lastIndexOf(".")); 151 } 152 method.invoke(object, operationType == 0 ? value : null); 153 break; 154 } 155 } 156 157 } catch ( IllegalAccessException e ) { 158 throw new ResolveFileException("invoke方法错误![Method:" + method.getName() + " [value:" + value + " ]"); 159 } catch ( InvocationTargetException e ) { 160 throw new ResolveFileException("invoke方法错误![Method:" + method.getName() + " [value:" + value + " ]"); 161 } catch (Exception e) { 162 throw new ResolveFileException("字段属性错误![Method:" + method.getName() + " [value:" + value + " ]"); 163 } 164 165 166 } 167 168 /** 169 * 170 * @param object 171 * @param methodName 172 * @return 173 * @throws ResolveFileException 174 */ 175 public static Object invokeClone (Object object,String methodName){ 176 Class clazz=object.getClass(); 177 try { 178 Method method=clazz.getMethod(methodName); 179 Object result=method.invoke(object); 180 return result; 181 }catch ( Exception e ){ 182 throw new ResolveFileException("解析Excel,反射执行set操作异常!"); 183 } 184 185 } 186 187 188 }
- Student.java;用于存储数据信息得bean。
1 package com.hdbs.beans; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * @author :WindsJune/博客园:WindsJune 8 * @version :1.1.0 9 * @date :2018年9月20日 下午6:05:57 10 * @comments : 11 */ 12 13 public class Student implements Cloneable{ 14 15 /** 16 * id 17 */ 18 private Integer id; 19 /** 20 * 学号 21 */ 22 private String no; 23 /** 24 * 姓名 25 */ 26 private String name; 27 /** 28 * 学院 29 */ 30 private String age; 31 /** 32 * 成绩 33 */ 34 private float score; 35 36 /** 37 * 地址 38 */ 39 private String adress; 40 41 private List<Student> studentsList=new ArrayList<>(); 42 43 public Integer getId() { 44 return id; 45 } 46 47 public void setId(Integer id) { 48 this.id = id; 49 } 50 51 public String getNo() { 52 return no; 53 } 54 55 public void setNo(String no) { 56 this.no = no; 57 } 58 59 public String getName() { 60 return name; 61 } 62 63 public void setName(String name) { 64 this.name = name; 65 } 66 67 public String getAge() { 68 return age; 69 } 70 71 public void setAge(String age) { 72 this.age = age; 73 } 74 75 public float getScore() { 76 return score; 77 } 78 79 public void setScore(float score) { 80 this.score = score; 81 } 82 83 public String getAdress() { 84 return adress; 85 } 86 87 public void setAdress(String adress) { 88 this.adress = adress; 89 } 90 91 public List<Student> getStudentsList() { 92 return studentsList; 93 } 94 95 public Object clone() throws CloneNotSupportedException{ 96 return super.clone(); 97 } 98 99 @Override 100 public String toString() { 101 return "Student{" + 102 "id=" + id + 103 ", no=‘" + no + ‘‘‘ + 104 ", name=‘" + name + ‘‘‘ + 105 ", age=‘" + age + ‘‘‘ + 106 ", score=" + score + 107 ", adress=‘" + adress + ‘‘‘ + 108 ‘}‘; 109 } 110 }
- Test.java;测试方法,在new ReadExcelUtil 的时候只需要将对象类型与Excel文件路径传入构造函数即可,然后调用 ReadExcelUtil的getObjectList即可得到解析后的所有对象。至于这个对象你可以用任何的对象,你可以换成Teacher、OrderInfo、UserInfo......但是前面提到的:Excel第一行的属性字段需要与bean的属性字段一致,否则无法调用目标方法,具体可参见ReflectionInitValue的方法。
1 package hello; 2 3 import java.util.List; 4 5 import com.hdbs.beans.Student; 6 import com.hdbs.resolver.ReadExcelUtil; 7 8 /** 9 * @version 1.0.0 10 * @author cnblogs-WindsJune 11 * @date 2018年9月23日 上午1:16:34 12 * 13 */ 14 public class Test { 15 public static void main(String[] args) { 16 Student student=new Student(); 17 String filePath="E:/test.xlsx"; 18 try { 19 ReadExcelUtil readExcelUtil=new ReadExcelUtil(student,filePath); 20 List<Object> list=readExcelUtil.getObjectsList(); 21 for (Object object:list){ 22 Student test=(Student) object; 23 System.out.println(test.toString()); 24 } 25 } catch (Exception e) { 26 e.printStackTrace(); 27 } 28 } 29 }
表格规范:
执行结果:
以上是关于POI解析Excel之应用反射等技术实现动态读取的主要内容,如果未能解决你的问题,请参考以下文章
Excel神办公—使用EasyExce实现数据“有对象写入”和“无对象写入”