早期 Web 開發常使用 Applet 即時繪製圖表,但此方式有兩大效能瓶頸:資料庫查詢延遲與 Applet 端繪圖運算緩慢。隨著瀏覽器對 Java Applet 支援終止,現代架構傾向「後台定時生成靜態圖片,前台直接載入顯示」。本文示範如何在 Servlet 中使用 Java 2D API 繪製圖表並輸出 JPEG 檔案,達到極佳的回應速度與資源利用率。
graph LR
A[排程器/觸發器] --> B(Servlet 繪圖邏輯)
B --> C[Java 2D 繪圖]
C --> D[輸出 JPEG/PNG]
D --> E[(靜態檔案儲存)]
F[前台網頁] -->|img src| E
package com.example.chart;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
public class ChartGeneratorServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. 設定輸出路徑(實務建議放在 Web 可存取的靜態資源目錄)
String outputPath = getServletContext().getRealPath("/static/charts/finance_summary.jpg");
File outputFile = new File(outputPath);
outputFile.getParentFile().mkdirs(); // 確保目錄存在
try (FileOutputStream fos = new FileOutputStream(outputFile)) {
// 2. 建立離屏繪圖緩衝區
int width = 400, height = 300;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
// 3. 啟用抗鋸齒提升畫質
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// 4. 繪製背景
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, width, height);
// 5. 繪製標題區
g2d.setColor(Color.BLACK);
g2d.setFont(new Font("微軟正黑體", Font.BOLD, 16));
g2d.drawString("財務餘額摘要", 120, 30);
g2d.setFont(new Font("微軟正黑體", Font.PLAIN, 12));
g2d.drawString("主要帳戶", 160, 50);
// 6. 繪製圖例與資料長條(模擬資料)
drawLegendAndBars(g2d);
// 7. 輸出 JPEG(品質 0.9)
ImageIO.write(image, "JPEG", fos);
// 8. 回應前台(可直接回傳圖片或 JSON 含圖片 URL)
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write("{\"status\":\"ok\",\"imageUrl\":\"/static/charts/finance_summary.jpg\"}");
} catch (Exception e) {
log("圖表生成失敗", e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "圖表生成異常");
}
}
private void drawLegendAndBars(Graphics2D g) {
// 圖例資料:標籤、顏色、數值(百分比)
String[] labels = {"營運比 %", "定期比 %", "房貸比 %", "租賃比 %"};
Color[] colors = {Color.DARK_GRAY, Color.YELLOW.darker(), Color.LIGHT_GRAY, Color.GREEN};
int[] values = {65, 45, 30, 55}; // 模擬百分比
int barMaxWidth = 250;
int startX = 50;
int startY = 80;
int barHeight = 25;
int gap = 15;
for (int i = 0; i < labels.length; i++) {
int y = startY + i * (barHeight + gap);
// 繪製圖例色塊
g.setColor(colors[i]);
g.fillRect(startX, y, 15, 15);
g.setColor(Color.BLACK);
g.drawRect(startX, y, 15, 15);
// 繪製標籤
g.drawString(labels[i], startX + 25, y + 12);
// 繪製長條圖
int barWidth = (int) (barMaxWidth * values[i] / 100.0);
g.setColor(colors[i]);
g.fillRect(startX + 100, y, barWidth, barHeight);
g.setColor(Color.BLACK);
g.drawRect(startX + 100, y, barWidth, barHeight);
// 顯示數值
g.drawString(values[i] + "%", startX + 110 + barWidth, y + 17);
}
}
}
原文使用 `com.sun.image.codec.jpeg.JPEGCodec` 屬於 Sun 專有 API,已在 JDK 9+ 移除。現代標準做法使用 `javax.imageio.ImageIO`,支援 JPEG、PNG、GIF、BMP、WBMP 等格式,且可調整壓縮品質:
// JPEG 品質控制(0.0 ~ 1.0)
ImageWriter writer = ImageIO.getImageWritersByFormatName("JPEG").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(0.9f); // 高品質
try (ImageOutputStream ios = ImageIO.createImageOutputStream(fos)) {
writer.setOutput(ios);
writer.write(null, new IIOImage(image, null, null), param);
}
伺服器環境常缺乏中文字型,導致輸出圖片出現「豆腐框」。解決方案:
Docker 環境下需確保基底映像檔包含字型套件:
# Dockerfile 片段
FROM openjdk:17-jdk-slim
RUN apt-get update && apt-get install -y fontconfig fonts-noto-cjk && fc-cache -fv
若需支援複雜圖表(折線圖、雷達圖、熱力圖等),建議整合成熟函式庫:
| 函式庫 | 特色 | 授權 |
|---|---|---|
| JFreeChart | 功能最完整、支援輸出 SVG/PDF | LGPL |
| XChart | API 簡潔、無相依、適合輕量專案 | Apache 2.0 |
| Apache ECharts (Java 封裝) | 前端渲染、互動豐富、視覺效果佳 | Apache 2.0 |
透過 Servlet 後台生成靜態圖片,結合排程器定時更新或事件觸發重繪,能大幅降低前台載入延遲與伺服器 CPU 壓力。搭配現代 `ImageIO` 標準 API、字型配置與快取策略,即可構建穩定、高效能的圖表服務。若專案需求進階互動,可考慮前端圖表庫(ECharts、Chart.js)配合 RESTful API 回傳 JSON 資料,實現前後端分離架構。