💡 Contoh lengkap yang berfungsi tersedia di GitHub:
sign-documents-in-docker-fonts-java

Layanan Penandatanganan Kontrak yang Berjalan Selama Sembilan Bulan

Penyediaan font dalam kontainer adalah langkah yang menentukan apakah layanan penandatanganan Java berfungsi di produksi atau hanya dalam pengujian yang kebetulan Anda tulis. Ini penting karena kegagalan sudah terjadwal: sebuah gambar JRE memberi Anda cakupan font yang cukup untuk terlihat benar, lalu menahan sisanya sampai dokumen tertentu tiba.

Pertimbangkan bentuknya. Sebuah alur kerja dokumen menandatangani kontrak, dideploy pada eclipse-temurin:17-jre, dan itu berfungsi. Sembilan bulan kemudian, perusahaan menandatangani pelanggan pertamanya di Jepang, nama masuk ke teks tanda tangan, dan pekerjaan gagal dengan Specified font file was not found. Tidak ada yang berubah dalam layanan. Gambar tersebut tidak pernah memiliki cakupan CJK; tidak ada dokumen yang memintanya.

Penyebab teknisnya singkat. eclipse-temurin:17-jre menyertakan 8 file font DejaVu untuk AWT, yang mencakup Latin, Yunani, dan Sirilik. GroupDocs.Signature tidak menggantikan keluarga yang hilang, sehingga permintaan untuk font yang mendukung bahasa Jepang gagal alih-alih menurun, dan membiarkan font tidak diatur tidak membantu karena perpustakaan kemudian meminta Times New Roman, yang juga tidak ada.

Mengapa Ini Lebih Buruk Daripada Gambar Tanpa Font

Gambar dasar .NET dan Python mengirimkan nol font. Itu adalah kegagalan yang lebih baik: tanda tangan pertama gagal, pada run pengujian pertama, dan seseorang memperbaikinya sebelum layanan dirilis.

Gambar JVM gagal sebagian, yang merupakan versi yang mahal. Bug berada dalam kode yang sudah berada di produksi, dipicu oleh data pelanggan bukan oleh apa pun dalam deployment, dan orang yang bertugas melihat kesalahan font dari layanan yang tidak disentuh selama berbulan‑bulan. Biaya insiden bukan pada perbaikan – perbaikan hanyalah satu lapisan Dockerfile – melainkan jam sebelum siapa pun mempercayai bahwa font terlibat.

Asimetri itu menjadi argumen untuk memperlakukan cakupan font sebagai sesuatu yang Anda pastikan pada startup, bukan sesuatu yang Anda temukan.

Itu juga mengubah siapa yang membayar. Gambar tanpa font menghabiskan waktu dua puluh menit bagi seorang pengembang selama penyiapan. Gambar dengan cakupan parsial menghabiskan seorang engineer on‑call satu jam pada waktu yang tidak membantu, ditambah nilai kontrak yang tertunda, ditambah tinjauan yang mengikuti insiden yang tidak dapat diatribusikan ke perubahan. Perbedaan teknis antara keduanya hanyalah empat paket dalam Dockerfile.

Apa Biaya Penyediaan Sebenarnya

Empat paket Debian pada tahap runtime:

RUN apt-get update && apt-get install -y --no-install-recommends \
        fontconfig \
        fonts-dejavu-core \
        fonts-liberation \
        fonts-noto-cjk \
    && fc-cache -f \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

Ukuran gambar biasanya menjadi keberatan, dan layak disebutkan secara spesifik: paket CJK adalah yang besar, tiga paket lainnya kecil, dan tidak ada yang opsional jika dokumen Anda dapat memuat nama non‑Latin. Instal apa yang sebenarnya dibutuhkan set dokumen Anda dan verifikasi dengan membaca kembali alih‑alih memangkas berdasarkan insting.

fontconfig adalah resolver plus fc-list untuk debugging. fonts-dejavu-core menggandakan apa yang sudah dibundel JRE, yang disengaja: itu menjaga gambar tetap jujur jika gambar dasar berubah. fonts-liberation penting karena dokumen yang dibuat di Windows merujuk Arial dan Times New Roman dengan nama dan mengharapkan rendering yang kompatibel secara metrik. fonts-noto-cjk adalah yang dibutuhkan insiden di atas.

Menyelesaikan Keluarga Font Alih‑Alih Menamai Satu

Penyediaan saja tidak cukup, karena kode masih harus menamai keluarga yang ada. Cara portabel adalah menanyakan perpustakaan: coba tanda tangan percobaan per kandidat, pertahankan yang pertama tidak melempar.

for (String candidate : candidates) {
    if (tryFamily(sourcePath, candidate) == null) {
        return candidate;
    }
}
return null;

Probe itu sendiri adalah panggilan tanda tangan biasa ke direktori sementara, dengan kegagalan diubah menjadi nilai alih‑alih pengecualian:

SignatureFont font = new SignatureFont();
font.setFamilyName(familyName);
font.setSize(10);
options.setFont(font);
signature.sign(scratch.getAbsolutePath(), options);
return null;

Deteksi nama file adalah jalan pintas yang tampak setara tetapi tidak. fonts-noto-cjk Debian menginstal NotoSansCJK-Regular.ttc, yang nama keluarganya adalah Noto Sans CJK JP, sehingga mencocokkan nama file sekaligus melewatkan font dan melaporkan keluarga yang tidak akan terpecahkan.

