目录树

  • 背景
  • 技术选型
  • 问题分析
  • 技术要点及难点分析
  • 源码分析
  • 测试用例

背景

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 -->
 <dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.15</version>
</dependency>
  • 主要类:Common.java、LRUCache.java、LRUCacheException.java、ResolveFileException.java、ReadExcelUtil.java、ReflectionInitValue.java、Student、Test
  • Common.java:基础常量池,主要用于反射执行Method方法时判断Method的参数类型的常量。
 package com.hdbs.common;

 /**
* @author :cnblogs-WindsJune
* @version :1.1.0
* @date :2018年9月20日 下午6:33:54
* @comments :解析Excel公共类常量类
*/ public class Common { public static final String OFFICE_EXCEL_2003_POSTFIX_xls = "xls";
public static final String OFFICE_EXCEL_2010_POSTFIX_xlsx = "xlsx";
public static final String DATA_TYPE_long ="long";
public static final String DATA_TYPE_boolean ="boolean";
public static final String DATA_TYPE_int ="int";
public static final String DATA_TYPE_float ="float";
public static final String DATA_TYPE_double ="double";
public static final String DATA_TYPE_Long ="class java.lang.Long";
public static final String DATA_TYPE_Integer ="class java.lang.Integer"; }
  • LRUCacheException.java;LRU缓存自定义异常类。
 package com.hdbs.exceptions;

 /**
* Creater: cnblogs-WindsJune
* Date: 2018/9/21
* Time: 10:04
* Description: No Description
*/
public class LRUCacheException extends Exception{
/**
* 错误码
*/
private String errorCode; /**
* 错误描述
*/
private String errorMessage; public LRUCacheException(String errorCode, String errorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
} public LRUCacheException(String message) {
super(message);
this.errorMessage = errorMessage;
} public String getErrorCode() {
return errorCode;
} public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
} public String getErrorMessage() {
return errorMessage;
} public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
  • ResolveFileException.java;解析Excel自定义异常类。
 package com.hdbs.exceptions;
/**
* Creater: cnblogs-WindsJune
* Date: 2018/9/20
* Time: 19:44
* Description: 解析Excel的公共异常类
*/ public class ResolveFileException extends RuntimeException{ /**
* 错误码
*/
private String errorCode; /**
* 错误描述
*/
private String errorMessage; public ResolveFileException(String errorCode, String errorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
} public ResolveFileException(String message) {
super(message);
this.errorMessage = errorMessage;
} public String getErrorCode() {
return errorCode;
} public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
} public String getErrorMessage() {
return errorMessage;
} public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
  • LRUCache.java:LRU缓存池,主要用于不同线程反射获取的Methods,减少相同线程反射执行次数,减轻应用的负载、提高执行效率。我这里是基于LinkedHashMap实现的LRU缓存,你也可以用数组或者其他方式实现该算法。以下代码逻辑如果不能理解的可以先去了解LinkedHashSet的源码。
 package com.hdbs.common;

 import com.hdbs.exceptions.LRUCacheException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map; /**
* Creater: cnblogs-WindsJune
* Date: 2018/9/20
* Time: 19:44
* Description: LinkedHashMap实现LRU缓存不同线程反射获取的Method方法
*/
public class LRUCache {
private static final Logger LOGGER=LoggerFactory.getLogger(LRUCache.class);
//缓存容量
private static final int cacheSize = 10; private static final Map<Integer,Method[]> cacheMap = new LinkedHashMap<Integer, Method[]>((int) Math.ceil(cacheSize / 0.75f) + 1, 0.75f, true){
@Override
protected boolean removeEldestEntry(Map.Entry<Integer,Method[]> eldest){ return size()> cacheSize; }
}; /**
* 设置缓存
* @param key
* @param methods
* @return boolean
*/
public static boolean set (Integer key,Method [] methods) throws LRUCacheException {
try {
cacheMap.put(key,methods);
return true;
}
catch ( Exception e ){
throw new LRUCacheException("Set LRU缓存异常!");
}
} /**
* 获取缓存的Method
* @param key
* @return Method
*/
public static Method[] get(Integer key) throws LRUCacheException {
Method[] methods=null;
try {
methods=cacheMap.get(key);
}catch ( Exception e ){
throw new LRUCacheException("Get LRU缓存异常!{}");
}
return methods;
}
}
  • ReadExcelUtil.java;解析Excel数据工具类(将Excel加载到内存)
 package com.hdbs.resolver;

 import com.hdbs.common.Common;
