不想用POI?幾行代碼完成Excel導出導入

Octopus

Octopus 是一個簡單的java excel導入導出工具。目的是不用接觸Apache POI的API就能夠完成簡單的Excel導出導入。
同時,能夠自定義表格樣式,導入檢驗數據和轉換數據java

Github地址ios

不BB,直接上圖git

從Maven導入

<dependency>
    <groupId>cn.chenhuanming</groupId>
    <artifactId>octopus</artifactId>
    <version>1.1.4</version>
</dependency>

導出Excel

從最簡單的例子開始

咱們從最簡單的例子開始,導出一些地址數據github

定義Address正則表達式

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
    private String city;
    private String detail;
}

用XML文件定義怎麼導出ide

<?xml version="1.0" encoding="UTF-8"?>
<Root class="cn.chenhuanming.octopus.entity.Address">

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

</Root>

Root標籤的class屬性,表明咱們要導出的類全限定名工具

一個Field標籤表明Excel裏的一列數據單元測試

name屬性值就是Address裏的屬性名,實際上Octopus調用其getter方法獲取值,因此要確保有getter方法測試

description屬性會被用來繪製表頭字體

咱們能夠開始作最後一件事,編寫Java代碼

public class AddressExample {
    List<Address> addresses;

    /**
     * preparing 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 Exception {

        //導出文件的is
        String rootPath = this.getClass().getClassLoader().getResource("").getPath();
        FileOutputStream os = new FileOutputStream(rootPath + "/address.xlsx");

        //從XML讀取配置,建議單例模式
        InputStream is = this.getClass().getClassLoader().getResourceAsStream("address.xml");
        Config config = new XmlConfigFactory(is).getConfig();

        //用Octopus,只須要一行代碼
        Octopus.writeOneSheet(os, config, "address", addresses);
    }
}

這是一個完整的單元測試,能夠在單測測試路徑找到它

自動繪製表頭

Octopus支持導出複雜對象時自動繪製表頭

此次咱們來導出一些公司數據,這裏是Company

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Company {
    private String name;
    private Address address;
}

而後咱們建立一個 company.xml 配置文件

<Root 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;

    /**
     * preparing 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 Exception {

        //導出文件的is
        String rootPath = this.getClass().getClassLoader().getResource("").getPath();
        FileOutputStream os = new FileOutputStream(rootPath + "/company.xlsx");

        //從XML讀取配置,建議單例模式
        InputStream is = this.getClass().getClassLoader().getResourceAsStream("company.xml");
        Config config = new XmlConfigFactory(is).getConfig();

        Octopus.writeOneSheet(os, config, "company", companies);
    }
}

最後是導出的Excel文件

Octopus能夠處理更復雜的數據,你能夠在cn.chenhuanming.octopus.example.ApplicantExample查看這個更復雜的例子

轉換數據

有時你想轉換導出的數據。例如,在上一個例子中,咱們不想導出整個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"/>

最後導出的結果

導入Excel

咱們直接拿上一個例子的導出結果來演示導入,共用同一個Config對象,直接編寫導入的代碼

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

for (Company company : importData) {
    System.out.println(company);
}

在控制檯能夠看到打印導入結果,能夠看到,以前的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"
      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,能夠看到裏面有非法數據

看一下怎麼編寫Java代碼

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

    Config config = new XmlConfigFactory(this.getClass().getClassLoader().getResourceAsStream("company3.xml")).getConfig();

    final SheetReader<CheckedData<Company>> sheetReader = Octopus.readFirstSheetWithValidation(is,config,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的魅力

註解

咱們推薦使用 xml 方式配置導入導出格式,由於 xml 配置與類不相耦合,相比註解更加靈活。
不過有時使用方可能不太在乎靈活性,但願把配置和數據類放在一塊兒,那麼可使用註解版本。
註解與 xml 文件的使用方法相似,主要有 @Sheet,@Formatter,@Header,@Field 這幾個。

  • @Sheet 註解在數據類上,可選 formatters 屬性, 表示全局轉換器
  • @Formatter 做爲 @Sheet 的 formatters 屬性值,表示一個轉換器
  • @Header 註解在數據類的字段上,表示該字段是一個複合字段
  • @Field 註解在數據類的字段上,表示該字段是一個單一字段

註解的的屬性取值請參考 xml 文件。下面是一個數據類的註解示例:

@Sheet(formatters = {
        @Formatter(target = BigDecimal.class, format = BigDecimalFormatter.class),
})
public class Applicants {
    @Field(description = "Value", color = "#74f441")
    private int id;
    @Field(description = "Name", fontSize = 20, border = "0,2,0,2", borderColor = ",#4242f4,,#4242f4")
    private String name;
    @Header(description = "Job", headerColor = "#4286f4")
    private Job job;
    @Field(description = "Entry Date", dateFormat = "yyyy-MM-dd")
    private Date entryDate;
    @Field(description = "Working/Leaved", options = "Working|Leaved",
            formatter = cn.chenhuanming.octopus.formatter.WorkingFormatter.class, color = "#42f4b9")
    private boolean working = true;
}

使用方法:

// 構造方法必須傳入一個帶有 @Sheet 註解的類
    Config config = new AnnotationConfigFactory(Applicants.class).getConfig();
    // ... 使用 config 就像 xml 的方式同樣

Q&A

須要操做Apache POI?

Octopus類能夠提供一行代碼式的API,讓你不用碰Apache POI的API。可是若是你確實須要用到Apache POI,能夠先看一下Octopus核心類SheetWriterSheetReader代碼。我在設計的時候儘可能考慮擴展,而且徹底基於接口實現,實在不行能夠選擇繼承重寫,屬性基本都是protected,或者直接本身實現接口

有建議或者問題?

提Issue或者email我chenhuanming.cn@gmail.com

相關文章
相關標籤/搜索