首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Servlet 動態圖表生成實戰:高效能圖形輸出技巧

     編輯:Java 相關知識

前言:為什麼不直接用 Applet?

早期 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

完整 Servlet 程式碼範例

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);
        }
    }
}

關鍵技術點解析

1. 使用 ImageIO 取代舊版 JPEGCodec

原文使用 `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);
}

2. 字型與中文顯示處理

伺服器環境常缺乏中文字型,導致輸出圖片出現「豆腐框」。解決方案:

  • 在容器/伺服器安裝開源中文字型(如 Noto Sans CJK、文泉驛微米黑)。
  • 或將 `.ttf` 字型檔打包在專案中,透過 `Font.createFont()` 動態載入。

3. 高併發下的效能優化

  • 物件池化:重複使用 `BufferedImage`、`Graphics2D` 物件(需注意執行緒安全)。
  • 非同步生成:使用 `@Asynchronous` 或 `CompletableFuture` 將繪圖任務移出請求執行緒。
  • 快取策略:相同參數的圖表在 TTL 內直接回傳快取檔案,避免重複運算。

4. 部署容器化注意事項

Docker 環境下需確保基底映像檔包含字型套件:

# Dockerfile 片段
FROM openjdk:17-jdk-slim
RUN apt-get update && apt-get install -y fontconfig fonts-noto-cjk && fc-cache -fv

進階擴充:整合圖表函式庫

若需支援複雜圖表(折線圖、雷達圖、熱力圖等),建議整合成熟函式庫:

函式庫特色授權
JFreeChart功能最完整、支援輸出 SVG/PDFLGPL
XChartAPI 簡潔、無相依、適合輕量專案Apache 2.0
Apache ECharts (Java 封裝)前端渲染、互動豐富、視覺效果佳Apache 2.0

總結

透過 Servlet 後台生成靜態圖片,結合排程器定時更新或事件觸發重繪,能大幅降低前台載入延遲與伺服器 CPU 壓力。搭配現代 `ImageIO` 標準 API、字型配置與快取策略,即可構建穩定、高效能的圖表服務。若專案需求進階互動,可考慮前端圖表庫(ECharts、Chart.js)配合 RESTful API 回傳 JSON 資料,實現前後端分離架構。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved