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实现数据“有对象写入”和“无对象写入”

Excel神办公—使用EasyExce实现数据“有对象写入”和“无对象写入”

Swift之深入解析反射Mirror与错误处理

java使用poi实现excel表格生成

用java领域技术给excel加密?

poi 解析excel问题