利用Java反射机制和POI实现导出Excel功能

##  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的数据参数,每一行是一个DO。那么,如果我们需要程序适应多种DO数据参数时,如何来确定表格的列数和每一个字段所在的列号呢?我们可以使用Java的反射机制来实现这种自适应需求。
这种需求的关键是我们需要程序智能的识别出传入的每个DO对象有多少个需要展示的字段,以及表头是什么,如何按照表头的顺序展示每个DO中的字段。我们来看看,如何利用反射机制实现这个功能。
### 1)创建表头
每一个表格都需要表头来表明每一列的意义,表头一般会是具有意义的汉字或者英文;表头与DO之间的关联就是DO中的变量名。所以我们可以使用一个LinkedHashMap结构来存储DO中的字段变量名和表头文字之间的映射,比如
```
LinkedHashMap header = new LinkedHashMap();
        header.put("recordTime", "时间");
        header.put("businessLine", "业务线");
        header.put("resourceName", "名称");        
```
使用LinkedHashMap是为了让表头的展示顺序能够受我们的控制,能够与hashmap中写入的顺序一致。
```
   /**
     * 创建各列表头
     * @param headString
     */
    public void createHeadRow(){    
        Row head = sheet.createRow(0);   //创建表格第一行对象,为表头行
        Iterator> 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为名称、类型和值组成的对象 
     */
    public static Map convertObjectToMap(Object o){
        Class oClass = o.getClass();
        Field[] fields = oClass.getDeclaredFields();   //获取类中的所有声明的属性
        Map map = new HashMap();
        try{
            for(int i=0;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             Row row = sheet.createRow(i+1);  //创建行,表头是第0行
           //转换数据,将每一个DO映射为属性名与FieldsEntity的Map
            Map map = DataConvertUtil.convertObjectToMap(data.get(i));            
            Iterator> head = header.entrySet().iterator();
            //创建每行的单元格并填充值
            for(int col = 0;col < header.size() && head.hasNext();col++){
                Cell cell = row.createCell(col);
                //设置表头的迭代器
                Map.Entry enty = (Map.Entry)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 header;   //表头    

//此处省略了功能模块代码,功能时间参见上面的介绍代码


    //初始化
    public static void init(String sheetName) {
        wb = new SXSSFWorkbook();
        sheet = wb.createSheet(sheetName);
    header = new LinkedHashMap();
        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 data = new ArrayList();
        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功能)