import com.hdbs.exceptions.ResolveFileException;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
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.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map; /**
* @author :cnblogs-WindsJune
* @version :1.1.0
* @date :2018年9月20日 下午6:13:43
* @comments :
*/ public class ReadExcelUtil { private static final Logger LOGGER = LoggerFactory.getLogger(ReadExcelUtil.class);
  //存放属性集
private Map<Integer,String []> fieldsMap=new HashMap<>();
  //存放解析后的对象List
private List<Object> objectsList = new ArrayList<>();
  //反射运行时对象
private Object object=null;
  //Excel文件路径
private String path =null;
  //获取解析后的对象集
public List<Object> getObjectsList() {
return this.objectsList;
} public ReadExcelUtil(Object object,String path) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException {
this.object=object;
this.path=path;
readExcel();
} /**
* 添加Object到List中
* @param object
* @return
*/
public boolean addListObject(Object object){
boolean isSucceed=this.objectsList.add(object);
return isSucceed;
} /**
* 读取excel,判断是xls结尾(2010之前);还是xlsx结尾(2010以后)的Excel
*
* @return
* @throws IOException
*/
public boolean readExcel() throws IOException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
if (StringUtils.isEmpty(path)) {
return false;
} else {
// 截取后缀名,判断是xls还是xlsx
String postfix = path.substring(path.lastIndexOf(".") + 1);
if (!StringUtils.isEmpty(postfix)) {
if (Common.OFFICE_EXCEL_2003_POSTFIX_xls.equals(postfix)) {
return readXls();
} else if (Common.OFFICE_EXCEL_2010_POSTFIX_xlsx.equals(postfix)) {
return readXlsx();
}
} else {
LOGGER.error("文件后缀名有误!");
throw new ResolveFileException("文件后缀名有误!" + "[" + path + "]");
}
}
return false;
} /**
* 读取xls(2010)之后的Excel
*
* @return
* @throws IOException
*/
public boolean readXlsx() throws IOException{
File file = new File(path);
InputStream is = new FileInputStream(file);
XSSFWorkbook xssfWorkbook = new XSSFWorkbook(is);
// 遍历sheet页
for (int numSheet = 0; numSheet < xssfWorkbook.getNumberOfSheets(); numSheet++) {
XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(numSheet);
String [] fields=null;
if (xssfSheet == null) {
continue;
}
// 循环行
for (int rowNum = 0; rowNum <= xssfSheet.getLastRowNum(); rowNum++) {
XSSFRow xssfRow = xssfSheet.getRow(rowNum);
int cloumns=xssfRow.getLastCellNum();
int i=0;
//获取第一行的所有属性
if (rowNum == 0){
fields=getFields(xssfRow,cloumns);
fieldsMap.put(numSheet,fields);
continue;
}
//遍历数据,反射set值
while (i<cloumns){
XSSFCell field=xssfRow.getCell(i);
String value=getValue(field);
try {
ReflectionInitValue.setValue(object,fields[i],value);
}catch ( Exception e ){
throw new ResolveFileException(e.getMessage());
}
i++;
}
//通过反射执行clone复制对象
Object result=ReflectionInitValue.invokeClone(object,"clone");
this.addListObject(result);
// System.out.println(object.toString());
}
}
return true;
} /**
* 读取xls(2010)之前的Excel
*
* @return
* @throws IOException
*/
public boolean readXls() throws IOException, ResolveFileException {
InputStream is = new FileInputStream(path);
HSSFWorkbook hssfWorkbook = new HSSFWorkbook(is);
// 遍历sheet页
for (int numSheet = 0; numSheet < hssfWorkbook.getNumberOfSheets(); numSheet++) {
HSSFSheet hssfSheet = hssfWorkbook.getSheetAt(numSheet);
String[] fields = null;
if (hssfSheet == null) {
continue;
}
// 循环行Row
for (int rowNum = 0; rowNum <= hssfSheet.getLastRowNum(); rowNum++) {
HSSFRow hssfRow = hssfSheet.getRow(rowNum);
int cloumns=hssfRow.getLastCellNum();
int i=0;
//获取第一行的所有属性
if (rowNum == 0){
//获取属性字段
fields=getFields(hssfRow,cloumns);
fieldsMap.put(numSheet,fields);
continue;
}
//遍历数据,反射set值
while (i<cloumns){
HSSFCell field=hssfRow.getCell(i);
String value=getValue(field);
try {
ReflectionInitValue.setValue(object,fields[i],value);
}catch ( Exception e ){
throw new ResolveFileException(e.getMessage());
}
i++;
}
//通过反射执行clone复制对象
Object result=ReflectionInitValue.invokeClone(object,"clone");
this.addListObject(result);
}
}
return true;
} /**
* xlsx -根据数据类型,获取单元格的值
* @param xssfRow
* @return
*/
@SuppressWarnings({ "static-access" })
private static String getValue(XSSFCell xssfRow) {
String value=null;
try {
if (xssfRow.getCellType() == xssfRow.CELL_TYPE_BOOLEAN) {
// 返回布尔类型的值
value=String.valueOf(xssfRow.getBooleanCellValue()).replace(" ","");
} else if (xssfRow.getCellType() == xssfRow.CELL_TYPE_NUMERIC) {
// 返回数值类型的值
value= String.valueOf(xssfRow.getNumericCellValue()).replace(" ","");
} else {
// 返回字符串类型的值
value= String.valueOf(xssfRow.getStringCellValue()).replace(" ","");
}
} catch (Exception e) {
//单元格为空,不处理
value=null;
LOGGER.error("单元格为空!");
}
return value;
} /**
* xls-根据数据类型,获取单元格的值
* @param hssfCell
* @return
*/
@SuppressWarnings({ "static-access" })
private static String getValue(HSSFCell hssfCell) {
String value=null;
try {
if (hssfCell.getCellType() == hssfCell.CELL_TYPE_BOOLEAN) {
// 返回布尔类型的值
value=String.valueOf(hssfCell.getBooleanCellValue()).replaceAll(" ","");
} else if (hssfCell.getCellType() == hssfCell.CELL_TYPE_NUMERIC) {
// 返回数值类型的值
value=String.valueOf(hssfCell.getNumericCellValue()).replaceAll(" ","");
} else {
// 返回字符串类型的值
value=String.valueOf(hssfCell.getStringCellValue()).replaceAll(" ","");
}
} catch (Exception e) {
//单元格为空,不处理
value=null;
LOGGER.error("单元格为空!");
}
return value;
} /**
* xls Excel文件类型获取属性(2010之前)
* @param cloumns
* @return String[]
*/
private static String[] getFields (HSSFRow hssfRow,int cloumns){
String [] fields=new String[cloumns];
int i=0;
try {
while (i<cloumns){
HSSFCell field=hssfRow.getCell(i);
String value=getValue(field);
fields[i]=value.trim();
i++;
}
}catch ( Exception e){
throw new ResolveFileException("获取属性集失败!");
}
return fields;
} /**
* xlsx Excel文件类型获取属性(2010之后)
* @param cloumns
* @return String[]
*/
private static String[] getFields(XSSFRow xssfRow,int cloumns){
String [] fields=new String[cloumns];
int i=0;
try {
while (i<cloumns){
XSSFCell field=xssfRow.getCell(i);
String value=getValue(field);
fields[i]=value.trim();
i++;
}
}catch ( Exception e ){
throw new ResolveFileException("获取属性集失败!");
}
return fields;
} }
  • ReflectionInitValue.java;
 package com.hdbs.resolver;

 import com.hdbs.common.Common;
