Java解析Excel之應用Reflection等技術實現動態讀取


 目錄樹

  • 背景
  • 技術選型
  • 問題分析
  • 技術要點及難點分析
  • 源碼分析
  • 測試用例

 


 

背景

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包Git下載地址:https://github.com/GitHubSuper543/auto-resolver-excel-jar.git,源碼地址:https://github.com/GitHubSuper543/auto-resolver-excel-resource.git

  使用方法:新建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 }

 

表格規范:

 

執行結果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM