GroupDocs Viewer Java 教程:將 Word 轉換為 HTML 並渲染帶有批註的文件
介紹
如果您需要 將 Word 轉換為 HTML 並保留每位審閱者的備註、評論或標註,您已來到正確的地方。許多 Java 開發人員在文件轉換時會失去原始檔案中寶貴的回饋。本教學將指導您使用 GroupDocs Viewer for Java 將 Word 轉換為 HTML,並渲染包括 Word、Excel、PowerPoint、PDF 等多種文件類型,且不遺失任何評論資料。
您將了解為何 GroupDocs Viewer 是可投入生產環境的選擇、如何設定環境、完整程式碼示例,以及在處理大型檔案時保持效能的實用技巧。

將帶有批註的文件渲染於 GroupDocs.Viewer for Java
本教學您將掌握的內容:
- 完整的 GroupDocs Viewer 設定與配置
- 步驟式 將 Word 轉換為 HTML 並保留評論
- 常見問題的解決方案與避免陷阱的技巧
- 真實案例的實作模式與最佳實踐
- 生產環境的效能優化技術
快速回答
- GroupDocs Viewer 能將 Word 轉換為 HTML 嗎? 可以——只需一行程式碼即可啟用 HTML 渲染與評論支援。
- 評論會保留在 HTML 輸出中嗎? 絕對會——
setRenderComments(true)會保留每一則評論與標註。 - 需要哪個 Java 版本? JDK 8 或更高。
- 生產環境需要授權嗎? 完整授權會移除浮水印並解鎖全部功能。
- 如何提升渲染速度? 僅渲染特定頁面、使用外部資源,並增加 JVM heap 大小。
什麼是「將 Word 轉換為 HTML」並保留評論?
「將 Word 轉換為 HTML」 指的是將 Microsoft Word .docx 檔案轉換為可在瀏覽器中顯示的 HTML 文件,同時保留原始的版面配置、樣式以及任何內嵌的評論。此過程可讓瀏覽器如實呈現文件,並顯示審閱者的回饋。
為何選擇 GroupDocs Viewer for Java?
在深入程式碼之前,先來看看為何 GroupDocs Viewer 是 Java 文件渲染的首選:
- 支援 170+ 格式 – 從 DOCX 到 CAD 檔案皆可處理,讓您只需一個相依套件即可滿足所有轉換需求。
- 無需第三方 Office 安裝 – 可在任何作業系統上執行,無需 Microsoft Office、LibreOffice 或其他大型執行環境。
- 保留格式與批註 – 評論、腳註與修訂痕跡在轉換後仍完整保留。
- 快速且輕量的引擎 – 一般 100 頁文件在標準 4 核心伺服器上渲染時間低於 2 秒。
- 完整文件與活躍社群 – 提供範例、論壇與即時支援,讓您在遇到問題時能快速取得協助。
何時使用此方式
- 建置需要顯示審閱者備註的 Web 文件檢視器
- 開發協作審閱平台,必須讓回饋保持可見
- 將舊版合約轉換為線上顯示於法律入口網站
- 開發在教材中嵌入講師批註的 e‑learning 解決方案
前置條件與環境設定
您需要的項目
- Java Development Kit (JDK) 8+ – 為您的應用程式提供執行時環境。
- Maven 3.6+ – 用於相依管理與專案建置。
- 您慣用的 IDE – 如 IntelliJ IDEA、Eclipse 或 VS Code。
- 帶有評論的範例文件 – 包含 DOCX、XLSX、PPTX 等檔案的審閱備註。
設定開發環境
步驟 1:驗證 Java 安裝
在終端機執行:
java -version
您應該會看到以 1.8 或更高開頭的版本字串。若未顯示,請從 Oracle 或 OpenJDK 官方網站下載最新的 JDK。
步驟 2:檢查 Maven 安裝
執行:
mvn -v
Maven 應回報其版本與使用的 Java 版本。若指令無法辨識,請從 Apache 官方網站安裝 Maven。
步驟 3:建立新的 Maven 專案
使用以下指令產生骨架專案:
mvn archetype:generate -DgroupId=com.example.viewer -DartifactId=viewer-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
進入新建立的 viewer-demo 資料夾,即可開始加入 GroupDocs Viewer。
設定 GroupDocs.Viewer for Java
新增相依
在任何 GroupDocs Viewer Java 教學的第一步,就是將函式庫加入專案。於 pom.xml 中加入以下設定:
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-viewer</artifactId>
<version>25.2</version>
</dependency>
小技巧: 請隨時檢查 GroupDocs 釋出頁面 以取得最新版本。函式庫持續維護,會定期推出更新與錯誤修正。
授權選項說明
GroupDocs 提供彈性的授權模式,以符合不同專案需求:
- 免費試用(適合學習): 30 天完整功能評估,並附帶評估浮水印。
- 臨時授權(開發使用): 延長評估期且無浮水印,適合概念驗證專案。請於 GroupDocs 臨時授權頁面 申請。
- 完整授權(正式上線): 無任何限制或浮水印,允許商業使用。可於 GroupDocs 購買頁面 取得。
基本初始化範本
以下是本教學中將持續使用的基本模式:
try (Viewer viewer = new Viewer("input.docx")) {
// Rendering options will be set later
}
此模式有效的原因:
- 自動資源管理 可防止記憶體洩漏。
- 例外處理 捕捉常見的檔案存取問題。
- 程式碼簡潔易讀,便於在大型專案中維護。
核心實作:渲染帶有評論的文件
流程說明
當您使用 GroupDocs Viewer 渲染文件時,函式庫會執行四個關鍵步驟:
- 文件分析 – 解析輸入檔案並建立內部表示。
- 評論抽取 – 識別所有評論、腳註與標註。
- HTML 產生 – 產生符合標準的乾淨 HTML,還原原始版面。
- 資源處理 – 將圖片、CSS、字型等資源內嵌或另存為外部檔案。
步驟式實作
步驟 1:設定檔案路徑
提前規劃輸入與輸出位置,可避免路徑相關錯誤:
Path inputPath = Paths.get("documents/sample-with-comments.docx");
Path outputDir = Paths.get("output/html");
Files.createDirectories(outputDir);
此作法的好處:
- 使用現代的 Java NIO.2
PathAPI,比java.io.File更可靠。 - 具描述性的變數名稱有助於除錯。
- 輸出模式中的
{0}佔位符會自動替換為頁碼。
步驟 2:設定 HTML 渲染選項
這裡就是魔法發生的地方。我們告訴 GroupDocs 具體的渲染需求:
HtmlViewOptions 用於設定文件渲染為 HTML 時的行為,包括資源處理與評論渲染。
HtmlViewOptions viewOptions = HtmlViewOptions.forEmbeddedResources(outputDir);
viewOptions.setRenderComments(true); // Preserve every comment
viewOptions.setPageNumberPrefix("page_");
主要設定說明:
forEmbeddedResources()會將 CSS、圖片與字型直接嵌入 HTML,使輸出檔案可攜帶。setRenderComments(true)這一行即確保 將 Word 轉換為 HTML 時保留所有審閱者備註。- 若偏好較輕量的 HTML,可改用
forExternalResources(),將資源另存為外部檔案。
步驟 3:執行渲染
將前述設定整合起來:
Viewer 是用來載入文件並執行渲染的主要類別。
try (Viewer viewer = new Viewer(inputPath.toFile())) {
viewer.view(viewOptions);
}
view 方法會讀取 Word 檔案、抽取評論、產生 HTML 頁面,並寫入 output/html。每一頁會以 page_1.html、page_2.html 等檔名儲存。
完整範例程式
將上述所有片段組合,即可得到一個可執行的類別,負責將 Word 文件轉換為保留評論的 HTML(完整原始碼可於官方 GitHub 倉庫取得)。
進階設定與選項
動態輸出目錄設定
在較大型的應用程式中,您可能需要依使用者 ID 或時間戳記產生輸出目錄:
String userId = "12345";
Path dynamicOutput = Paths.get("output", userId, LocalDate.now().toString());
Files.createDirectories(dynamicOutput);
HtmlViewOptions dynamicOptions = HtmlViewOptions.forEmbeddedResources(dynamicOutput);
常見問題與除錯
問題 1:「找不到檔案」錯誤
請確保輸入路徑為絕對路徑或相對於工作目錄,並檢查檔案權限。使用 Path 物件可減少字串拼接錯誤。
問題 2:評論未出現在輸出中
再次確認在呼叫 viewer.view() 之前已執行 setRenderComments(true)。同時確定來源文件確實包含評論,可透過 viewer.getDocumentInfo().getComments() 進行檢查。
問題 3:大型文件記憶體不足
GroupDocs Viewer 會串流資料,但超過 500 頁的巨檔仍可能耗盡 JVM heap。可使用 -Xmx4g 增加堆積大小,或僅渲染必要頁面。
問題 4:渲染速度緩慢
使用 viewer.view(pageRange, viewOptions) 只渲染特定頁碼。外部資源 (forExternalResources()) 亦可減少 HTML 體積,加速瀏覽器載入。
真實案例實作模式
模式 1:Web 應用程式整合
將渲染邏輯嵌入 Spring Boot 控制器,即時提供 HTML 給前端:
@RestController
@RequestMapping("/api/view")
public class DocumentController {
@GetMapping("/{id}")
public ResponseEntity<Resource> renderDocument(@PathVariable String id) throws IOException {
Path docPath = Paths.get("documents", id + ".docx");
Path outDir = Files.createTempDirectory("viewer");
HtmlViewOptions options = HtmlViewOptions.forEmbeddedResources(outDir);
options.setRenderComments(true);
try (Viewer viewer = new Viewer(docPath.toFile())) {
viewer.view(options);
}
// Return the first HTML page as a Resource
Path firstPage = outDir.resolve("page_1.html");
Resource resource = new UrlResource(firstPage.toUri());
return ResponseEntity.ok()
.contentType(MediaType.TEXT_HTML)
.body(resource);
}
}
模式 2:批次處理多文件
若需一次轉換整個資料夾的 Word 檔,可遍歷目錄並重複使用同一個 HtmlViewOptions 實例,以降低物件建立開銷。
效能最佳化與最佳實踐
記憶體管理建議
- 始終使用 try‑with‑resources 來管理
Viewer實例。 - 將大型文件分批處理,避免一次載入全部內容。
- 使用 VisualVM 等工具監控 JVM heap,必要時調整
-Xmx。 - 對常用文件實作快取,減少重複渲染。
資源使用指引
小型應用(< 100 文件/天):
HtmlViewOptions options = HtmlViewOptions.forEmbeddedResources(Paths.get("output"));
options.setRenderComments(true);
高流量應用(1000+ 文件/天):
HtmlViewOptions options = HtmlViewOptions.forExternalResources(Paths.get("output"));
options.setRenderComments(true);
options.setCacheEnabled(true);
快取策略
將渲染後的 HTML 以文件雜湊為鍵存入分散式快取(如 Redis)。收到請求時先檢查快取,若命中則直接回傳快取內容,省去渲染步驟。
何時選擇 GroupDocs Viewer 而非其他方案
GroupDocs Viewer 的理想使用情境
- 文件管理系統 – 需要顯示多種檔案類型且保留批註。
- 協作審閱平台 – 必須讓所有參與者看到評論。
- 教育工具 – 講師的註解會與投影片同步顯示。
- 法律應用 – 合約中的律師批註需忠實呈現。
考慮其他方案的情況
- 僅需簡易 PDF 顯示 – 瀏覽器內建的 PDF 檢視器已足夠。
- 基本影像轉換 –
ImageIO或類似函式庫更輕量。 - 純文字抽取 – Apache POI 或 iText 可能更合適。
常見問答
Q:可以不渲染評論嗎?
A:可以,只要省略 setRenderComments(true) 或將其設為 false。
Q:哪些檔案格式支援評論渲染?
A:大多數主流格式皆支援,包括 DOC/DOCX、XLS/XLSX、PPT/PPTX、PDF 等。完整列表請參考官方文件。
Q:我可以自訂 HTML 輸出的樣式嗎?
A:當然可以。使用 HtmlViewOptions.setEmbedResources(false) 產生外部 CSS,之後自行加入樣式表。
Q:如何處理受密碼保護的文件?
A:提供帶有密碼的 LoadOptions 實例:
LoadOptions 允許您指定文件載入參數,例如密碼。
LoadOptions loadOptions = new LoadOptions("myPassword");
try (Viewer viewer = new Viewer(inputPath.toFile(), loadOptions)) {
viewer.view(viewOptions);
}
Q:能只渲染特定頁面嗎?
A:可以,使用接受 PageNumber 集合的重載 view 方法:
PageNumber 代表在渲染子集合時使用的頁碼索引。
viewer.view(new int[]{1, 3, 5}, viewOptions);
Q:為何大型文件渲染較慢?
A:檔案越大處理時間越長。可透過僅渲染必要頁面、使用外部資源、增加 JVM heap,或啟用非同步處理來提升速度。
Q:如何監控渲染進度?
A:GroupDocs Viewer 本身未提供回呼機制,您可在 viewer.view() 前後使用 System.nanoTime() 計時,並記錄耗時。
Q:若來源文件損毀會發生什麼?
A:函式庫會拋出 ViewerException。請將呼叫包在 try‑catch 中,並記錄錯誤以實現優雅降級。
Q:可以在商業應用中使用 GroupDocs Viewer 嗎?
A:可以,但必須購買商業授權。免費試用版會有浮水印,必須在正式上線前移除。
Q:有使用量限制嗎?
A:函式庫本身不設限制,唯授權合約可能規範使用上限,請參閱您的授權條款。
Q:我可以重新分發包含 GroupDocs Viewer 的應用程式嗎?
A:您可以分發自己的應用程式,但不得重新分發 GroupDocs 函式庫本身的二進位檔。請遵守授權條款。
後續步驟與進階主題
您已掌握 將 Word 轉換為 HTML 並保留評論的基礎。以下是進一步提升技能的方向:
- 加上浮水印 – 為渲染頁面加入自訂浮水印,以加強品牌或保密性。
- 擷取中繼資料 – 透過
viewer.getDocumentInfo()取得作者、建立日期與頁數等資訊。 - 自訂檢視器 – 為 PDF、試算表或簡報建立專屬檢視器,針對評論顯示方式進行客製化。
- 雲端儲存整合 – 直接從 AWS S3、Azure Blob 或 Google Drive 讀取檔案渲染,無需先行下載至本機。
推薦學習路徑
- 嘗試不同檔案類型 – 測試 Excel、PowerPoint、PDF 等,觀察評論在各格式的處理情形。
- 建置簡易 Web 檢視器 – 建立一個最小化的 HTML 頁面,使用
<iframe>或 AJAX 載入產生的 HTML。 - 探索 GroupDocs 生態系 – 了解 GroupDocs Annotation、Comparison、Signature 等套件,打造端到端的文件工作流程。
- 加入社群 – 參與 GroupDocs 論壇 交流技巧、範例與支援。
取得協助與支援
官方資源
社群資源
- Stack Overflow(標籤:
groupdocs-viewer) - Reddit 程式設計社群
- Java 開發者 Discord 伺服器
最後更新日期: 2026-05-21
測試版本: GroupDocs.Viewer 25.2 for Java
作者: GroupDocs
java -version
javac -version
mvn -version
mvn archetype:generate -DgroupId=com.example.documentviewer -DartifactId=groupdocs-viewer-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
<repositories>
<repository>
<id>repository.groupdocs.com</id>
<name>GroupDocs Repository</name>
<url>https://releases.groupdocs.com/viewer/java/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-viewer</artifactId>
<version>25.2</version>
</dependency>
</dependencies>
import com.groupdocs.viewer.Viewer;
// The try-with-resources pattern ensures proper cleanup
try (Viewer viewer = new Viewer("path/to/your/document.docx")) {
// All rendering operations happen here
// Resources are automatically closed when done
} catch (Exception e) {
System.err.println("Error rendering document: " + e.getMessage());
e.printStackTrace();
}
import java.nio.file.Path;
import java.nio.file.Paths;
// Create a descriptive output directory
Path outputDirectory = Paths.get("rendered-documents");
Path pageFilePathFormat = outputDirectory.resolve("page_{0}.html");
import com.groupdocs.viewer.options.HtmlViewOptions;
// Create HTML options with embedded resources
HtmlViewOptions viewOptions = HtmlViewOptions.forEmbeddedResources(pageFilePathFormat);
// The crucial setting – enable comment rendering!
viewOptions.setRenderComments(true);
import com.groupdocs.viewer.Viewer;
try (Viewer viewer = new Viewer("path/to/your/document.docx")) {
// Create output directory if it doesn't exist
if (!outputDirectory.toFile().exists()) {
outputDirectory.toFile().mkdirs();
}
// Perform the actual rendering
viewer.view(viewOptions);
System.out.println("Document rendered successfully!");
System.out.println("Output location: " + outputDirectory.toAbsolutePath());
} catch (Exception e) {
System.err.println("Rendering failed: " + e.getMessage());
e.printStackTrace();
}
package com.example.documentviewer;
import com.groupdocs.viewer.Viewer;
import com.groupdocs.viewer.options.HtmlViewOptions;
import java.nio.file.Path;
import java.nio.file.Paths;
public class DocumentRenderer {
public static void main(String[] args) {
renderDocumentWithComments("sample-document.docx", "output");
}
public static void renderDocumentWithComments(String inputFile, String outputDir) {
// Set up paths
Path outputDirectory = Paths.get(outputDir);
Path pageFilePathFormat = outputDirectory.resolve("page_{0}.html");
// Configure rendering options
HtmlViewOptions viewOptions = HtmlViewOptions.forEmbeddedResources(pageFilePathFormat);
viewOptions.setRenderComments(true);
// Render the document
try (Viewer viewer = new Viewer(inputFile)) {
// Ensure output directory exists
outputDirectory.toFile().mkdirs();
// Execute rendering
viewer.view(viewOptions);
System.out.println("✓ Document rendered with comments preserved");
System.out.println("📂 Output directory: " + outputDirectory.toAbsolutePath());
} catch (Exception e) {
System.err.println("❌ Rendering failed: " + e.getMessage());
e.printStackTrace();
}
}
}
import java.nio.file.Path;
import java.nio.file.Paths;
public class PathManager {
/**
* Creates a structured output path based on document name and timestamp
*/
public static Path getOutputDirectoryPath(String documentName) {
String timestamp = String.valueOf(System.currentTimeMillis());
String cleanDocName = documentName.replaceAll("[^a-zA-Z0-9]", "_");
return Paths.get("rendered-docs")
.resolve(cleanDocName)
.resolve(timestamp);
}
/**
* Simple output directory for basic use cases
*/
public static Path getSimpleOutputPath(String folderName) {
return Paths.get("output").resolve(folderName);
}
}
// Always check if file exists before processing
Path inputPath = Paths.get("your-document.docx");
if (!inputPath.toFile().exists()) {
throw new IllegalArgumentException("Input file not found: " + inputPath.toAbsolutePath());
}
// Check if file is readable
if (!inputPath.toFile().canRead()) {
throw new IllegalArgumentException("Cannot read input file: " + inputPath.toAbsolutePath());
}
HtmlViewOptions viewOptions = HtmlViewOptions.forEmbeddedResources(pageFilePathFormat);
// This line is crucial – don't forget it!
viewOptions.setRenderComments(true);
// For debugging, you can verify the setting:
System.out.println("Comments enabled: " + viewOptions.isRenderComments());
// Increase JVM heap size when running
// java -Xmx2g -Xms1g YourApplication
// Or process documents page by page for very large files
HtmlViewOptions viewOptions = HtmlViewOptions.forEmbeddedResources(pageFilePathFormat);
viewOptions.setRenderComments(true);
// Render only specific pages if needed
viewer.view(viewOptions, 1, 2, 3); // Renders only pages 1, 2, and 3
// Use external resources for faster processing of multiple pages
HtmlViewOptions viewOptions = HtmlViewOptions.forExternalResources(
pageFilePathFormat,
"resources/page_{0}/",
"resources/page_{0}/{0}"
);
// Enable caching if processing the same document multiple times
// (Note: Implement caching at application level)
@RestController
@RequestMapping("/api/documents")
public class DocumentController {
@PostMapping("/render")
public ResponseEntity<String> renderDocument(
@RequestParam("file") MultipartFile file) {
try {
// Save uploaded file temporarily
Path tempFile = Files.createTempFile("upload", ".tmp");
file.transferTo(tempFile.toFile());
// Render with comments
String outputDir = renderDocumentWithComments(
tempFile.toString(),
"web-output"
);
return ResponseEntity.ok("Document rendered: " + outputDir);
} catch (Exception e) {
return ResponseEntity.badRequest()
.body("Rendering failed: " + e.getMessage());
}
}
}
public class BatchDocumentProcessor {
public void processFolderWithComments(String inputFolder) {
File folder = new File(inputFolder);
File[] files = folder.listFiles((dir, name) ->
name.toLowerCase().endsWith(".docx") ||
name.toLowerCase().endsWith(".xlsx") ||
name.toLowerCase().endsWith(".pptx")
);
if (files == null) return;
for (File file : files) {
try {
String outputDir = file.getName().replace(".", "_") + "_output";
renderDocumentWithComments(file.getAbsolutePath(), outputDir);
System.out.println("✓ Processed: " + file.getName());
} catch (Exception e) {
System.err.println("❌ Failed to process " + file.getName() + ": " + e.getMessage());
}
}
}
}
// Simple approach works fine
try (Viewer viewer = new Viewer(documentPath)) {
viewer.view(viewOptions);
}
public class DocumentRenderingService {
private final ExecutorService executorService =
Executors.newFixedThreadPool(4); // Limit concurrent renderings
public CompletableFuture<String> renderAsync(String documentPath) {
return CompletableFuture.supplyAsync(() -> {
try (Viewer viewer = new Viewer(documentPath)) {
// Rendering logic here
return "success";
} catch (Exception e) {
throw new RuntimeException(e);
}
}, executorService);
}
}
public class CachedDocumentRenderer {
private final Map<String, String> renderCache = new ConcurrentHashMap<>();
public String renderWithCaching(String documentPath) {
String cacheKey = generateCacheKey(documentPath);
return renderCache.computeIfAbsent(cacheKey, key -> {
// Only render if not already cached
return performActualRendering(documentPath);
});
}
private String generateCacheKey(String documentPath) {
// Include file modification time in cache key
File file = new File(documentPath);
return documentPath + "_" + file.lastModified();
}
}
LoadOptions loadOptions = new LoadOptions();
loadOptions.setPassword("your-password");
try (Viewer viewer = new Viewer("protected-doc.docx", loadOptions)) {
// Render as usual
}
viewer.view(viewOptions, 1, 3, 5); // Renders only pages 1, 3, and 5
System.out.println("Starting render for: " + documentName);
long startTime = System.currentTimeMillis();
viewer.view(viewOptions);
long endTime = System.currentTimeMillis();
System.out.println("Rendering completed in: " + (endTime - startTime) + "ms");
try (Viewer viewer = new Viewer(documentPath)) {
viewer.view(viewOptions);
} catch (CorruptOrDamagedFileException e) {
System.err.println("Document is corrupted: " + e.getMessage());
} catch (Exception e) {
System.err.println("General error: " + e.getMessage());
}