Dengan resolusi yang ada, dua kelas kegagalan terpisah dengan bersih. Tidak ada keluarga Latin berarti gambar tidak dapat menandatangani sama sekali, yang seharusnya menghentikan kontainer. Tidak ada keluarga CJK berarti satu tanda tangan dilewati dan proses berlanjut dengan peringatan:

List<SignOptions> options = new ArrayList<>();
options.add(buildTextOptions(LATIN_TEXT, latinFamily, 50));

if (cjkFamily != null) {
    options.add(buildTextOptions(CJK_TEXT, cjkFamily, 120));
}

SignResult result = signature.sign(outputPath, options);

Perbedaan ini penting secara operasional. Kontainer yang keluar pada startup dengan “no usable font family” adalah masalah deployment, tertangkap oleh siapa pun yang men-deploy-nya. Tanda tangan yang hilang secara diam‑diam dari dokumen yang dikirim adalah masalah kepatuhan, tertangkap oleh penerima. Menghubungkan kasus fatal ke exit code non‑zero menjaga kegagalan dalam kategori pertama.

Kemudian baca kembali hasilnya, karena tanda tangan CJK yang ditulis tanpa cakupan CJK dapat dirender sebagai kotak kosong tanpa mengeluarkan apa pun:

TextSearchOptions options = new TextSearchOptions();
options.setAllPages(true);
List<TextSignature> found = signature.search(TextSignature.class, options);

Di Mana Posisi Tim yang Sudah Merilis?

Tambahkan lapisan font, tambahkan resolusi pada startup, dan log kedua hasil pada baris pertama layanan, di mana engineer berikutnya akan benar‑benar melihatnya. Perubahannya hanyalah edit Dockerfile plus kira‑kira tiga puluh baris, dan mengubah insiden yang dipicu pelanggan menjadi kontainer yang either mulai dengan cakupan yang diketahui atau menolak untuk mulai. Dokumen yang sudah ditandatangani tidak terpengaruh; hanya yang baru yang mendapatkan jalur CJK.

Memeriksa Gambar yang Sudah Anda Jalankan

Sebelum mengubah apa pun, ada baiknya mengetahui apa yang ada di gambar Anda saat ini. Dua perintah menjawabnya dari luar:

docker run --rm your-image sh -c "ls -R /usr/share/fonts | head"
docker run --rm your-image sh -c "fc-list : family | sort -u | head -20"

Perintah pertama menampilkan file font, yang kedua menampilkan nama keluarga yang akan dikembalikan resolver, dan kesenjangan di antara keduanya adalah alasan mengapa pencocokan nama file gagal. Jika fc-list tidak ada, itu sudah menjadi jawaban sendiri: fontconfig tidak terinstal, dan pencarian keluarga apa pun berjalan dalam gelap.

Di dalam layanan, pemeriksaan setara berada di log startup tepat di samping keluarga yang terpecahkan. Baris yang berbunyi fonts on disk: 8, latin: DejaVu Sans, cjk: (none) memberi orang berikutnya tepat apa yang dapat dan tidak dapat ditandatangani kontainer ini, yang lebih berguna daripada pengecualian apa pun yang mereka baca pada pukul tiga pagi.

Detail JVM yang Tidak Diharapkan Siapa‑Pun

Satu hal lagi yang menggigit khusus pada Java, dan bukan tentang font. Artefak Maven GroupDocs adalah jar “fat” yang ditandatangani. Membungkus ulang menjadi jar berbayang menghasilkan NoClassDefFoundError: com/groupdocs/signature/options/search/SearchOptions, dan remediasi biasa menghapus META-INF/*.SF|RSA|DSA tidak cukup: MANIFEST.MF membawa sekitar 19 MB digest per‑entri dan juga harus dipotong ke bagian utama. Contoh menghindari masalah dengan menjalankan terhadap classpath biasa dengan direktori dependency/ alih‑alih menbayang apa pun.

Saya menyebutnya karena keduanya – cakupan font parsial dan jar yang ditandatangani – memiliki bentuk yang sama: jalur JVM gagal dengan cara yang tampak seperti kode Anda padahal tidak. Keduanya juga murah untuk dipertahankan setelah dinamai: tetapkan tata letak classpath yang Anda tahu berfungsi, dan pastikan cakupan font pada startup alih‑alih mempercayai gambar dasar. Tidak ada yang memerlukan redesain, dan keduanya menghilangkan kelas insiden yang sebaliknya tidak dapat dibedakan dari bug aplikasi.

Kesimpulan

Layanan penandatanganan Java dalam kontainer hanya satu lapisan Dockerfile dan satu pemeriksaan startup dari pada menjadi dapat diprediksi. Instal fontconfig, DejaVu, Liberation, dan Noto CJK; selesaikan keluarga dengan probing alih‑alih mengasumsikan; lewati apa yang tidak dapat disematkan; verifikasi dengan membaca kembali. Repositori contoh mengirimkan kedua gambar, sehingga perbedaan antara cakupan dan tidak cakupan memerlukan dua build untuk dilihat alih‑alih satu insiden untuk dipelajari.

Sumber Daya Tambahan