เวลาทำงานบน Netbeans ไม่พบปัญหาอะไร แต่ตอน compile เป็น Fat-Jar กลับเกิดเรื่อง
เวลาทำงานแล้วเจอ Error
Your InputStream was neither an OLE2 stream, nor an OOXML stream or you haven’t provide the poi-ooxml*.jar in the classpath/modulepath – FileMagic: OLE2, having providers: [org.apache.poi.xssf.usermodel.XSSFWorkbookFactory@6239637c]
ที่เป็นอย่างนี้ เพราะมีการใช้ WorkbookFactory
แต่ WorkbookFactory มันก็น่าใช้ เพราะมันช่วยจัดการไฟล์ทั้ง XLS และ XLSX ได้โดยเราไม่ต้องเขียน code 2 รอบ
OLE2: คือรูปแบบของไฟล์ Microsoft Office รุ่นเก่า (Excel 97-2003) ซึ่งก็คือไฟล์นามสกุล .xls
OOXML: คือรูปแบบของไฟล์ Microsoft Office รุ่นใหม่ๆ (Excel 2007 ขึ้นไป) ซึ่งก็คือไฟล์นามสกุล .xlsx
Apache POI แยก library สำหรับการอ่านไฟล์ .xls และ .xlsx ออกจากกัน
ในการอ่านไฟล์ .xlsx (OOXML) ต้องใช้ poi-ooxml-*.jar
ในการอ่านไฟล์ .xls (OLE2) ต้องใช้ poi-*.jar
ข้อความ FileMagic: OLE2 ที่แสดงขึ้นมาเป็นตัวบ่งชี้ที่สำคัญว่า Apache POI ตรวจพบว่าไฟล์นี้น่าจะเป็นไฟล์ Excel .xls (รุ่นเก่า) แต่ไม่สามารถอ่านโครงสร้างภายในไฟล์ได้ถูกต้อง จึงเกิด error ขึ้น
แต่ตอน deploy เป็น Fat-Jar ดันเกิดปัญหา ซึ่ง AI วิเคราะห์ว่าน่าจะเป็นเพราะการทำ package มันเจอ services ที่ซ้ำซ้อนกัน แล้วมันเลือกมาแค่อันเดียว
ก่อนอื่น ยังงัยก็ต้องตรวจสอบว่า ไฟล์ Apache POI / OOXML ถูกรวมไว้ใน Fat-Jar แล้วหรือยัง
นอกจากนั้นก็ลองตรวจสอบว่าไฟล์ XML มีปัญหาหรือไม่
ทำงานไม่ได้ทั้ง XLS และ XLSX หรือเปล่า
## สาเหตุที่แท้จริง: การรวมไฟล์ META-INF/services ทับกัน
อธิบายง่ายๆ คือ:
- ทั้ง
poi.jar(สำหรับ .xls) และpoi-ooxml.jar(สำหรับ .xlsx) ต่างก็มีไฟล์พิเศษที่ใช้สำหรับ “ลงทะเบียน” ตัวเองกับWorkbookFactoryไฟล์นี้จะอยู่ในMETA-INF/services/ - เมื่อ Gradle Shadow Plugin สร้าง Fat JAR มันจะเจอฟაイルชื่อเดียวกันจาก 2 ที่นี้
- โดยปกติแล้ว มันจะเลือกเอามาแค่ไฟล์เดียว และทิ้งอีกไฟล์ไป!
ผลก็คือ Fat JAR ของคุณมีคลาสสำหรับ .xls และ .xlsx ครบถ้วน แต่ไฟล์ทะเบียน (Service file) มีแค่ของ .xlsx อันเดียว ทำให้ WorkbookFactory รู้จักแต่วิธีเปิด .xlsx เท่านั้น ซึ่งตรงกับ Error ที่ฟ้องว่า having providers: [XSSF...] เป๊ะๆ เลยครับ
## วิธีแก้ไขใน Gradle (build.gradle)
เราต้องสั่งให้ Shadow Plugin “รวม” (Merge) เนื้อหาของไฟล์ทะเบียนนี้เข้าด้วยกัน แทนที่จะเลือกมาแค่อันเดียว
ให้คุณเพิ่มการตั้งค่าสำหรับ shadowJar task ในไฟล์ build.gradle ของคุณดังนี้ครับ
// ... ส่วน plugins และ dependencies ของคุณ ...
// เพิ่ม task นี้เข้าไป หรือแก้ไขของเดิมถ้ามีอยู่แล้ว
shadowJar {
// คำสั่งนี้คือพระเอกของงานครับ
mergeServiceFiles()
// ถ้าคุณมีการตั้งค่าอื่นๆ ก็ใส่ไว้ตามเดิม
archiveBaseName = 'polar_gce'
archiveClassifier = 'all'
archiveVersion = '1.0'
}
// ถ้าคุณใช้ application plugin ให้แน่ใจว่าได้กำหนด main class ไว้
application {
mainClass = 'com.ncs2polar.app.SpeedStackTableFrame'
}
สิ่งที่ต้องทำ:
- นำ
shadowJar { mergeServiceFiles() }ไปใส่ในbuild.gradle - รันคำสั่ง build ใหม่อีกครั้ง:
./gradlew clean shadowJar - นำไฟล์ JAR ที่ได้จาก
build/libsไปทดสอบอีกครั้ง
การ MergeServiceFiles ช่วยแก้ปัญหาได้จริง
แต่ AI ยังแนะนำวิธีที่ 2 ให้ ซึ่งก็น่าจะช่วยแหล่ะ แต่มันดูไม่สะดวก คือ การไม่ใช้ WorkbookFactory
## ถ้ายังไม่ได้: ลองสร้าง Workbook โดยตรง (วิธีสำรอง)
หากวิธีข้างบนยังไม่ได้ผล (ซึ่งไม่น่าเป็นไปได้) ให้ลองใช้วิธีสุดท้ายคือเลิกใช้ WorkbookFactory แล้วเขียนโค้ดเพื่อสร้าง Workbook เองโดยตรง วิธีนี้เป็นการแก้ปัญหาที่ปลายเหตุ แต่ทำให้โปรแกรมทำงานได้แน่นอน
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; // for .xls
import org.apache.poi.xssf.usermodel.XSSFWorkbook; // for .xlsx
import java.io.FileInputStream;
import java.io.InputStream;
// ...
String filePath = "path/to/your/file.xls";
Workbook workbook = null;
InputStream inputStream = new FileInputStream(filePath);
try {
if (filePath.toLowerCase().endsWith(".xls")) {
// สร้าง Workbook สำหรับ .xls โดยตรง
workbook = new HSSFWorkbook(inputStream);
} else if (filePath.toLowerCase().endsWith(".xlsx")) {
// สร้าง Workbook สำหรับ .xlsx โดยตรง
workbook = new XSSFWorkbook(inputStream);
} else {
throw new IllegalArgumentException("Invalid file extension. Only .xls and .xlsx are supported.");
}
// ... ทำงานกับ workbook ต่อไป ...
} finally {
if (workbook != null) {
workbook.close();
}
inputStream.close();
}

