不想用POI?幾行程式碼完成Excel匯出匯入

暮夜望日發表於2019-03-03

Octopus

Octopus(就是一個名字而已~) 是一個簡單的java excel匯入匯出工具。目的是不用接觸Apache POI的API就可以完成簡單的Excel匯出匯入。
同時,可以自定義表格樣式,匯入檢驗資料合法和轉換資料。

最大的特點就是匯出複雜結構物件時自動繪製表頭

不BB,直接上圖

不想用POI?幾行程式碼完成Excel匯出匯入

為了準確性,演示的例子都是完整的單元測試程式碼,都可以在測試路徑找到原始碼,各位客官可以clone下來跑一下看效果,包括上面這個複雜例子(ApplicantExample)喔

Github地址

從Maven匯入

為了方便使用,直接用Github做了一個私人倉庫

增加倉庫

	<repositories>
	    <repository>
	        <id>chenhuanming-repo</id>
	        <name>chenhuanming-repo</name>
	        <url>https://raw.githubusercontent.com/zerouwar/my-maven-repo/master</url>
	    </repository>
	</repositories>
複製程式碼

引入依賴

	<dependency>
			<groupId>cn.chenhuanming</groupId>
			<artifactId>octopus</artifactId>
			<version>1.0.0</version>
	</dependency>
複製程式碼

匯出Excel

從最簡單的例子開始

我們從最簡單的例子開始——匯出一些地址資料。Address類只有兩個屬性

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
    private String city;
    private String detail;
}
複製程式碼

在匯出前,我們需要建立一個XML檔案定義怎樣去匯出

<?xml version="1.0" encoding="UTF-8"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/zerouwar/my-maven-repo/master/cn/chenhuanming/octopus/1.0.0/octopus.xsd"
      class="cn.chenhuanming.octopus.entity.Address">

    <Field name="city" description="City"/>
    <Field name="detail" description="Detail"/>

</Root>
複製程式碼

首先,建立Root根元素。這裡引用octopus.xsd檔案幫助我們編寫XML

然後,賦值class屬性,代表我們要匯出的類全限定名

最後,建立兩個Field元素,代表要匯出類的兩個屬性

name屬性值就是Address裡的屬性名,實際上Octopus呼叫其getter方法獲取值,所以要確保有getter方法

description屬性會被用在繪製表頭

我們可以開始做最後一件事,編寫Java程式碼

public class AddressExample {
    List<Address> addresses;

    /**
     * make testing data
     */
    @Before
    public void prepare() {
        addresses = new ArrayList<>();
        DataFactory df = new DataFactory();
        for (int i = 0; i < df.getNumberBetween(5, 10); i++) {
            addresses.add(new Address(df.getCity(), df.getAddress()));
        }
    }

    @Test
    public void export() throws FileNotFoundException {

        //where to export
        String rootPath = this.getClass().getClassLoader().getResource("").getPath();
        FileOutputStream os = new FileOutputStream(rootPath + "/address.xlsx");

        //read config from address.xml
        InputStream is = this.getClass().getClassLoader().getResourceAsStream("address.xml");
        ConfigReader configReader = Octopus.getXMLConfigReader(is);

        try {
            Octopus.writeOneSheet(os, configReader, "address", addresses);
        } catch (IOException e) {
            System.out.println("export failed");
        }
    }
}
複製程式碼

這是一個完整的單元測試程式碼,不過匯出Excel其實只要兩步:

  1. 從XML配置檔案中建立一個ConfigReader物件
  2. 呼叫Octopus.writeOneSheet(),傳入匯出的檔案,configReader,工作表的名字和資料

下面是匯出的Excel檔案

不想用POI?幾行程式碼完成Excel匯出匯入

自動繪製表頭

Octopus支援匯出複雜物件時自動繪製表頭

這次我們來匯出一些公司資料,這裡是Company

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Company {
    private String name;
    private Address address;
}
複製程式碼

然後我們建立一個 company.xml 配置檔案

