JasperReports 7.0.x ได้รื้อถอนสถาปัตยกรรม Monolithic JAR เดิมทั้งหมด เพื่อตอบสนองต่อข้อกำหนด Java 17/21 LTS และมาตรฐาน Jakarta EE โดยแตกโมดูลแยกเป็นอิสระ (Decoupled Engine):
iText 2.1.7 (ติด Security CVE และ LGPL), และ javax.servlet เมื่อรันบน Spring Boot 3+ จะเกิด Classpath Conflict และ Metaspace Pollution ทันทีjasperreports-core, jasperreports-pdf (OpenPDF/PDFBox สะอาดปราศจากข้อจำกัด License), jasperreports-excel (Decoupled POI), และ jasperreports-jakarta สำหรับ Web Integration
.jrxml): เอกสารนิยามโครงสร้างและนิพจน์ (Expressions) ในรูป XML Schema 7.0.xJasperCompileManager): แปลง XML Template และ Expressions ภาษา Java ให้อยู่ในรูป Executable Bytecode (JasperReport / .jasper)JasperFillManager): ฉีดข้อมูลจริงจาก Data Source และ Parameters เข้าไปใน Template เพื่อคำนวณและวาด Layout ทีละหน้า ได้ผลลัพธ์เป็น JasperPrintJRAbstractExporter): สังเคราะห์ Abstract Page Model ให้ออกมาเป็น Byte Stream ของไฟล์มาตรฐานจริง เช่น PDF หรือ Excelเมื่อมีการสั่งคอมไพล์ผ่าน JasperCompileManager.compileReport(...) Engine จะมีกระบวนการทำงานระดับ Low-level ดังนี้:
JRXmlDigester อ่านและสกัด Java Expressions ทั้งหมดinvoice_1715000000_12345.java) บรรจุ Logic การคำนวณลงใน Method evaluate().class)JasperReport ที่พร้อมนำไป FillcompileReport() ใน Controller ทุกคำขอ เมื่อมีโหลดสูง JVM จะต้องรัน Java Compiler ตลอดเวลาทำให้ CPU พุ่ง 100% และ Custom ClassLoader จะโหลดคลาสใหม่เข้า Metaspace อย่างต่อเนื่องจน Garbage Collector กวาดทิ้งไม่ทัน ส่งผลให้ระบบล่มด้วย OutOfMemoryError: Metaspace
.jrxml เป็น .jasper ตั้งแต่ขั้นตอน CI/CD Build แล้วโหลดผ่าน JRLoader.loadObject()
datasource.next()$F{}, $P{} และสะสมตัวแปร $V{}JRPrintPage) ทันทีJRPrintText, JRPrintLine) ยัดลงใน Page ModelJasperPrint เป็น In-Memory Visual Tree ขนาดมหึมา หากมีหลายพันหน้า Element Nodes นับล้านชิ้นจะอยู่บน Heap พร้อมกันจนเกิด OutOfMemoryError: Java heap space
// Production-Grade Implementation (Spring Boot 3.x / Java 21)
@Configuration
public class JasperReportConfig {
@Bean
public JasperReport invoiceReport() throws JRException, IOException {
// แนะนำที่สุด: โหลด .jasper ที่ Pre-compile แล้วจาก CI/CD
try (InputStream is = new ClassPathResource("reports/invoice.jasper").getInputStream()) {
return (JasperReport) JRLoader.loadObject(is);
}
}
}
@Service
public class InvoiceReportingService {
private final JasperReport invoiceReport;
public InvoiceReportingService(JasperReport invoiceReport) { this.invoiceReport = invoiceReport; }
public void streamInvoicePdf(InvoiceData data, OutputStream outputStream) throws JRException {
Map<String, Object> parameters = new HashMap<>();
parameters.put("REPORT_LOCALE", Locale.of("th", "TH"));
JRDataSource dataSource = new JRBeanCollectionDataSource(data.getItems());
// Fill Stage: สร้าง JasperPrint แยก Context ต่อ Request Thread
JasperPrint jasperPrint = JasperFillManager.fillReport(invoiceReport, parameters, dataSource);
// Export Stage: สตรีมตรงสู่ OutputStream โดยไม่เก็บ Buffer ก้อนใหญ่ใน Heap
JRPdfExporter exporter = new JRPdfExporter();
exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(outputStream));
exporter.exportReport();
}
}
| Stage | Engine API | Input Object | Output Artifact | JVM Threat | Architectural Mitigation |
|---|---|---|---|---|---|
| Compile | JasperCompileManager |
.jrxml (XML Template) |
JasperReport / .jasper |
CPU 100%, Metaspace OOM | Pre-compile via CI/CD, Singleton Cache |
| Fill | JasperFillManager |
JasperReport + Data |
JasperPrint (Page Tree) |
Heap OOM (Bloat) | Virtualizers (Disk Paging), Thread Isolation |
| Export | JRAbstractExporter |
JasperPrint |
PDF / XLSX Binary Stream | Memory Buffer Spikes | Direct OutputStream Streaming |
คลิกเลือกคำตอบในแต่ละข้อ ระบบจะตรวจ สอบสิทธิ์ อัปเดตคะแนนทันที และกางเฉลยเชิงลึก: