Jeesite-匯入匯出原始碼跟蹤分析(匯出)
使用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)
相關推薦
Jeesite-匯入匯出原始碼跟蹤分析(匯出)
使用Jeesite開發的時候,我們都少不了Excel匯入匯出的功能。這部分需要我我們掌握基本的POI,反射,當然在我們的框架中還定義了註解,也樣在程式碼上整潔許多,下面我們先看一下: 一. 匯入匯出的公共工具: /** * Copyright © 20
前程無憂爬蟲原始碼及分析(一)
一、網頁分析 1.1 關鍵字頁面(url入口) 首先在前程無憂網站上檢索關鍵詞"大資料": &n
以太坊原始碼深入分析(3)-- 以太坊RPC通訊例項和原理程式碼分析(上)
上一節提到,以太坊在node start的時候啟動了RPC服務,以太坊通過Rpc服務來實現以太坊相關介面的遠端呼叫。這節我們用個例項來看看以太坊 RPC是如何工作的,以及以太坊RPC的原始碼的實現一,RPC通訊例項1,RPC啟動命令 :geth --rpcgo-ethereu
MFC原始碼實戰分析(三)——訊息對映原理與訊息路由機制初探
如果在看完上一篇文章後覺得有點暈,不要害怕。本節我們就不用這些巨集,而是用其中的內容重新完成開頭那個程式,進而探究MFC訊息對映的本來面目。 MFC訊息對映機制初探 還我本來面目 class CMyWnd : public CFrameWnd
Qemu-KVM虛擬機器初始化及建立過程原始碼簡要分析(一)
我們知道,Qemu-KVM實際上包括Qemu和KVM兩部分,那麼在建立以及初始化虛擬機器時,實際上也是在這兩部分進行的。 KVM實際上就是kvm核心模組,包括kvm.ko、kvm-intel.ko、kvm-amd.ko三部分,後兩部分分別對應Intel體系的
Qemu-KVM虛擬機器初始化及建立過程原始碼簡要分析(二)
前面我們講了KVM核心層建立及初始化虛擬機器的一些工作過程,現在講一下Qemu層的流程以及與KVM核心層的配合過程。 Qemu層是從vl.c中的main()函式開始的,這裡通過在程式碼中新增一些註釋的方式來進行講解,中間省略了很多不重要或者我也沒有搞
以太坊原始碼深入分析(2)-- go-ethereum 客戶端入口和Node分析
一,geth makefile 以及編譯邏輯上篇提到用 make geth 來編譯geth客戶端。我們來看看make file做了什麼: geth: build/env.sh go run build/ci.go install ./cmd/geth @echo
Ceph Monitor原始碼機制分析(一)—— 概述
0 前言 最近終於有點時間可以看看Ceph的程式碼了,接下來準備就Ceph monitor這個Ceph叢集中最重要的元件進行深入的分析。 1 Monitor的作用 Monitor在Ceph叢集中扮演著管理者的角色,維護了整個叢集的狀態(抽象成幾張map,包括osdmap、m
Atlas框架原始碼簡要分析(中)--Atlas中bundle的安裝和初始化
Atlas框架原始碼簡要分析(中)–Atlas中bundle的安裝和初始化 在上一篇中大致的看了下Atlas整體框架的初始化及啟動,下面我們以啟動一個沒有安裝的子Bundle中的Activity為切入點,來跟蹤一個Bundle是如何載入並啟動在這個Bun
以太坊原始碼深入分析(4)-- 以太坊RPC通訊例項和原理程式碼分析(下)
上一節我們試著寫了一個RPC的請求例項,通過分析原始碼知道了RPC服務的建立流程,以及Http RPC server建立過程,Http RPC Client的請求流程。這一節,先分析一下Http RPC server如何處理client的請求。然後再分析一下IPC RPC的處
OpenStack建立例項完整過程原始碼詳細分析(14)----依據AMQP通訊架構實現訊息接收機制解析之一
感謝朋友支援本部落格,歡迎共同探討交流,由於能力和時間有限,錯誤之處在所難免,歡迎指正! 如果轉載,請保留作者資訊。 部落格地址:http://blog.csdn.net/gaoxingnengjisuan 郵箱地址:[email protected] 這篇博文
以太坊原始碼深入分析(8)-- 以太坊核心BlockChain原始碼分析
前面幾節都在分析以太坊的通訊協議,怎麼廣播,怎麼同步,怎麼下載。這一節講講以太坊的核心模組BlockChain,也就是以太坊的區塊鏈。一,BlockChain的初始化Ethereum服務初始化的時候會呼叫core.SetupGenesisBlock來載入創始區塊。顧名思義,創
nova建立虛擬機器過程原始碼簡要分析(一)
nova部署虛擬機器原始碼呼叫過程簡要分析,關於novaclient的程式處理流程暫時還沒有分析。後期如果有時間會進一步分析novaclient的程式執行過程,以及客戶端和服務之間的http請求響應關係。 nova/api/openstack/compute/
Hadoop2異常分析(二):Sqoop匯出資料錯誤
sqoop錯誤: Error during import: No primary key could be found for table tab1. Please specify one with
Android ADB 原始碼分析(三)
前言 之前分析的兩篇文章 Android Adb 原始碼分析(一) 嵌入式Linux:Android root破解原理(二) 寫完之後,都沒有寫到相關的實現程式碼,這篇文章寫下ADB的通訊流程的一些細節 看這篇文章之前,請先閱讀 Linux的SOCKET
Mybatis 原始碼分析(2)—— 引數處理
Mybatis對引數的處理是值得推敲的,不然在使用的過程中對發生的一系列錯誤直接懵逼了。 以前遇到引數繫結相關的錯誤我就是直接給加@param註解,也稀裡糊塗地解決了,但是後來遇到了一些問題推翻了我的假設:單個引數不需要使用 @param 。由此產生了一個疑問,Mybatis到底是怎
Mybatis 原始碼分析(9)—— 事物管理
Mybatis 提供了事物的頂層介面: public interface Transaction { /** * Retrieve inner database connection * @return DataBase connection * @throw
Mybatis 原始碼分析(8)—— 一二級快取
一級快取 其實關於 Mybatis 的一級快取是比較抽象的,並沒有什麼特別的配置,都是在程式碼中體現出來的。 當呼叫 Configuration 的 newExecutor 方法來建立 executor: public Executor newExecutor(Transac
Mybatis原始碼分析(7)—— 結果集處理
解析封裝 ResultMap 是和結果集相關的東西,最初在解析 XML 的時候,於 parseStatementNode 方法中,針對每一個 select 節點進行解析,轉換為 MappedStatement(類似 Spring 的 bean 配置和 BeanDefinition 的
Mybatis原始碼分析(6)—— 從JDBC看Mybatis的設計
Java資料庫連線,(Java Database Connectivity,簡稱JDBC)是Java語言中用來規範客戶端程式如何來訪問資料庫的應用程式介面,提供了諸如查詢和更新資料庫中資料的方法。 六步流程: 載入驅動(5.x驅動包不需要這步了) 建立