import com.hdbs.common.LRUCache;
import com.hdbs.exceptions.LRUCacheException;
import com.hdbs.exceptions.ResolveFileException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type; /**
* Creater: cnblogs-WindsJune
* Date: 2018/9/21
* Time: 9:54
* Description: No Description
*/
public class ReflectionInitValue { private static int threadHashCodeKey=Thread.currentThread().toString().hashCode(); /**
* 通过反射动态将Excel读取的信息设置到对应的bean中
*
* @param object-存储对象bean
* @param key-属性参数名
* @param value-属性值
* @throws Exception
*/
public static void setValue(Object object, String key, String value) throws LRUCacheException {
String methodName = null;
String paramType = null;
Method[] methods = null;
if (LRUCache.get(threadHashCodeKey) == null) {
Class<?> clazz = object.getClass();
methods = clazz.getDeclaredMethods();
LRUCache.set(threadHashCodeKey, methods);
} else {
methods = LRUCache.get(threadHashCodeKey);
}
for (Method method : methods) {
methodName = method.getName();
if (methodName.startsWith("set") && methodName.toLowerCase().equals("set" + key.toLowerCase())) {
Type[] types = method.getGenericParameterTypes();
for (Type type : types) {
paramType = type.toString();
// 根据参数类型转化value,并进行set操作
excuteInvokeSetvalue(object, method, paramType, value, 0);
}
// 该属性已经执行setValue操作,无需循环
break;
}
}
} /**
* 初始化对象bean
*
* @param object
* @throws Exception
*/
public static void initBeans(Object object) throws ResolveFileException, LRUCacheException {
// Class<?> clazz = object.getClass();
String methodName = null;
String paramType = null;
Method[] methods = LRUCache.get(threadHashCodeKey);
try {
for (Method method : methods) {
methodName = method.getName();
if (methodName.startsWith("set")) {
Type[] types = method.getGenericParameterTypes();
for (Type type : types) {
paramType = type.getClass().getName();
}
// 根据参数类型转化value,并进行set初始化属性值
excuteInvokeSetvalue(object, method, paramType, "", 1);
}
}
} catch (Exception e) {
throw new ResolveFileException("初始化bean错误!Method:[ " + methodName + " ]");
}
} /**
* 根据参数类型转化value,并进行set操作
*
* @param object-存储对象bean
* @param method-执行的set对应属性的方法
* @param paramType-属性参数类型
* @param value-属性值
* @param operationType-操作类型(0-设置属性,1-初始化bean)
* @throws Exception
*/
public static void excuteInvokeSetvalue(Object object, Method method, String paramType, String value,
int operationType){
try {
switch (paramType) {
case Common.DATA_TYPE_long: {// 参数属性long
if (value !=null && value.contains(".")){
value=value.substring(0,value.lastIndexOf("."));
}
Long temp = Long.valueOf(operationType == 0 && value !=null ? value : "0");
method.invoke(object, temp);
break;
}
case Common.DATA_TYPE_boolean: {// 参数属性boolean
boolean temp = (operationType == 0 ? (Boolean.valueOf(value != null ? value:"false")) : false);
method.invoke(object, temp);
break;
}
case Common.DATA_TYPE_int: {// 参数属性int
if (value !=null && value.contains(".")){
value=value.substring(0,value.lastIndexOf("."));
}
int temp = Integer.valueOf(operationType == 0 && value!=null ? value : "0");
method.invoke(object, temp);
break;
}
case Common.DATA_TYPE_float: {// 参数属性float
if (value !=null && value.contains(".")){
value=value.substring(0,value.lastIndexOf("."));
}
float temp = Float.valueOf(operationType == 0 && value !=null ? value : "0");
method.invoke(object, temp);
break;
}
case Common.DATA_TYPE_double: {// 参数属性double
double temp = Double.valueOf(operationType == 0 && value !=null ? value : "0");
method.invoke(object, temp);
break;
}
case Common.DATA_TYPE_Long: {// 参数属性Long
if (value !=null && value.contains(".")){
value=value.substring(0,value.lastIndexOf("."));
}
Long temp = Long.valueOf(operationType == 0 && value!=null ? value : "0");
method.invoke(object, temp);
break;
}
case Common.DATA_TYPE_Integer: {// 参数属性Integer
if (value !=null && value.contains(".")){
value=value.substring(0,value.lastIndexOf("."));
}
int temp = Integer.valueOf(operationType == 0 && value!=null ? value : "0");
method.invoke(object, temp);
break;
}
default: {// 参数属性String
if (value !=null && value.contains(".")){
value=value.substring(0,value.lastIndexOf("."));
}
method.invoke(object, operationType == 0 ? value : null);
break;
}
} } catch ( IllegalAccessException e ) {
throw new ResolveFileException("invoke方法错误![Method:" + method.getName() + " [value:" + value + " ]");
} catch ( InvocationTargetException e ) {
throw new ResolveFileException("invoke方法错误![Method:" + method.getName() + " [value:" + value + " ]");
} catch (Exception e) {
throw new ResolveFileException("字段属性错误![Method:" + method.getName() + " [value:" + value + " ]");
} } /**
*
* @param object
* @param methodName
* @return
* @throws ResolveFileException
*/
public static Object invokeClone (Object object,String methodName){
Class clazz=object.getClass();
try {
Method method=clazz.getMethod(methodName);
Object result=method.invoke(object);
return result;
}catch ( Exception e ){
throw new ResolveFileException("解析Excel,反射执行set操作异常!");
} } }
  • Student.java;用于存储数据信息得bean。
 package com.hdbs.beans;

 import java.util.ArrayList;
