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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章