<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/zerouwar/my-maven-repo/master/cn/chenhuanming/octopus/1.0.0/octopus.xsd"
      class="cn.chenhuanming.octopus.entity.Address">


    <Field name="name"
           description="Name"
           color="#ff0000"/>

    <Header name="address" description="Address">
        <Field name="city" description="City"/>
        <Field name="detail" description="Detail"/>
    </Header>

</Root>
複製程式碼

我們用Header元素代表要匯出Company的一個複雜屬性,同時設定字型顏色是紅色

Java程式碼基本跟之前的一樣

public class CompanyExample {
    List<Company> companies;

    /**
     * make testing data
     */
    @Before
    public void prepare() {
        companies = new ArrayList<>();
        DataFactory df = new DataFactory();
        for (int i = 0; i < df.getNumberBetween(5, 10); i++) {
            companies.add(new Company(df.getBusinessName(), new Address(df.getCity(), df.getAddress())));
        }
    }

    @Test
    public void export() throws FileNotFoundException {

        //where to export
        String rootPath = this.getClass().getClassLoader().getResource("").getPath();
        FileOutputStream os = new FileOutputStream(rootPath + "/company.xlsx");

        //read config from company.xml
        InputStream is = this.getClass().getClassLoader().getResourceAsStream("company.xml");
        ConfigReader configReader = Octopus.getXMLConfigReader(is);

        try {
            Octopus.writeOneSheet(os, configReader, "company", companies);
        } catch (IOException e) {
            System.out.println("export failed");
        }
    }

}
複製程式碼

最後是匯出的Excel檔案

不想用POI?幾行程式碼完成Excel匯出匯入

Octopus可以處理更復雜的資料,你可以在cn.chenhuanming.octopus.example.ApplicantExample檢視這個更復雜的例子

不想用POI?幾行程式碼完成Excel匯出匯入

轉換資料

有時你想轉換匯出的資料。例如,在上一個例子中,我們不想匯出整個Address物件,把它當做一個字串匯出

我們所需要做的只是實現一個Formatter

public class AddressFormatter implements Formatter<Address> {
    @Override
    public String format(Address address) {
        return address.getCity() + "," + address.getDetail();
    }

    @Override
    public Address parse(String str) {
        String[] split = str.split(",");
        if (split.length != 2) {
            return null;
        }
        return new Address(split[0], split[1]);
    }
}
複製程式碼

parse方法用於匯入Excel時,只要關注format方法。這裡接受一個Address物件,返回一個字串。

最後,配置AddressFormatter到XML檔案

<Field name="name"
          description="Name"
          color="#ff0000"/>

<Field name="address"
      description="Address"
      formatter="cn.chenhuanming.octopus.formatter.AddressFormatter"/>
複製程式碼

最後匯出的結果

不想用POI?幾行程式碼完成Excel匯出匯入

匯入Excel

我們直接拿上一個例子的匯出結果來演示匯入,共用同一個ConfigReader,直接編寫匯入的程式碼

//First get the excel file
FileInputStream fis = new FileInputStream(rootPath + "/company2.xlsx");

try {
    SheetReader<Company> importData = Octopus.readFirstSheet(fis, configReader, new DefaultCellPosition(1, 0));

    for (Company company : importData) {
        System.out.println(company);
    }
} catch (Exception e) {
    System.out.println("import failed");
}
複製程式碼

在控制檯可以看到列印匯入結果,可以看到,之前的AddressFormatter也完成了資料的轉換工作

Company(name=Graham Motor Services, address=Address(city=Monroe, detail=666 Bonnair Ave))
Company(name=Social Circle Engineering, address=Address(city=Fort Gaines, detail=956 Third Ridge))
Company(name=Enigma Cafe, address=Address(city=Mcdonough, detail=1278 Midway Trail))
Company(name=Hapeville Studios, address=Address(city=Riceboro, detail=823 Tuscarawas Blvd))
Company(name=Thalman Gymnasium, address=Address(city=Ebenezer, detail=1225 Blackwood Avenue))
Company(name=Sparks Pro Services, address=Address(city=Darien, detail=1362 Woodlawn Lane))
Company(name=Toccoa Development, address=Address(city=Ridgeville, detail=1790 Lawn Ave))
複製程式碼

