1. 程式人生 > >Jeesite-匯入匯出原始碼跟蹤分析(匯出)

Jeesite-匯入匯出原始碼跟蹤分析(匯出)

使用Jeesite開發的時候,我們都少不了Excel匯入匯出的功能。這部分需要我我們掌握基本的POI,反射,當然在我們的框架中還定義了註解,也樣在程式碼上整潔許多,下面我們先看一下:

一. 匯入匯出的公共工具:

/**
 * Copyright &copy; 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 &copy; 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驅動包不需要這步了) 建立