Jeesite開發,Excel匯入匯出的功能
一. 匯入匯出的公共工具:
- /**
- * 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
- /**
- * 匯出欄位名(預設呼叫當前欄位的“get”方法,如指定匯出欄位為物件,請填寫“物件名.物件屬性”,例:“area.name”、“office.name”)
- */
- String value() default"";
- /**
- * 匯出欄位標題(需要新增批註請用“**”分隔,標題**批註,僅對匯出模板有效)
- */
- String title();
- /**
- * 欄位型別(0:匯出匯入;1:僅匯出;2:僅匯入)
- */
- int type() default0;
- /**
- * 匯出欄位對齊方式(0:自動;1:靠左;2:居中;3:靠右)
- *
- * 備註:Integer/Long型別設定居右對齊(align=3)
- */
- int align() default0;
- /**
- * 匯出欄位欄位排序(升序)
- */
- int sort() default0;
- /**
- * 如果是字典型別,請設定字典的type值
- */
- String dictType() default"";
- /**
- * 反射型別
- */
- Class<?> fieldType() default Class.class;
- /**
- * 欄位歸屬組(根據分組匯出匯入)
- */
- int[] groups() default {};
- }
反射工具類:
- /**
- * 反射工具類.
- * 提供呼叫getter/setter方法, 訪問私有變數, 呼叫私有方法, 獲取泛型型別Class, 被AOP過的真實類等工具函式.
- * @author calvin
- * @version 2013-01-15
- */
- @SuppressWarnings("rawtypes")
- publicclass Reflections {
- privatestaticfinal String SETTER_PREFIX = "set";
- privatestaticfinal String GETTER_PREFIX = "get";
- privatestaticfinal String CGLIB_CLASS_SEPARATOR = "$$";
- privatestatic Logger logger = LoggerFactory.getLogger(Reflections.class);
- /**
- * 呼叫Getter方法.
- * 支援多級,如:物件名.物件名.方法
- */
- publicstatic 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方法, 僅匹配方法名。
- * 支援多級,如:物件名.物件名.方法
- */
- publicstaticvoid 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函式.
- */
- publicstatic Object getFieldValue(final Object obj, final String fieldName) {
- Field field = getAccessibleField(obj, fieldName);
- if (field == null) {
- thrownew 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函式.
- */
- publicstaticvoid setFieldValue(final Object obj, final String fieldName, final Object value) {
- Field field = getAccessibleField(obj, fieldName);
- if (field == null) {
- thrownew 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後反覆呼叫.
- * 同時匹配方法名+引數型別,
- */
- publicstatic Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
- final Object[] args) {
- Method method = getAccessibleMethod(obj, methodName, parameterTypes);
- if (method == null) {
- thrownew 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後反覆呼叫.
- * 只匹配函式名,如果有多個同名函式呼叫第一個。
- */
- publicstatic Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) {
- Method method = getAccessibleMethodByName(obj, methodName);
- if (method == null) {
- thrownew IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
- }
- try {
- return method.invoke(obj, args);
- } catch (Exception e) {
- throw convertReflectionExceptionToUnchecked(e);
- }
- }
- /**
- * 迴圈向上轉型, 獲取物件的DeclaredField, 並強制設定為可訪問.
- *
- * 如向上轉型到Object仍無法找到, 返回null.
- */
- publicstatic 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
- }
- }
- returnnull;
- }
- /**
- * 迴圈向上轉型, 獲取物件的DeclaredMethod,並強制設定為可訪問.
- * 如向上轉型到Object仍無法找到, 返回null.
- * 匹配函式名+引數型別。
- *
- * 用於方法需要被多次呼叫的情況. 先使用本函式先取得Method,然後呼叫Method.invoke(Object obj, Object... args)
- */
- publicstatic 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
- }
- }
- returnnull;
- }
- /**
- * 迴圈向上轉型, 獲取物件的DeclaredMethod,並強制設定為可訪問.
- * 如向上轉型到Object仍無法找到, 返回null.
- * 只匹配函式名。
- *
- * 用於方法需要被多次呼叫的情況. 先使用本函式先取得Method,然後呼叫Method.invoke(Object obj, Object... args)
- */
- publicstatic 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;
- }
- }
- }
- returnnull;
- }
- /**
- * 改變private/protected的方法為public,儘量不呼叫實際改動的語句,避免JDK的SecurityManager抱怨。
- */
- publicstaticvoid makeAccessible(Method method) {
- if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
- && !method.isAccessible()) {
- method.setAccessible(true);
- }
- }
- /**
- * 改變private/protected的成員變數為public,儘量不呼叫實際改動的語句,避免JDK的SecurityManager抱怨。
- */
- publicstaticvoid 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")
- publicstatic <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
- */
- publicstatic Class getClassGenricType(final Class clazz, finalint 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];
- }
- publicstatic 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.
- */
- publicstatic RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
- if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
- || e instanceof NoSuchMethodException) {
- returnnew IllegalArgumentException(e);
- } elseif (e instanceof InvocationTargetException) {
- returnnew RuntimeException(((InvocationTargetException) e).getTargetException());
- } elseif (e instanceof RuntimeException) {
- return (RuntimeException) e;
- }
- returnnew 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
- */
- publicclass ExportExcel {
- privatestatic Logger log = LoggerFactory.getLogger(ExportExcel.class);
- /**
- * 工作薄物件
- */
- private SXSSFWorkbook wb;
- /**
- * 工作表物件
- */
- private Sheet sheet;
- /**
- * 樣式列表
- */
- private Map<String, CellStyle> styles;
- /**
- * 當前行號
- */
- privateint 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[]>() {
- publicint compare(Object[] o1, Object[] o2) {
- returnnew 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 表頭列表
- */
- privatevoid 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){
- thrownew 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(Indexed