Spring Bootで、Thymeleafの「PDF出力ボタン」クリックをトリガーにして、RabbitMQを使って非同期にPDFを生成するサンプル構成を以下に示します。
完成イメージ
今回はデスクトップにPDFを出力しました。こんな感じです。
下記のようにPDFが出力されます。

🔧 使用する技術スタック
Spring Boot
Thymeleaf
RabbitMQ
iText PDF (または OpenPDF)
Spring AMQP (RabbitMQ連携)
📦 プロジェクト構成
└── SpringAQMP ←プロジェクト名/
    ├── src/main/java/
    │   └── com.packt.cardatabase/
    │       ├── config/
    │       │   └── RabbitConfig.java
    │       ├── controller/
    │       │   └── PdfController.java
    │       ├── dto/
    │       │   └── PdfRequest.java
    │       ├── messaging/
    │       │   ├── consumer/
    │       │   │   └── PdfRequestListener.java
    │       │   └── producer/
    │       │       └── PdfRequestSender.java
    │       ├── service/
    │       │   └── Pdfservice.java
    │       └── SpringAqmpApplication.java
    ├── src/main/resources/
    │   ├── staticフォルダ
    │   ├── templates/
    │   │   └── index.html
    │   ├── application.properties
    │   └── application.yml
    └── pom.xml
1. pom.xml
spring-boot-starter-amqp、itext-core、jackson-databindのモジュールを組み込みます。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.5.4</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.packt.cardatabase</groupId>
	<artifactId>SpringAQMP</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>SpringAQMP</name>
	<description>Demo project for Spring Boot</description>
	<url/>
	<licenses>
		<license/>
	</licenses>
	<developers>
		<developer/>
	</developers>
	<scm>
		<connection/>
		<developerConnection/>
		<tag/>
		<url/>
	</scm>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
		<dependency>
		    <groupId>org.springframework.boot</groupId>
		    <artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-core -->
		<dependency>
		    <groupId>com.itextpdf</groupId>
		    <artifactId>itext-core</artifactId>
		    <version>9.2.0</version>
		    <type>pom</type>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
		<dependency>
		    <groupId>com.fasterxml.jackson.core</groupId>
		    <artifactId>jackson-databind</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>
Mavenリポジトリ
■Spring Boot Starter AMQP
■Jackson Databind
■IText Core
2. application.yml(RabbitMQ設定)
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
3. DTO: PdfRequest.java
package com.packt.cardatabase.dto;
import java.io.Serializable;
public class PdfRequest implements Serializable{
	private static final long serialVersionUID = 1L;
	
	private String title;
	private String content;
	
	public String getTitle() {
		return this.title;
	}
	
	public void setTitle(String title) {
		this.title = title;
	}
	
	public String getContent() {
		return this.content;
	}
	
	public void setContent(String content) {
		this.content = content;
	}
}
4. Controller: PdfController.java
package com.packt.cardatabase.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.packt.cardatabase.dto.PdfRequest;
import com.packt.cardatabase.messaging.producer.PdfRequestSender;
@Controller
public class PdfController {
	
	private final PdfRequestSender sender;
	
	public PdfController(PdfRequestSender sender) {
        this.sender = sender;
    }
	
	@GetMapping("/")
    public String index() {
        return "index";
    }
	
	@PostMapping("/generate-pdf")
    public String generatePdf(@RequestParam String title,
                              @RequestParam String content,
                              Model model) {
        PdfRequest request = new PdfRequest();
        request.setTitle(title);
        request.setContent(content);
        sender.send(request);
        model.addAttribute("message", "PDF生成要求を送信しました!");
        return "index";
    }
}
5. メッセージ送信: PdfRequestSender.java
RabbitTemplate での送信
PdfRequestSender クラスのようにメッセージを送信する際も、Jackson2JsonMessageConverter を適切に利用することで、シリアライズされたメッセージが送信されます。
これにより、PdfRequest オブジェクトがJSON形式で送信され、リスナーで受信したときに適切にデシリアライズされます。
結論
RabbitConfig クラスの設定で、Jackson2JsonMessageConverter を使用してメッセージのシリアライズおよびデシリアライズを行い、SimpleMessageListenerContainer でメッセージをリスニングしています。
送信側(PdfRequestSender)で RabbitTemplate を使ってメッセージを送信する際も、このコンバータが利用されることを確認しています。
package com.packt.cardatabase.messaging.producer;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import com.packt.cardatabase.dto.PdfRequest;
@Component
public class PdfRequestSender {
	
