Level 3: Application Integration • Chapter 3.4 Scriptlets & Lifecycle Event Hooks
คำถาม: ในระบบที่คอนฟิก Font Extension และเปิดใช้งาน HarfBuzz Shaper ในบทที่ 3.3 สมบูรณ์แล้ว หากมีการคำนวณภาษีหรือสถิติเชิงซ้อนในแถว Detail Band โดยนักพัฒนาเขียน Business Logic แฝงใน Expression `$F{amount}.multiply(new BigDecimal("0.07")).add(...)` ร่วมกับ Method Calls ภายนอกซ้ำๆ 500,000 แถว จะก่อให้เกิดผลกระทบใดต่อ JVM และเหตุใดจึงควรย้าย Logic ไปยัง JRDefaultScriptlet หรือ Pre-calculation Pipeline?
The "Why" & Real-world Problem: The Side-Effect Spaghetti & Memory Leak
ในงานรายงานระดับสถาบันการเงิน มักมีความจำเป็นต้องคำนวณค่าสะสมข้ามกลุ่ม (Cross-group Aggregations), บันทึก Transaction Audit Trail, หรือแปลงตัวเลขเป็นตัวอักษรทางกฎหมาย (Legal Currency Text / BahtText) ข้อผิดพลาดร้ายแรงที่สุดคือการเรียกใช้ Static Helper Class ที่มี Side-Effects แฝง หรือการเชื่อมต่อฐานข้อมูลภายนอก (External I/O) ภายใน Report Expression ส่งผลให้:
Scriptlet สืบทอดมาจาก JRDefaultScriptlet ทำหน้าที่เป็น Observer Hook ที่ผูกติดกับ Filler State Machine ได้แก่ beforeReportInit, beforeGroupInit, beforeDetailEval, afterDetailEval, และ afterReportInit
สามารถอ่าน Field/Parameter และเขียนผลลัพธ์กลับสู่ Report Variable Registry ผ่านเมธอด setVariableValue("VAR_NAME", value) ทำให้ Template คงความสะอาด ไร้ Logic รกใน XML
JasperReports Engine จะสร้าง Instance ใหม่ของ Custom Scriptlet ต่อ 1 การเรียกใช้งาน JasperFillManager.fillReport() เสมอ ไม่มีการแชร์ Instance ข้ามเธรด รับประกัน Thread-safety
สถาปัตยกรรมระดับ Enterprise กำหนดให้ Scriptlet เป็น In-Memory Pure Computational Engine เท่านั้น ห้ามทำ Network I/O, DB Connection, หรือ Disk Writes เด็ดขาด
| Failure Pattern | Engine & JVM Mechanics | Architectural Defense (Standard 7.0.x) |
|---|---|---|
| Double Counting on Band Overflow | เมื่อ Band เกิดการตัดหน้า (Split) Engine อาจประเมิน Expression ซ้ำในหน้าถัดไป ทำให้ตัวแปรนับยอดสะสมเบิ้ล 2 ครั้ง | ใช้ Hook afterDetailEval() หรือควบคุม Flag ผ่าน Scriptlet เพื่อป้องกันการนับซ้ำเมื่อแถวเดิมถูกประเมินใหม่ |
| External Network Blocking | การยิง HTTP/REST API ใน Scriptlet เพื่อแปลงสกุลเงิน ทำให้ Filler Thread บล็อกค้าง Latency พุ่งทะลุเพดาน | ย้าย External I/O ออกไปทำใน Spring Boot Pre-processing ก่อนส่งข้อมูลเข้า JRDataSource |
| ClassNotFoundException on Build/Fill | Scriptlet class ไม่ได้อยู่ใน Classpath ของ Compilation Plugin หรือ Runtime Classloader | จัดแพ็กเกจ Scriptlet เป็น JAR ใน Application Dependency และระบุ FQCN ในแอตทริบิวต์ scriptletClass |
| Memory Retention in Custom State | การเก็บสะสม Object หนักๆ ลงใน List/Map ภายใน Scriptlet ตลอดการ Fill 1 ล้านแถวทำให้ Heap เต็ม | ล้าง State ใน afterGroupInit() หรือใช้ Primitive Accumulators เท่านั้น |
สลับแท็บเพื่อศึกษาและทดสอบทั้ง 2 ส่วน: Lab 3.4.1 (Java 21 Custom Scriptlet Class) และ Lab 3.4.2 (Unified Element Model JRXML 7.0.x)
| TRANSACTION REF | ITEM DESCRIPTION | BASE AMOUNT | COMPUTED TAX (7%) |
|---|---|---|---|
| TXN-202609-001 | Enterprise Cloud Infrastructure Tier-4 | 1,200,000.00 | 84,000.00 |
| TXN-202609-002 | High-Speed Dedicated Fiber Backbone | 450,000.00 | 31,500.00 |
| TXN-202609-003 | Kubernetes Cluster Orchestration Node | 880,000.00 | 61,600.00 |
| TXN-202609-004 | Hardware Security Module (HSM) Vault | 2,100,000.00 | 147,000.00 |
ทดสอบความเข้าใจเชิงลึกเกี่ยวกับ Scriptlet Lifecycle Hooks, State Accumulation, Concurrency, Split Overflow, และ Clean Architecture
[Filler State Machine Start]
│
▼
beforeReportInit() ──────────► Initialize Run-wide Hash/Audit Context
│
┌───────┴───────────────────────┐
▼ (For Each Group) │
beforeGroupInit(groupName) │
│ │
├────────────────────────┐ │
▼ (For Each Record) │ │
beforeDetailEval() │ │
│ (Calculate Row Logic) │ │
▼ │ │
afterDetailEval() ──────────┴──────┘
│ (Update Variable Registry via setVariableValue)
▼
afterGroupInit(groupName)
│
▼
afterReportInit() ───────────► Finalize Legal Checksum & Signatures