import java.util.List; /**
* @author :WindsJune/博客园:WindsJune
* @version :1.1.0
* @date :2018年9月20日 下午6:05:57
* @comments :
*/ public class Student implements Cloneable{ /**
* id
*/
private Integer id;
/**
* 学号
*/
private String no;
/**
* 姓名
*/
private String name;
/**
* 学院
*/
private String age;
/**
* 成绩
*/
private float score; /**
* 地址
*/
private String adress; private List<Student> studentsList=new ArrayList<>(); public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public String getNo() {
return no;
} public void setNo(String no) {
this.no = no;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getAge() {
return age;
} public void setAge(String age) {
this.age = age;
} public float getScore() {
return score;
} public void setScore(float score) {
this.score = score;
} public String getAdress() {
return adress;
} public void setAdress(String adress) {
this.adress = adress;
} public List<Student> getStudentsList() {
return studentsList;
} public Object clone() throws CloneNotSupportedException{
return super.clone();
} @Override
public String toString() {
return "Student{" +
"id=" + id +
", no='" + no + '\'' +
", name='" + name + '\'' +
", age='" + age + '\'' +
", score=" + score +
", adress='" + adress + '\'' +
'}';
}
}
  • Test.java;测试方法,在new ReadExcelUtil 的时候只需要将对象类型与Excel文件路径传入构造函数即可,然后调用 ReadExcelUtil的getObjectList即可得到解析后的所有对象。至于这个对象你可以用任何的对象,你可以换成Teacher、OrderInfo、UserInfo......但是前面提到的:Excel第一行的属性字段需要与bean的属性字段一致,否则无法调用目标方法,具体可参见ReflectionInitValue的方法。
 package hello;

 import java.util.List;

 import com.hdbs.beans.Student;
import com.hdbs.resolver.ReadExcelUtil; /**
* @version 1.0.0
* @author cnblogs-WindsJune
* @date 2018年9月23日 上午1:16:34
*
*/
public class Test {
public static void main(String[] args) {
Student student=new Student();
String filePath="E:/test.xlsx";
try {
ReadExcelUtil readExcelUtil=new ReadExcelUtil(student,filePath);
List<Object> list=readExcelUtil.getObjectsList();
for (Object object:list){
Student test=(Student) object;
System.out.println(test.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

表格规范:

 

执行结果:

Java解析Excel之应用Reflection等技术实现动态读取的更多相关文章

  1. Java 解析Excel(xls、xlsx两种格式)

    Java 解析Excel(xls.xlsx两种格式) 一.环境 JDK 1.8 二.JAR 1.commons-collections4-4.1.jar 2.poi-3.9-20121203.jar ...

  2. java 解析excel工具类

      java 解析excel工具类 CreateTime--2018年3月5日16:48:08 Author:Marydon ReadExcelUtils.java import java.io.Fi ...

  3. java 解析excel

    2014年2月25日 14:24:48 解析excel方法 //首先是jar包下载,请自行百度 //代码 package cn.wuwenfu.excel; import java.io.File; ...

  4. 转:java 解析excel,带合并单元的excel

    收集了一些对博主有帮助的博文,如下 >>>>>>>>>>>第一部分: 首先,mavn导入jar包 <!-- 解析excel需要导 ...

  5. 如何实现批量上传----------Java解析excel

    一.引子 在web平台开发中仅经常会遇到一下需要批量的问题,通常得做法是使用excel上传,下面主要介绍一下在实际开发中到的实例. 二.准备工作 1.需要导入的jar包(主要用到poi包) (1)po ...

  6. java解析Excel日期格式转换问题

    Excel上传导入,Excel里面单元格是日期的会解析出来数字,比如2020-07-11会解析为44023解决方法一: Excel单元格格式设置为文本格式.解决方法二: 使用代码处理,把解析出来的44 ...

  7. Java解析Excel

    前两天总结了些关于Excel和CSV结合TestNG进行数据驱动测试的例子,对于Excel存放TestCase和关键字如何进行解析,也做了对应的总结,希望在学习的路上勇往直前,有不对的地方,希望大家指 ...

  8. java解析Excel(xls、xlsx两种格式)

    https://www.cnblogs.com/hhhshct/p/7255915.html ***************************************************** ...

  9. Java解析excel文档并以List<T>输出

    /********************************************************工具类start*********************************** ...

随机推荐

  1. VUE配置项结构

    VUE配置项结构 config:项目的配置文件 index.js: 基础的配置信息 dev.env.js:开发环境配置信息 prod.env.js:线上环境配置信息 build: 项目打包所需要的内容 ...

  2. plan,idea,and dream

    自学机器学习/数据分析/前端 目前想法是从前端入手,学会写/分析网页及其内容/数据,然后使用爬虫爬取数据,然后用机器学习算法对数据进行处理.哈哈,想法是不是太天真了. 学习都从网上的资料入手,因此发现 ...

  3. Tinker + Bugly + Jenkins 爬坑之路

    前阵子 Android 端的线上崩溃比较多,热修复被提上日程.实现方案是 Tinker,Jenkins 打包,最后补丁包上传到 Bugly 进行分发.主要在 Jenkins 打包这一块爬了不少坑,现记 ...

  4. MySQL中有关char、varchar、int、tinyint、decimal

    char.varchar属于字符串类型 1.char属于定长,能确切的知道列值的长度,也就是有多少个字符.当指定char(5)时,表示只能存5个字符,如5个英文‘a’,5个汉字‘我’,5个符号‘&am ...

  5. 2 pygraphviz在windows10 64位下的安装问题(反斜杠的血案)

    可以负责任的说,这篇文档是windows10安装pygraphviz中,在中文技术网站中最新的文档,没有之一.是自己完全结合各种问题,包括调试等,总结出来的. 问题来源:主要是可视化RvNN网络的树结 ...

  6. SQL Server 表的管理_关于事务操作的详解(案例代码)

    SQL Server 表的管理_关于事务操作的详解(案例代码) 1.概念 事务(transaction): 是将多个修改语句组合在一起的方法,这个方法中的所有语句只有全部执行才能正确完成功能.即要么全 ...

  7. Python实例---游戏人生[类的学习]

    # -*- coding:utf-8 -*- # ##################### 定义实现功能的类 ##################### class Person: def __in ...

  8. August 04th 2017 Week 31st Friday

    Love is a vine that grows into our hearts. 爱是长在我们心里的藤蔓. What is love? Maybe no one can explain it cl ...

  9. December 09th 2016 Week 50th Friday

    In books lies the soul of the whole past time. 书中有所有先贤的全部灵魂. I must know that if I run my business i ...

  10. [COGS 2065]学数数

    2065. 学数数 ★★★☆   输入文件:jxthree.in   输出文件:jxthree.out   简单对比时间限制:1 s   内存限制:256 MB [题目描述] 从前有一只咩,还有一只叽 ...