	private final RabbitTemplate rabbitTemplate;
	
	public static final String QUEUE = "pdf.queue";
	
	public PdfRequestSender(RabbitTemplate rabbitTemplate) {
		this.rabbitTemplate = rabbitTemplate;
	}
	
	public void send(PdfRequest request) {
		rabbitTemplate.convertAndSend(QUEUE,request);
	}
}
6. メッセージ受信: PdfRequestListener.java
package com.packt.cardatabase.messaging.consumer;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import com.packt.cardatabase.dto.PdfRequest;
import com.packt.cardatabase.messaging.producer.PdfRequestSender;
import com.packt.cardatabase.service.PdfService;
@Component
public class PdfRequestListener {
	private final PdfService pdfService;
    public PdfRequestListener(PdfService pdfService) {
        this.pdfService = pdfService;
    }
    @RabbitListener(queues = PdfRequestSender.QUEUE)
    public void receive(PdfRequest request) {
        pdfService.generatePdf(request);
    }
}
7. PDF生成サービス: PdfService.java
package com.packt.cardatabase.service;
import java.io.File;
import java.io.FileOutputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import com.packt.cardatabase.dto.PdfRequest;
@Service
public class PdfService {
	@Value("${file.path}")
	private String fontFilePath;
	
	public void generatePdf(PdfRequest request) {
		try {
			// ユーザーのデスクトップパスを取得
			String userHome = System.getProperty("user.home");
			String desktopPath = userHome + File.separator + "Desktop";
			
			// 出力PDFのファイル名とパス
			String fileName = "generated_" + System.currentTimeMillis() + ".pdf";
			String outputPath = desktopPath + File.separator + fileName;
			
			// PDF書き込み
			PdfWriter writer = new PdfWriter(new FileOutputStream(outputPath));
			PdfDocument pdf = new PdfDocument(writer);
			Document document = new Document(pdf);
			
			// フォント設定(ボールド)
			PdfFont boldFont = PdfFontFactory.createFont("c:\\windows\\fonts\\msgothic.ttc,0", "Identity-H");
			//PdfFont boldFont = PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD);
			
			//タイトルをPDFにセットする
			//Paragraph title = new Paragraph(request.getTitle()).setFont(boldFont);
			Paragraph title = new Paragraph(request.getTitle())
					.setFont(boldFont)
					.setFontSize(10);
			System.out.println("タイトルは、" + title);
			document.add(title);
			
			//内容(Content)をPDFニセットする
            Paragraph content = new Paragraph(request.getContent()).setFont(boldFont).setFontSize(12);
            System.out.println("内容は、" + content);
            document.add(content);
            
            //ドキュメントを閉じる
			document.close();
			System.out.println("PDF generated: " + fileName);
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}
8.RabbitConfigクラス
詳細な説明
Jackson2JsonMessageConverter の設定:
Jackson2JsonMessageConverter は、AMQP メッセージ(通常はバイト配列)を JSON に変換して、PdfRequest などのオブジェクトに変換します。この設定を messageListenerContainer のリスナー部分でも使用しています。
RabbitTemplate の設定:
RabbitTemplate は、メッセージの送受信に使われ、ここでは Jackson2JsonMessageConverter を使って、送信時にオブジェクトを JSON にシリアライズし、受信時に JSON をオブジェクトにデシリアライズします。
SimpleMessageListenerContainer の設定:
ここでリスナーを設定します。SimpleMessageListenerContainer を使って、指定したキュー(pdf.queue)からメッセージを受信し、そのメッセージをPdfRequestオブジェクトにデシリアライズして処理します。
MessageListener インターフェースの実装:
MessageListener を匿名クラスとして実装し、メッセージを受信した際にその処理を記述しています。メッセージはJackson2JsonMessageConverterを使用して、PdfRequest オブジェクトに変換されます。
エラーハンドリング:
onMessage メソッド内で、デシリアライズ処理やその他のエラーをキャッチして適切にハンドリングすることが重要です。
package com.packt.cardatabase.config;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.packt.cardatabase.dto.PdfRequest;
import com.packt.cardatabase.service.PdfService;
@Configuration
@EnableRabbit
public class RabbitConfig {
    // Jackson2JsonMessageConverter を設定してリスナーで使えるようにする
    @Bean
    public Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
        return new Jackson2JsonMessageConverter();  // Jackson を使用してメッセージの変換を行う
    }
    // RabbitTemplate を使ってメッセージ送信、受信の際にコンバータを適用する
    @Bean
    public RabbitTemplate rabbitTemplate(org.springframework.amqp.rabbit.connection.ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter());  // メッセージコンバータを設定
        return rabbitTemplate;
    }
    // SimpleMessageListenerContainer の設定
    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(org.springframework.amqp.rabbit.connection.ConnectionFactory connectionFactory) {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("pdf.queue");  // メッセージキュー名を指定
        PdfService pdfService = new PdfService();
        // ここで MessageListener を匿名クラスで設定するのではなく、処理するメソッドを指定します
        container.setMessageListener(message -> {
            try {
                // メッセージのデシリアライズ(Json から PdfRequest オブジェクトに変換)
                PdfRequest request = (PdfRequest) jackson2JsonMessageConverter().fromMessage(message);
                // PdfRequest を処理
                System.out.println("Received PdfRequest: " + request);
                // 実際の処理はここで行う
                pdfService.generatePdf(request);
            } catch (Exception e) {
                e.printStackTrace();
                // エラーハンドリング
            }
        });
        return container;
    }
}
9. HTMLテンプレート: templates/index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>PDF生成</title>
</head>
<body>
<h1>PDF生成フォーム</h1>
<form th:action="@{/generate-pdf}" method="post">
    <label>タイトル: <input type="text" name="title"/></label><br/>
    <label>内容: <textarea name="content"></textarea></label><br/>
    <button type="submit">PDF出力</button>
