使用Jeesite開發的時候,我們都少不了Excel導入導出的功能。這部分需要我我們掌握基本的POI,反射,當然在我們的框架中還定義了註解,也樣在代碼上整潔許多,下面我們先看一下:
一. 導入導出的公共工具:
/**
* Copyright © 2012-2013 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
*/
package com.cyou.seal.common.utils.excel.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Excel註解定義
* @author ThinkGem
* @version 2013-03-10
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelField {
/**
* 導出字段名(默認調用當前字段的“get”方法,如指定導出字段爲對象,請填寫“對象名.對象屬性”,例:“area.name”、“office.name”)
*/
String value() default "";
/**
* 導出字段標題(需要添加批註請用“**”分隔,標題**批註,僅對導出模板有效)
*/
String title();
/**
* 字段類型(0:導出導入;1:僅導出;2:僅導入)
*/
int type() default 0;
/**
* 導出字段對齊方式(0:自動;1:靠左;2:居中;3:靠右)
*
* 備註:Integer/Long類型設置居右對齊(align=3)
*/
int align() default 0;
/**
* 導出字段字段排序(升序)
*/
int sort() default 0;
/**
* 如果是字典類型,請設置字典的type值
*/
String dictType() default "";
/**
* 反射類型
*/
Class<?> fieldType() default Class.class;
/**
* 字段歸屬組(根據分組導出導入)
*/
int[] groups() default {};
}
反射工具類:
/**
* 反射工具類.
* 提供調用getter/setter方法, 訪問私有變量, 調用私有方法, 獲取泛型類型Class, 被AOP過的真實類等工具函數.
* @author calvin
* @version 2013-01-15
*/
@SuppressWarnings("rawtypes")
public class Reflections {
private static final String SETTER_PREFIX = "set";
private static final String GETTER_PREFIX = "get";
private static final String CGLIB_CLASS_SEPARATOR = "$$";
private static Logger logger = LoggerFactory.getLogger(Reflections.class);
/**
* 調用Getter方法.
* 支持多級,如:對象名.對象名.方法
*/
public static Object invokeGetter(Object obj, String propertyName) {
Object object = obj;
for (String name : StringUtils.split(propertyName, ".")){
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
}
return object;
}
/**
* 調用Setter方法, 僅匹配方法名。
* 支持多級,如:對象名.對象名.方法
*/
public static void invokeSetter(Object obj, String propertyName, Object value) {
Object object = obj;
String[] names = StringUtils.split(propertyName, ".");
for (int i=0; i<names.length; i++){
if(i<names.length-1){
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
}else{
String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
invokeMethodByName(object, setterMethodName, new Object[] { value });
}
}
}
/**
* 直接讀取對象屬性值, 無視private/protected修飾符, 不經過getter函數.
*/
public static Object getFieldValue(final Object obj, final String fieldName) {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}
Object result = null;
try {
result = field.get(obj);
} catch (IllegalAccessException e) {
logger.error("不可能拋出的異常{}", e.getMessage());
}
return result;
}
/**
* 直接設置對象屬性值, 無視private/protected修飾符, 不經過setter函數.
*/
public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}
try {
field.set(obj, value);
} catch (IllegalAccessException e) {
logger.error("不可能拋出的異常:{}", e.getMessage());
}
}
/**
* 直接調用對象方法, 無視private/protected修飾符.
* 用於一次性調用的情況,否則應使用getAccessibleMethod()函數獲得Method後反覆調用.
* 同時匹配方法名+參數類型,
*/
public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
final Object[] args) {
Method method = getAccessibleMethod(obj, methodName, parameterTypes);
if (method == null) {
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
}
try {
return method.invoke(obj, args);
} catch (Exception e) {
throw convertReflectionExceptionToUnchecked(e);
}
}
/**
* 直接調用對象方法, 無視private/protected修飾符,
* 用於一次性調用的情況,否則應使用getAccessibleMethodByName()函數獲得Method後反覆調用.
* 只匹配函數名,如果有多個同名函數調用第一個。
*/
public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) {
Method method = getAccessibleMethodByName(obj, methodName);
if (method == null) {
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
}
try {
return method.invoke(obj, args);
} catch (Exception e) {
throw convertReflectionExceptionToUnchecked(e);
}
}
/**
* 循環向上轉型, 獲取對象的DeclaredField, 並強制設置爲可訪問.
*
* 如向上轉型到Object仍無法找到, 返回null.
*/
public static Field getAccessibleField(final Object obj, final String fieldName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(fieldName, "fieldName can't be blank");
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
try {
Field field = superClass.getDeclaredField(fieldName);
makeAccessible(field);
return field;
} catch (NoSuchFieldException e) {//NOSONAR
// Field不在當前類定義,繼續向上轉型
continue;// new add
}
}
return null;
}
/**
* 循環向上轉型, 獲取對象的DeclaredMethod,並強制設置爲可訪問.
* 如向上轉型到Object仍無法找到, 返回null.
* 匹配函數名+參數類型。
*
* 用於方法需要被多次調用的情況. 先使用本函數先取得Method,然後調用Method.invoke(Object obj, Object... args)
*/
public static Method getAccessibleMethod(final Object obj, final String methodName,
final Class<?>... parameterTypes) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
try {
Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
makeAccessible(method);
return method;
} catch (NoSuchMethodException e) {
// Method不在當前類定義,繼續向上轉型
continue;// new add
}
}
return null;
}
/**
* 循環向上轉型, 獲取對象的DeclaredMethod,並強制設置爲可訪問.
* 如向上轉型到Object仍無法找到, 返回null.
* 只匹配函數名。
*
* 用於方法需要被多次調用的情況. 先使用本函數先取得Method,然後調用Method.invoke(Object obj, Object... args)
*/
public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
Method[] methods = searchType.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
makeAccessible(method);
return method;
}
}
}
return null;
}
/**
* 改變private/protected的方法爲public,儘量不調用實際改動的語句,避免JDK的SecurityManager抱怨。
*/
public static void makeAccessible(Method method) {
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
&& !method.isAccessible()) {
method.setAccessible(true);
}
}
/**
* 改變private/protected的成員變量爲public,儘量不調用實際改動的語句,避免JDK的SecurityManager抱怨。
*/
public static void makeAccessible(Field field) {
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
.isFinal(field.getModifiers())) && !field.isAccessible()) {
field.setAccessible(true);
}
}
/**
* 通過反射, 獲得Class定義中聲明的泛型參數的類型, 注意泛型必須定義在父類處
* 如無法找到, 返回Object.class.
* eg.
* public UserDao extends HibernateDao<User>
*
* @param clazz The class to introspect
* @return the first generic declaration, or Object.class if cannot be determined
*/
@SuppressWarnings("unchecked")
public static <T> Class<T> getClassGenricType(final Class clazz) {
return getClassGenricType(clazz, 0);
}
/**
* 通過反射, 獲得Class定義中聲明的父類的泛型參數的類型.
* 如無法找到, 返回Object.class.
*
* 如public UserDao extends HibernateDao<User,Long>
*
* @param clazz clazz The class to introspect
* @param index the Index of the generic ddeclaration,start from 0.
* @return the index generic declaration, or Object.class if cannot be determined
*/
public static Class getClassGenricType(final Class clazz, final int index) {
Type genType = clazz.getGenericSuperclass();
if (!(genType instanceof ParameterizedType)) {
logger.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
return Object.class;
}
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
if (index >= params.length || index < 0) {
logger.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
+ params.length);
return Object.class;
}
if (!(params[index] instanceof Class)) {
logger.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
return Object.class;
}
return (Class) params[index];
}
public static Class<?> getUserClass(Object instance) {
Assert.notNull(instance, "Instance must not be null");
Class clazz = instance.getClass();
if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
Class<?> superClass = clazz.getSuperclass();
if (superClass != null && !Object.class.equals(superClass)) {
return superClass;
}
}
return clazz;
}
/**
* 將反射時的checked exception轉換爲unchecked exception.
*/
public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
|| e instanceof NoSuchMethodException) {
return new IllegalArgumentException(e);
} else if (e instanceof InvocationTargetException) {
return new RuntimeException(((InvocationTargetException) e).getTargetException());
} else if (e instanceof RuntimeException) {
return (RuntimeException) e;
}
return new RuntimeException("Unexpected Checked Exception.", e);
}
}
關於自定義註解:
(1)@Target:說明了Annotation所修飾的對象範圍,Annotation可以用於packages、types(類、接口、枚舉、Annotation類型),類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。在Annotation類型的聲明中使用了target可更加明晰其修飾的目標。取值(ElementType)有:
1.CONSTRUCTOR:用於描述構造器
2.FIELD:用於描述域
3.LOCAL_VARIABLE:用於描述局部變量
4.METHOD:用於描述方法
5.PACKAGE:用於描述包
6.PARAMETER:用於描述參數
7.TYPE:用於描述類、接口(包括註解類型) 或enum聲明
(2)@Retention定義了該Annotation被保留的時間長短:
某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;
編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因爲Annotation與class在使用上是被分離的)。
使用這個meta-Annotation可以對 Annotation的“生命週期”限制。
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在運行時有效(即運行時保留)
這個工具類,可以幫註解可以幫我我們定義導出的時候的字段名,標題,對齊方式,字段排序,反射的是哪個類,是導入還是導出。
二. Excel導出
我們在Excel導出的時候,基本思路就是,(1)獲取要導出的數據 (2)通過反射獲取對應的class的字段 (3)將導出的數據通過反射放到list (4)繪製Excel
導出工具類:
/**
* 導出Excel文件(導出“XLSX”格式,支持大數據量導出 @see org.apache.poi.ss.SpreadsheetVersion)
* @author ThinkGem
* @version 2013-04-21
*/
public class ExportExcel {
private static Logger log = LoggerFactory.getLogger(ExportExcel.class);
/**
* 工作薄對象
*/
private SXSSFWorkbook wb;
/**
* 工作表對象
*/
private Sheet sheet;
/**
* 樣式列表
*/
private Map<String, CellStyle> styles;
/**
* 當前行號
*/
private int rownum;
/**
* 註解列表(Object[]{ ExcelField, Field/Method })
*/
List<Object[]> annotationList = Lists.newArrayList();
/**
* 構造函數
* @param title 表格標題,傳“空值”,表示無標題
* @param cls 實體對象,通過annotation.ExportField獲取標題
*/
public ExportExcel(String title, Class<?> cls){
this(title, cls, 2);
}
/**
* 構造函數
* @param title 表格標題,傳“空值”,表示無標題
* @param cls 實體對象,通過annotation.ExportField獲取標題
* @param type 導出類型(1:導出數據;2:導出模板)
* @param groups 導入分組
*/
public ExportExcel(String title, Class<?> cls, int type, int... groups){
// Get annotation field
//getDeclaredFields:通過反射獲取對應class的全部字段,包括私有字段,但是不包括父類申明的字段
Field[] fs = cls.getDeclaredFields();
for (Field f : fs){
//獲取字段中帶有@ExcelField的標籤
ExcelField ef = f.getAnnotation(ExcelField.class);
//標籤不爲空並且導出類型是導入導出或者導出模板
if (ef != null && (ef.type()==0 || ef.type()==type)){
if (groups!=null && groups.length>0){
boolean inGroup = false;
for (int g : groups){
if (inGroup){
break;
}
for (int efg : ef.groups()){
if (g == efg){
inGroup = true;
annotationList.add(new Object[]{ef, f});
break;
}
}
}
}else{
annotationList.add(new Object[]{ef, f});
}
}
}
// Get annotation method
//獲取對應類中的全部方法,包括pulbic,protected,private.但是不包括繼承的方法,當然也包括他所實現接口的方法
Method[] ms = cls.getDeclaredMethods();
for (Method m : ms){
ExcelField ef = m.getAnnotation(ExcelField.class);
//字段:導入導出或者字段導出
if (ef != null && (ef.type()==0 || ef.type()==type)){
if (groups!=null && groups.length>0){
boolean inGroup = false;
for (int g : groups){
if (inGroup){
break;
}
for (int efg : ef.groups()){
if (g == efg){
inGroup = true;
annotationList.add(new Object[]{ef, m});
break;
}
}
}
}else{
annotationList.add(new Object[]{ef, m});
}
}
}
// Field sorting
Collections.sort(annotationList, new Comparator<Object[]>() {
public int compare(Object[] o1, Object[] o2) {
return new Integer(((ExcelField)o1[0]).sort()).compareTo(
new Integer(((ExcelField)o2[0]).sort()));
};
});
// Initialize
List<String> headerList = Lists.newArrayList();
for (Object[] os : annotationList){
String t = ((ExcelField)os[0]).title();
// 如果是導出,則去掉註釋
if (type==1){
String[] ss = StringUtils.split(t, "**", 2);
if (ss.length==2){
t = ss[0];
}
}
headerList.add(t);
}
initialize(title, headerList);
}
/**
* 構造函數
* @param title 表格標題,傳“空值”,表示無標題
* @param headers 表頭數組
*/
public ExportExcel(String title, String[] headers) {
initialize(title, Lists.newArrayList(headers));
}
/**
* 構造函數
* @param title 表格標題,傳“空值”,表示無標題
* @param headerList 表頭列表
*/
public ExportExcel(String title, List<String> headerList) {
initialize(title, headerList);
}
/**
* 初始化函數
* @param title 表格標題,傳“空值”,表示無標題
* @param headerList 表頭列表
*/
private void initialize(String title, List<String> headerList) {
//SXSSFWorkbook專門用來處理大數據寫入Excel2007的問題
this.wb = new SXSSFWorkbook(500);
this.sheet = wb.createSheet("Export");
this.styles = createStyles(wb);
// Create title
if (StringUtils.isNotBlank(title)){
Row titleRow = sheet.createRow(rownum++);
titleRow.setHeightInPoints(30);//設置行高
Cell titleCell = titleRow.createCell(0);
titleCell.setCellStyle(styles.get("title"));//將已經寫好的單元格屬性調用
titleCell.setCellValue(title);//設置單元格的值
//addMergedRegion:設置列寬,設置列寬的方法在HSSFSheet中,方法參數:1:第幾列,2:寬度
//單元格合併方法也是在HSSFSheet中,方法參數:一個CellRangeAddress,該類構造函數的4個參數分別表示爲:合併開始行,合併結束行,合併開始列,合併結束列
sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(),
titleRow.getRowNum(), titleRow.getRowNum(), headerList.size()-1));
}
// Create header
if (headerList == null){
throw new RuntimeException("headerList not null!");
}
Row headerRow = sheet.createRow(rownum++);
headerRow.setHeightInPoints(16);
for (int i = 0; i < headerList.size(); i++) {
Cell cell = headerRow.createCell(i);
cell.setCellStyle(styles.get("header"));
String[] ss = StringUtils.split(headerList.get(i), "**", 2);
if (ss.length==2){
cell.setCellValue(ss[0]);
Comment comment = this.sheet.createDrawingPatriarch().createCellComment(
new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6));
comment.setString(new XSSFRichTextString(ss[1]));
cell.setCellComment(comment);
}else{
cell.setCellValue(headerList.get(i));
}
sheet.autoSizeColumn(i);
}
for (int i = 0; i < headerList.size(); i++) {
int colWidth = sheet.getColumnWidth(i)*2;
sheet.setColumnWidth(i, colWidth < 3000 ? 3000 : colWidth);
}
log.debug("Initialize success.");
}
/**
* 創建表格樣式
* @param wb 工作薄對象
* @return 樣式列表
*/
private Map<String, CellStyle> createStyles(Workbook wb) {
Map<String, CellStyle> styles = new HashMap<String, CellStyle>();
CellStyle style = wb.createCellStyle();
style.setAlignment(CellStyle.ALIGN_CENTER);
style.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
Font titleFont = wb.createFont();
titleFont.setFontName("Arial");
titleFont.setFontHeightInPoints((short) 16);
titleFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
style.setFont(titleFont);
styles.put("title", style);
style = wb.createCellStyle();
style.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
style.setBorderRight(CellStyle.BORDER_THIN);
style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderLeft(CellStyle.BORDER_THIN);
style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderTop(CellStyle.BORDER_THIN);
style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setBorderBottom(CellStyle.BORDER_THIN);
style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex());
Font dataFont = wb.createFont();
dataFont.setFontName("Arial");
dataFont.setFontHeightInPoints((short) 10);
style.setFont(dataFont);
styles.put("data", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(CellStyle.ALIGN_LEFT);
styles.put("data1", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(CellStyle.ALIGN_CENTER);
styles.put("data2", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
style.setAlignment(CellStyle.ALIGN_RIGHT);
styles.put("data3", style);
style = wb.createCellStyle();
style.cloneStyleFrom(styles.get("data"));
// style.setWrapText(true);
style.setAlignment(CellStyle.ALIGN_CENTER);
style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
style.setFillPattern(CellStyle.SOLID_FOREGROUND);
Font headerFont = wb.createFont();
headerFont.setFontName("Arial");
headerFont.setFontHeightInPoints((short) 10);
headerFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
headerFont.setColor(IndexedColors.WHITE.getIndex());
style.setFont(headerFont);
styles.put("header", style);
return styles;
}
/**
* 添加一行
* @return 行對象
*/
public Row addRow(){
return sheet.createRow(rownum++);
}
/**
* 添加一個單元格
* @param row 添加的行
* @param column 添加列號
* @param val 添加值
* @return 單元格對象
*/
public Cell addCell(Row row, int column, Object val){
return this.addCell(row, column, val, 0, Class.class);
}
/**
* 添加一個單元格
* @param row 添加的行
* @param column 添加列號
* @param val 添加值
* @param align 對齊方式(1:靠左;2:居中;3:靠右)
* @return 單元格對象
*/
public Cell addCell(Row row, int column, Object val, int align, Class<?> fieldType){
Cell cell = row.createCell(column);
CellStyle style = styles.get("data"+(align>=1&&align<=3?align:""));
try {
if (val == null){
cell.setCellValue("");
} else if (val instanceof String) {
cell.setCellValue((String) val);
} else if (val instanceof Integer) {
cell.setCellValue((Integer) val);
} else if (val instanceof Long) {
cell.setCellValue((Long) val);
} else if (val instanceof Double) {
cell.setCellValue((Double) val);
} else if (val instanceof Float) {
cell.setCellValue((Float) val);
} else if (val instanceof Date) {
DataFormat format = wb.createDataFormat();
style.setDataFormat(format.getFormat("yyyy-MM-dd"));
cell.setCellValue((Date) val);
} else {
if (fieldType != Class.class){
cell.setCellValue((String)fieldType.getMethod("setValue", Object.class).invoke(null, val));
}else{
cell.setCellValue((String)Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(),
"fieldtype."+val.getClass().getSimpleName()+"Type")).getMethod("setValue", Object.class).invoke(null, val));
}
}
} catch (Exception ex) {
log.info("Set cell value ["+row.getRowNum()+","+column+"] error: " + ex.toString());
cell.setCellValue(val.toString());
}
cell.setCellStyle(style);
return cell;
}
/**
* 添加數據(通過annotation.ExportField添加數據)
* @return list 數據列表
*/
public <E> ExportExcel setDataList(List<E> list){
for (E e : list){
int colunm = 0;
Row row = this.addRow();
StringBuilder sb = new StringBuilder();
for (Object[] os : annotationList){
ExcelField ef = (ExcelField)os[0];
Object val = null;
// Get entity value
try{
if (StringUtils.isNotBlank(ef.value())){
val = Reflections.invokeGetter(e, ef.value());
}else{
if (os[1] instanceof Field){
val = Reflections.invokeGetter(e, ((Field)os[1]).getName());
}else if (os[1] instanceof Method){
val = Reflections.invokeMethod(e, ((Method)os[1]).getName(), new Class[] {}, new Object[] {});
}
}
// If is dict, get dict label
if (StringUtils.isNotBlank(ef.dictType())){
val = DictUtils.getDictLabel(val==null?"":val.toString(), ef.dictType(), "");
}
}catch(Exception ex) {
// Failure to ignore
log.info(ex.toString());
val = "";
}
this.addCell(row, colunm++, val, ef.align(), ef.fieldType());
sb.append(val + ", ");
}
//log.debug("Write success: ["+row.getRowNum()+"] "+sb.toString());
}
return this;
}
public ExportExcel setDataListList(List<List<String>> listList){
for (List<String> list : listList) {
int colunm = 0;
Row row = this.addRow();
StringBuilder sb = new StringBuilder();
for (String val : list) {
this.addCell(row, colunm++, val, 2, "".getClass());
sb.append(val + ", ");
}
}
return this;
}
/**
* 輸出數據流
* @param os 輸出數據流
*/
public ExportExcel write(OutputStream os) throws IOException{
wb.write(os);
return this;
}
/**
* 輸出到客戶端
* @param fileName 輸出文件名
*/
public ExportExcel write(HttpServletResponse response, String fileName) throws IOException{
response.reset();
response.setContentType("application/octet-stream; charset=utf-8");
response.setHeader("Content-Disposition", "attachment; filename="+Encodes.urlEncode(fileName));
write(response.getOutputStream());
return this;
}
/**
* 輸出到文件
* @param fileName 輸出文件名
*/
public ExportExcel writeFile(String name) throws FileNotFoundException, IOException{
FileOutputStream os = new FileOutputStream(name);
this.write(os);
return this;
}
/**
* 清理臨時文件
*/
public ExportExcel dispose(){
wb.dispose();
return this;
}
// /**
// * 導出測試
// */
public static void main(String[] args) throws Throwable {
String[] titles = { "商品名", "商品單價", "商品單位" };
List<String> headerList = Lists.newArrayList();
for (int i = 0; i <= titles.length-1; i++) {
headerList.add(titles[i]);
}
List<String> dataRowList = Lists.newArrayList();
for (int i = 1; i <= headerList.size(); i++) {
dataRowList.add("數據"+i);
}
List<List<String>> dataList = Lists.newArrayList();
for (int i = 1; i <=100; i++) {
dataList.add(dataRowList);
}
ExportExcel ee = new ExportExcel("表格標題", headerList);
for (int i = 0; i < dataList.size(); i++) {
Row row = ee.addRow();
for (int j = 0; j < dataList.get(i).size(); j++) {
ee.addCell(row, j, dataList.get(i).get(j));
}
}
ee.writeFile("C:/target/export.xlsx");
ee.dispose();
log.debug("Export success.");
}
}
導出實體類Vo:將要導出的實體類的字段的get方法上加上我們的自定義註解,申明導出的title,導出順序,是否字典,對齊方式:
public class Certificate {
private static final long serialVersionUID = -2627501790384505697L;
/**
* 資產唯一編碼
*/
@LogFiledName(value="資產唯一編碼")
private String assertUniqueCode;
/**
* 版本類型
*/
@LogFiledName(value="版本類型")
private String versionType;
/**
* 期限(字典:永久/租賃)
*/
@LogFiledName(value="期限(字典:永久/租賃)",dic="certificate_term")
private String term;
public Certificate() {
super();
}
public Certificate(String id){
this();
this.id = id;
}
@ExcelField(title="資產唯一編碼", align=2, sort=1)
public String getAssertUniqueCode() {
return assertUniqueCode;
}
public void setAssertUniqueCode(String assertUniqueCode) {
this.assertUniqueCode = assertUniqueCode;
}
@ExcelField(title="版本類型", align=2, sort=4)
public String getVersionType() {
return versionType;
}
public void setVersionType(String versionType) {
this.versionType = versionType;
}
@ExcelField(title="期限", align=2, sort=5, dictType="certificate_term")
public String getTerm() {
return term;
}
public void setTerm(String term) {
this.term = term;
}
}
對上面的代碼簡單分析:
(1)當我們獲取到全部的數據,調用如下:
new ExportExcel("軟資產信息", Certificate.class).setDataList(certificateListAll).write(response, fileName).dispose();
調用ExportExcel的構造函數,參數1:標題,參數2:實體對象。(2)通過反射獲取class的全部字段,包括私有字段,但是不包括父類申明的字段:
Field[] fs = cls.getDeclaredFields();
(3)遍歷全部字段,取出帶有@ExcelFiel標籤的字段,這樣我們就可以得到都導出那些字段了:
ExcelField ef = f.getAnnotation(ExcelField.class);
(4)遍歷我們取出的標籤
(5)遍歷類中全部方法,包括public,protected,private。但是不包括繼承的方法,當然也包括他所實現接口的方法:
Method[] ms = cls.getDeclaredMethods();
同樣,遍歷所以帶有註解的方法
(6)將我們遍歷篩選出來的List進行排序
(7)調用初始化函數,繪製Sheet
private void initialize(String title, List<String> headerList)
先創建SXSSFWorkbook,在創建Sheet,將Sheet使用統一的樣式:this.styles = createStyles(wb);
設置標題是第幾行,行高,創建單元格,合併單元格。
(8)添加數據:public <E> ExportExcel setDataList(List<E> list)
遍歷我們篩選出的數據,設置第幾行,如果是字段,返回字典對應的label。添加單元格:
public Cell addCell(Row row, int column, Object val, int align, Class<?> fieldType)
創建單元格:Cell cell = row.createCell(column);
判斷導出的值的類型,並設置到單元格中.
(9)最後以文件的方式輸出到客戶端:設置文件的格式,文件名
public ExportExcel write(HttpServletResponse response, String fileName)