匯入校驗資料

有時候我們對匯入的資料有一定的要求,Octopus提供簡單的資料校驗配置

首先給我們的Company增加一個status屬性,只能是 good,badclosed 三個值其中一個,同時name不可以為空,看一下XML配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/zerouwar/my-maven-repo/master/cn/chenhuanming/octopus/1.0.0/octopus.xsd"
      class="cn.chenhuanming.octopus.entity.Company">


    <Field name="name"
           description="Name"
           color="#ff0000"
           is-blankable="false"/>

    <Field name="address"
           description="Address"
           formatter="cn.chenhuanming.octopus.formatter.AddressFormatter"
    />

    <Field name="status"
           description="Status"
           options="good|bad|closed"/>
    <!--| split options -->
    
</Root>
複製程式碼

這是我們要匯入的Excel,可以看到裡面有非法資料

不想用POI?幾行程式碼完成Excel匯出匯入

看一下怎麼編寫Java程式碼

@Test
public void importCheckedData() throws IOException, InvalidFormatException {
    InputStream is = this.getClass().getClassLoader().getResourceAsStream("wrongCompany.xlsx");

    ConfigReader configReader = new XmlConfigReader(this.getClass().getClassLoader().getResourceAsStream("company3.xml"));

    final SheetReader<CheckedData<Company>> sheetReader = Octopus.readFirstSheetWithValidation(is,configReader,new DefaultCellPosition(1,0));

    for (CheckedData<Company> checkedData : sheetReader) {
        System.out.println(checkedData);
    }
}
複製程式碼

這裡我們呼叫Octopus.readFirstSheetWithValidation,獲取帶校驗結果的SheetReader,看一下匯入的結果

CheckedData(data=Company(name=Graham Motor Services, address=Address(city=Monroe, detail=666 Bonnair Ave), status=good), exceptions=[])
CheckedData(data=Company(name=Social Circle Engineering, address=Address(city=Fort Gaines, detail=956 Third Ridge), status=null), exceptions=[cn.chenhuanming.octopus.exception.NotAllowValueException])
CheckedData(data=Company(name=null, address=Address(city=Mcdonough, detail=1278 Midway Trail), status=null), exceptions=[cn.chenhuanming.octopus.exception.CanNotBeBlankException, cn.chenhuanming.octopus.exception.NotAllowValueException])
複製程式碼

可以看到每一個CheckData有一個data屬性和一個exceptions列表。
這個異常列表存放著匯入時每一個單元格可能出現的校驗錯誤,異常型別都是ParseException

除了is-blankableoptions,還可以通過regex配置正規表示式檢查。當校驗錯誤時,會丟擲對應的ParseException子類

  • is-blankable:丟擲 CanNotBeBlankException
  • options:丟擲 NotAllowValueException
  • regex:丟擲 PatternNotMatchException

你可以通過這些異常來進行跟進一步的處理。如果上面三種校驗方式不能滿足需求,在Formatterparse丟擲自定義的ParseException。Octopus會捕獲它們放到exceptions列表中,並自動把單元格位置和你的配置內容塞到ParseException中,這樣你可以自己選擇處理這些異常資料

以上程式碼都可以在測試路徑cn.chenhuanming.octopus.example找到,通過這些例子可以感受下Octopus的魅力

Q&A

沒有Java註解配置?

目前只提供XML配置,因為XML和類檔案解耦,有時候你無法修改類程式碼時,尤其是匯出場景,XML會是更好的選擇。如果你是”anti-xml”,可以實現註解版ConfigReader,把註解配置轉換成Field,這應該不會很難。後面我有空再弄註解配置吧~

需要操作Apache POI?

Octopus類可以提供一行程式碼式的API,讓你不用碰Apache POI的API。但是如果你確實需要用到Apache POI,可以先看一下Octopus核心類SheetWriterSheetReader程式碼。我在設計的時候儘量考慮擴充套件,並且完全基於介面實現,實在不行可以選擇繼承重寫,屬性基本都是protected,或者直接自己實現介面

有建議或者想法?

email我chenhuanming.cn@gmail.com

相關文章