</form>
<p th:if="${message}" th:text="${message}"></p>
</body>
</html>
トラブルシューティング
出力したPDFにはタイトルと内容(Content)が表示されない。
(原因)
PdfService クラスの generatePdf メソッドで、PDFにタイトルとコンテンツ(content)が表示されない問題の原因は、Paragraph をPDFに追加していないことです。
title とcontentをPDFドキュメントに追加していないため、生成されるPDFは空になってしまっています。
なので、title と content をドキュメントに追加する:
Document オブジェクトに title と content のParagraphを追加する必要があります。document.add() メソッドを使って追加します。
修正後のコードはこちら⇩
package com.packt.cardatabase.service;
import java.io.File;
import java.io.FileOutputStream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import com.packt.cardatabase.dto.PdfRequest;
@Service
public class PdfService {
	@Value("${file.path}")
	private String fontFilePath;
	
	public void generatePdf(PdfRequest request) {
		try {
			// ユーザーのデスクトップパスを取得
			String userHome = System.getProperty("user.home");
			String desktopPath = userHome + File.separator + "Desktop";
			
			// 出力PDFのファイル名とパス
			String fileName = "generated_" + System.currentTimeMillis() + ".pdf";
			String outputPath = desktopPath + File.separator + fileName;
			
			// PDF書き込み
			PdfWriter writer = new PdfWriter(new FileOutputStream(outputPath));
			PdfDocument pdf = new PdfDocument(writer);
			Document document = new Document(pdf);
			
			// フォント設定(ボールド)
			PdfFont boldFont = PdfFontFactory.createFont("c:\\windows\\fonts\\msgothic.ttc,0", "Identity-H");
			//PdfFont boldFont = PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD);
			
			//タイトルをPDFにセットする
			//Paragraph title = new Paragraph(request.getTitle()).setFont(boldFont);
			Paragraph title = new Paragraph(request.getTitle())
					.setFont(boldFont)
					.setFontSize(10);
			System.out.println("タイトルは、" + title);
			document.add(title);
			
			//内容(Content)をPDFニセットする
            Paragraph content = new Paragraph(request.getContent()).setFont(boldFont).setFontSize(12);
            System.out.println("内容は、" + content);
            document.add(content);
            
            //ドキュメントを閉じる
			document.close();
			System.out.println("PDF generated: " + fileName);
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}
日本語だとPDFに表示されないです。日英どちらの表記であってもPDFに表示されるようにしたい
(原因)
日本語のテキストをPDFに表示するためには、iTextでデフォルトのフォント(例:Helvetica)では日本語を表示することができません。
解決方法
日本語を表示するには、TrueTypeフォント(.ttf)を使って日本語に対応するフォントを指定する必要があります。
例えば、Noto Sans CJK やIPAゴシック、MS Gothicなどの日本語フォントを使うと、日本語も表示できるようになります。
🌟ちなみにWindowsではこれらのフォントファイルは「C:\Windows\Fonts」フォルダにあります。
■Noto Sans CJKのダウンロードサイト
参考にしたサイト



