利用Java反射机制和POI实现导出Excel功能
2014-11-16 17:00
465 查看
## 1.Apache POI简介
Apache poi工具包是一个著名的操作Microsoft文档的Java工具库,里面提供大量的对word,excel,ppt操作的方法。最近由于项目需要一个将数据库数据导出到excel的功能,所以学习了这个工具包的用法。下面简要介绍一下操作excel的几个重要的类和方法。
### 1.HSSF、XSSF和SXSSF
HSSF和XSSF包都是poi中操作excel的包,他们的区别在于hssf包操作的是Excel '97(-2007)格式的文档,而xssf操作的是 Excel 2007 以上版本格式的文档。换言之,hssf生成的excel文件后缀名是.xls,xssf生成的文件后缀是.xlsx。如果要同时支持这两种格式的文档,poi也提供了SXSSF包来实现其功能。
### 2.创建一个excel文档
poi包在操作的时候有几个重要的概念,workbook,sheet,row,和cell.。这几个概念与excel中的同样的概念对应。一个workbook代表着一个完整的excel文档,它包含一个或多个sheet;一个sheet表示一个具体的表格,包含多个row对象,一个row就是表格的一行,包含多个单元格cell。用poi生成一个excel文档的示例如下:
```
public class CreateExcelSimpleTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建一个工作簿
Workbook wb = new SXSSFWorkbook();
//在该工作簿上创建一个表格并命名
Sheet sheet = wb.createSheet("POISimpleTest");
//在表格中创建一个行对象,也就是第1行,poi中行数是从0开始计算的
Row row = sheet.createRow(0);
//在行对象中创建第一个单元格对象,单元格也是从0开始计数
Cell cell = row.createCell(0);
cell.setCellValue("第一个单元格");
/**
* 上面的操作都是在内存中进行,声称表格需要输出到文件
*/
FileOutputStream out = null;
try{
out = new FileOutputStream("D:\\第一个poi表格.xls");
wb.write(out);
}catch(IOException e){
e.printStackTrace();
}finally{
if(out != null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
```
## 2.利用Java反射机制实现自适应导出功能
从上面的程序可以看出,仅仅使用poi包进行excel生成的话会很死板。因为创建行和单元格的时候都需要指定行号和列号。我们导出Excel的时候,一般会传入类似以List<XXXDO>的数据参数,每一行是一个DO。那么,如果我们需要程序适应多种DO数据参数时,如何来确定表格的列数和每一个字段所在的列号呢?我们可以使用Java的反射机制来实现这种自适应需求。
这种需求的关键是我们需要程序智能的识别出传入的每个DO对象有多少个需要展示的字段,以及表头是什么,如何按照表头的顺序展示每个DO中的字段。我们来看看,如何利用反射机制实现这个功能。
### 1)创建表头
每一个表格都需要表头来表明每一列的意义,表头一般会是具有意义的汉字或者英文;表头与DO之间的关联就是DO中的变量名。所以我们可以使用一个LinkedHashMap<String,String>结构来存储DO中的字段变量名和表头文字之间的映射,比如
```
LinkedHashMap<String,String> header = new LinkedHashMap<String,String>();
header.put("recordTime", "时间");
header.put("businessLine", "业务线");
header.put("resourceName", "名称");
```
使用LinkedHashMap是为了让表头的展示顺序能够受我们的控制,能够与hashmap中写入的顺序一致。
```
/**
* 创建各列表头
* @param headString
*/
public void createHeadRow(){
Row head = sheet.createRow(0); //创建表格第一行对象,为表头行
Iterator<Entry<String,String>> headTitle = header.entrySet().iterator(); //循环输出表头
for(int i=0;headTitle.hasNext();i++){
Cell cell = head.createCell(i);
cell.setCellValue(headTitle.next().getValue());
}
}
```
### 2)利用反射获取DO的属性值
我们需要让程序知道传过来的DO有哪些属性,属性变量的类型是什么,以及如何获取属性的值,做到这样我们才能让程序自动将相应的值填写到表格里面。
首先定义一个Entry类,来存储每一个属性变量的名称、类型和值;
```
package poi.autoreflect;
/**
* 类FieldsEntity.java的实现描述:TODO 类实现描述
* @author keming.hh 2014年9月24日 下午4:13:21
*/
public class FieldsEntity {
private String attributeName; //属性变量名称
private Object value; //属性变量值
private Class classType; //属性类型
public String getAttributeName() {
return attributeName;
}
public void setAttributeName(String attributeName) {
this.attributeName = attributeName;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public Class getClassType() {
return classType;
}
public void setClassType(Class classType) {
this.classType = classType;
}
public FieldsEntity(String fieldName, Object o, Class classType){
this.attributeName = fieldName;
this.value = o;
this.classType = classType;
}
}
```
对象转换,将每一个DO转换为上面的FieldsEntity 类形式,并存如map中;
```
/**
* 类DataConvertUtil.java的实现描述:TODO 类实现描述
*/
public class DataConvertUtil {
// private static final Logger logger = LoggerFactory.getLogger(DataConvertUtil.class);
/**
* 将对象的属性名称与值映射为MAP
* @param o 对象
* @return Map<key,value> key为属性名称,value为名称、类型和值组成的对象
*/
public static Map<String,FieldsEntity> convertObjectToMap(Object o){
Class oClass = o.getClass();
Field[] fields = oClass.getDeclaredFields(); //获取类中的所有声明的属性
Map<String,FieldsEntity> map = new HashMap<String, FieldsEntity>();
try{
for(int i=0;i<fields.length;i++){
// 不对序列化ID进行映射
if(fields[i].getName().equals("serialVersionUID")){
continue;
}
Object valueObject = getFieldValue(o,fields[i].getName());
map.put(fields[i].getName(), new FieldsEntity(fields[i].getName(), valueObject, fields[i].getType()));
}
return map;
}catch(Exception e){
e.printStackTrace();
}
}
}
```
从上面的代码可以看出,我们利用反射的机制获取每一个DO中的所有fied,同时利用getFieldValue方法获取每一个属性的值。那么这里getFieldValue方法做什么工作呢?其实他就是拼凑出DO中的所有getter方法名,然后调用getter方法来获得属性值。当然,这种情况只有在DO的getter方法是符合规范的命名的情况下才能有效,由此可见命名规范多么重要啊。
```
/**
* 通过对象的getter方法获取属性值
* @param o 对象
* @param name 属性名称
* @return 相应属性的值
*/
public static Object getFieldValue(Object o,String name) throws SecurityException, NoSuchMethodException,IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Class owner = o.getClass();
Method mothed = owner.getMethod(createGetter(name));
Object object = mothed.invoke(o);
return object;
}
/**
* 通过属性名称拼凑getter方法
* @param fieldName
* @return
*/
public static String createGetter(String fieldName){
if(fieldName == null || fieldName.length() == 0 ){
return null;
}
StringBuffer sb = new StringBuffer("get");
sb.append(fieldName.substring(0, 1).toUpperCase()).append(fieldName.substring(1));
return sb.toString();
}
```
通过上面的步骤,我们就可以让程序获得任何一个DO的属性数量,属性值,然后我们就可以自动的进行Excel生成了。
### 3)生成表格
```
/**
* 创建数据行
* @param data
* @param cols
*/
public static void createRows(List<?> data){
int rowCount = data.size(); //根据数据集设置行数
for(int i=0;i<rowCount;i++){
Row row = sheet.createRow(i+1); //创建行,表头是第0行
//转换数据,将每一个DO映射为属性名与FieldsEntity的Map
Map<String,FieldsEntity> map = DataConvertUtil.convertObjectToMap(data.get(i));
Iterator<Entry<String,String>> head = header.entrySet().iterator();
//创建每行的单元格并填充值
for(int col = 0;col < header.size() && head.hasNext();col++){
Cell cell = row.createCell(col);
//设置表头的迭代器
Map.Entry<String,String> enty = (Map.Entry<String,String>)head.next();
String name = enty.getKey();
cell.setCellValue(map.get(name).getValue().toString()); //填充属性值
}
}
}
```
### 4)将excel输出到文件流
```
private static void flashoutFile(OutputStream out, SXSSFWorkbook book) {
try {
book.write(out);
out.close();
book.dispose();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(out!=null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
```
到此我们这个功能就实现了,可以让程序自动识别DO的需要展示的属性,并生成Excel文档。测试的代码如下
测试数据DO :
```
public class DataObject {
private Date recordTime;
private String businessLine;
private String resourceName;
public Date getRecordTime() {
return recordTime;
}
public void setRecordTime(Date recordTime) {
this.recordTime = recordTime;
}
public String getBusinessLine() {
return businessLine;
}
public void setBusinessLine(String businessLine) {
this.businessLine = businessLine;
}
public String getResourceName() {
return resourceName;
}
public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}
}
```
整体测试程序:
```
public class CreateExcelByReflect {
private static Sheet sheet; //表格对象
private static SXSSFWorkbook wb; //工作簿
private static LinkedHashMap<String,String> header; //表头
//此处省略了功能模块代码,功能时间参见上面的介绍代码
//初始化
public static void init(String sheetName) {
wb = new SXSSFWorkbook();
sheet = wb.createSheet(sheetName);
header = new LinkedHashMap<String,String>();
header.put("recordTime", "时间");
header.put("businessLine", "业务线");
header.put("resourceName", "名称");
}
public static void main(String[] args) {
init("测试表格");
Date date = new Date();
DataObject data1 = new DataObject();
data1.setRecordTime(date);
data1.setBusinessLine("业务1");
data1.setResourceName("资源1");
DataObject data2 = new DataObject();
data2.setRecordTime(date);
data2.setBusinessLine("业务2");
data2.setResourceName("资源2");
List<DataObject> data = new ArrayList<DataObject>();
data.add(data1);
data.add(data2);
createHeadRow(sheet); //创建表头
createRows(data); //创建数据行
try{
FileOutputStream out = new FileOutputStream("D:\\测试2.xlsx");
flashoutFile(out,wb);
}catch(Exception e){
e.printStackTrace();
}
}
}
```
Apache poi工具包是一个著名的操作Microsoft文档的Java工具库,里面提供大量的对word,excel,ppt操作的方法。最近由于项目需要一个将数据库数据导出到excel的功能,所以学习了这个工具包的用法。下面简要介绍一下操作excel的几个重要的类和方法。
### 1.HSSF、XSSF和SXSSF
HSSF和XSSF包都是poi中操作excel的包,他们的区别在于hssf包操作的是Excel '97(-2007)格式的文档,而xssf操作的是 Excel 2007 以上版本格式的文档。换言之,hssf生成的excel文件后缀名是.xls,xssf生成的文件后缀是.xlsx。如果要同时支持这两种格式的文档,poi也提供了SXSSF包来实现其功能。
### 2.创建一个excel文档
poi包在操作的时候有几个重要的概念,workbook,sheet,row,和cell.。这几个概念与excel中的同样的概念对应。一个workbook代表着一个完整的excel文档,它包含一个或多个sheet;一个sheet表示一个具体的表格,包含多个row对象,一个row就是表格的一行,包含多个单元格cell。用poi生成一个excel文档的示例如下:
```
public class CreateExcelSimpleTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建一个工作簿
Workbook wb = new SXSSFWorkbook();
//在该工作簿上创建一个表格并命名
Sheet sheet = wb.createSheet("POISimpleTest");
//在表格中创建一个行对象,也就是第1行,poi中行数是从0开始计算的
Row row = sheet.createRow(0);
//在行对象中创建第一个单元格对象,单元格也是从0开始计数
Cell cell = row.createCell(0);
cell.setCellValue("第一个单元格");
/**
* 上面的操作都是在内存中进行,声称表格需要输出到文件
*/
FileOutputStream out = null;
try{
out = new FileOutputStream("D:\\第一个poi表格.xls");
wb.write(out);
}catch(IOException e){
e.printStackTrace();
}finally{
if(out != null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
```
## 2.利用Java反射机制实现自适应导出功能
从上面的程序可以看出,仅仅使用poi包进行excel生成的话会很死板。因为创建行和单元格的时候都需要指定行号和列号。我们导出Excel的时候,一般会传入类似以List<XXXDO>的数据参数,每一行是一个DO。那么,如果我们需要程序适应多种DO数据参数时,如何来确定表格的列数和每一个字段所在的列号呢?我们可以使用Java的反射机制来实现这种自适应需求。
这种需求的关键是我们需要程序智能的识别出传入的每个DO对象有多少个需要展示的字段,以及表头是什么,如何按照表头的顺序展示每个DO中的字段。我们来看看,如何利用反射机制实现这个功能。
### 1)创建表头
每一个表格都需要表头来表明每一列的意义,表头一般会是具有意义的汉字或者英文;表头与DO之间的关联就是DO中的变量名。所以我们可以使用一个LinkedHashMap<String,String>结构来存储DO中的字段变量名和表头文字之间的映射,比如
```
LinkedHashMap<String,String> header = new LinkedHashMap<String,String>();
header.put("recordTime", "时间");
header.put("businessLine", "业务线");
header.put("resourceName", "名称");
```
使用LinkedHashMap是为了让表头的展示顺序能够受我们的控制,能够与hashmap中写入的顺序一致。
```
/**
* 创建各列表头
* @param headString
*/
public void createHeadRow(){
Row head = sheet.createRow(0); //创建表格第一行对象,为表头行
Iterator<Entry<String,String>> headTitle = header.entrySet().iterator(); //循环输出表头
for(int i=0;headTitle.hasNext();i++){
Cell cell = head.createCell(i);
cell.setCellValue(headTitle.next().getValue());
}
}
```
### 2)利用反射获取DO的属性值
我们需要让程序知道传过来的DO有哪些属性,属性变量的类型是什么,以及如何获取属性的值,做到这样我们才能让程序自动将相应的值填写到表格里面。
首先定义一个Entry类,来存储每一个属性变量的名称、类型和值;
```
package poi.autoreflect;
/**
* 类FieldsEntity.java的实现描述:TODO 类实现描述
* @author keming.hh 2014年9月24日 下午4:13:21
*/
public class FieldsEntity {
private String attributeName; //属性变量名称
private Object value; //属性变量值
private Class classType; //属性类型
public String getAttributeName() {
return attributeName;
}
public void setAttributeName(String attributeName) {
this.attributeName = attributeName;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public Class getClassType() {
return classType;
}
public void setClassType(Class classType) {
this.classType = classType;
}
public FieldsEntity(String fieldName, Object o, Class classType){
this.attributeName = fieldName;
this.value = o;
this.classType = classType;
}
}
```
对象转换,将每一个DO转换为上面的FieldsEntity 类形式,并存如map中;
```
/**
* 类DataConvertUtil.java的实现描述:TODO 类实现描述
*/
public class DataConvertUtil {
// private static final Logger logger = LoggerFactory.getLogger(DataConvertUtil.class);
/**
* 将对象的属性名称与值映射为MAP
* @param o 对象
* @return Map<key,value> key为属性名称,value为名称、类型和值组成的对象
*/
public static Map<String,FieldsEntity> convertObjectToMap(Object o){
Class oClass = o.getClass();
Field[] fields = oClass.getDeclaredFields(); //获取类中的所有声明的属性
Map<String,FieldsEntity> map = new HashMap<String, FieldsEntity>();
try{
for(int i=0;i<fields.length;i++){
// 不对序列化ID进行映射
if(fields[i].getName().equals("serialVersionUID")){
continue;
}
Object valueObject = getFieldValue(o,fields[i].getName());
map.put(fields[i].getName(), new FieldsEntity(fields[i].getName(), valueObject, fields[i].getType()));
}
return map;
}catch(Exception e){
e.printStackTrace();
}
}
}
```
从上面的代码可以看出,我们利用反射的机制获取每一个DO中的所有fied,同时利用getFieldValue方法获取每一个属性的值。那么这里getFieldValue方法做什么工作呢?其实他就是拼凑出DO中的所有getter方法名,然后调用getter方法来获得属性值。当然,这种情况只有在DO的getter方法是符合规范的命名的情况下才能有效,由此可见命名规范多么重要啊。
```
/**
* 通过对象的getter方法获取属性值
* @param o 对象
* @param name 属性名称
* @return 相应属性的值
*/
public static Object getFieldValue(Object o,String name) throws SecurityException, NoSuchMethodException,IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Class owner = o.getClass();
Method mothed = owner.getMethod(createGetter(name));
Object object = mothed.invoke(o);
return object;
}
/**
* 通过属性名称拼凑getter方法
* @param fieldName
* @return
*/
public static String createGetter(String fieldName){
if(fieldName == null || fieldName.length() == 0 ){
return null;
}
StringBuffer sb = new StringBuffer("get");
sb.append(fieldName.substring(0, 1).toUpperCase()).append(fieldName.substring(1));
return sb.toString();
}
```
通过上面的步骤,我们就可以让程序获得任何一个DO的属性数量,属性值,然后我们就可以自动的进行Excel生成了。
### 3)生成表格
```
/**
* 创建数据行
* @param data
* @param cols
*/
public static void createRows(List<?> data){
int rowCount = data.size(); //根据数据集设置行数
for(int i=0;i<rowCount;i++){
Row row = sheet.createRow(i+1); //创建行,表头是第0行
//转换数据,将每一个DO映射为属性名与FieldsEntity的Map
Map<String,FieldsEntity> map = DataConvertUtil.convertObjectToMap(data.get(i));
Iterator<Entry<String,String>> head = header.entrySet().iterator();
//创建每行的单元格并填充值
for(int col = 0;col < header.size() && head.hasNext();col++){
Cell cell = row.createCell(col);
//设置表头的迭代器
Map.Entry<String,String> enty = (Map.Entry<String,String>)head.next();
String name = enty.getKey();
cell.setCellValue(map.get(name).getValue().toString()); //填充属性值
}
}
}
```
### 4)将excel输出到文件流
```
private static void flashoutFile(OutputStream out, SXSSFWorkbook book) {
try {
book.write(out);
out.close();
book.dispose();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(out!=null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
```
到此我们这个功能就实现了,可以让程序自动识别DO的需要展示的属性,并生成Excel文档。测试的代码如下
测试数据DO :
```
public class DataObject {
private Date recordTime;
private String businessLine;
private String resourceName;
public Date getRecordTime() {
return recordTime;
}
public void setRecordTime(Date recordTime) {
this.recordTime = recordTime;
}
public String getBusinessLine() {
return businessLine;
}
public void setBusinessLine(String businessLine) {
this.businessLine = businessLine;
}
public String getResourceName() {
return resourceName;
}
public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}
}
```
整体测试程序:
```
public class CreateExcelByReflect {
private static Sheet sheet; //表格对象
private static SXSSFWorkbook wb; //工作簿
private static LinkedHashMap<String,String> header; //表头
//此处省略了功能模块代码,功能时间参见上面的介绍代码
//初始化
public static void init(String sheetName) {
wb = new SXSSFWorkbook();
sheet = wb.createSheet(sheetName);
header = new LinkedHashMap<String,String>();
header.put("recordTime", "时间");
header.put("businessLine", "业务线");
header.put("resourceName", "名称");
}
public static void main(String[] args) {
init("测试表格");
Date date = new Date();
DataObject data1 = new DataObject();
data1.setRecordTime(date);
data1.setBusinessLine("业务1");
data1.setResourceName("资源1");
DataObject data2 = new DataObject();
data2.setRecordTime(date);
data2.setBusinessLine("业务2");
data2.setResourceName("资源2");
List<DataObject> data = new ArrayList<DataObject>();
data.add(data1);
data.add(data2);
createHeadRow(sheet); //创建表头
createRows(data); //创建数据行
try{
FileOutputStream out = new FileOutputStream("D:\\测试2.xlsx");
flashoutFile(out,wb);
}catch(Exception e){
e.printStackTrace();
}
}
}
```
相关文章推荐
- java利用POI导出Excel功能实现
- spring+springmvc+hibernate利用poi实现导出Excel功能
- Springboot/SpringMVC+POI 实现Excel导出功能(点击下载方式实现)
- 利用poi实现页面上excel的导入和导出(不刷新页面)之导出
- Poi简单实现Excel的导出导入功能
- 利用poi实现页面上excel的导入和导出(不刷新页面)之导入
- 利用SQL*PLUS导出成EXCEL和html的功能实现报表统计
- Java程序员从笨鸟到菜鸟之(一百零五)java操作office和pdf文件(三)利用jxl实现数据导出excel报表以及与POI的区别
- java利用POI实现Excel导入导出详解-支持97-2013版本以及2017版本
- POI 文档 Excel导出功能实现
- POI实现java导出Excel功能
- java操作Excel之POI(5)利用POI实现使用模板批量导出数据
- Java程序员从笨鸟到菜鸟之(一百零五)java操作office和pdf文件(三)利用jxl实现数据导出excel报表以及与POI的区别
- Apache-POI实现Excel文档的导出下载功能
- JAVA利用POI实现excel的导入导出
- 利用poi实现table表格导出excel
- Java实现POI导出Excel报表功能
- 基于easyui利用poi技术实现导出,导入,导出text功能
- 利用java反射机制实现通用Excel导出
- java操作Excel之POI(4)利用POI实现数据的批量导出