Java 使用 IText 7 签署 Pdf 时堆 Space
Java Heap Space when Sign a Pdf with IText 7
我创建了这个方法来签署 pdf 文件:
public File sign7(File pdfOriginal,
String diretorioSalvar,
String nomeArquivo,
String motivo, String local,
LocalDateTime data,
String textoAssinatura,
boolean visible,
PDFService.DisposicaoPagina dispPagina,
File arquivoOriginal
) throws IOException, DocumentException, GeneralSecurityException {
log.debug("comecou assinar");
File diretorioSaida = new File(diretorioSalvar);
diretorioSaida.mkdirs();
File pdfAssinado = new File(diretorioSalvar+File.separator+nomeArquivo);
String keystore_password = KEYSTORE_PASSWORD;
String key_password = KEYSTORE_PASSWORD;
keystore.load(KEYSTORE.getInputStream(), keystore_password.toCharArray());
PrivateKey key = (PrivateKey) keystore.getKey(alias, key_password.toCharArray());
Certificate[] chain = keystore.getCertificateChain(alias);
log.debug("keystore provider : {}", keystore.getProvider().getName());
log.debug("Assinando com alias :{}", alias);
log.debug("chain size: " + chain.length);
PdfReader reader = new PdfReader(new RandomAccessBufferedFileInputStream(pdfOriginal));
PdfWriter writer = new PdfWriter(pdfAssinado);
PdfDocument doc = new PdfDocument(reader,writer);
FileOutputStream os = new FileOutputStream(pdfAssinado);
PdfSigner signer = new PdfSigner(doc.getReader(),os,true);
signer.setCertificationLevel(PdfSigner.NOT_CERTIFIED);
//TEXTO DO CARIMBO
String texto;
ImageData imgCarimbo;
PdfPage moldPage = doc.getLastPage();
PageSize pSize = new PageSize(moldPage.getPageSize());
PdfCanvas cPage = new PdfCanvas(moldPage);
PdfFont font = null;
try {
font = PdfFontFactory.createFont(FONT.getFile().getPath(), PdfEncodings.WINANSI, true);
} catch (IOException e) {
e.printStackTrace();
}
cPage.setFillColor(Color.BLACK);
Rectangle rect = new Rectangle(
(float) (pSize.getWidth()*0.653), //0.725 Y
(float) (pSize.getHeight()*0.9), //0.90 X
(float) (pSize.getWidth()*0.32), //0.25 Largura
(float) (pSize.getHeight()*0.068)); //0.07 Altura
cPage.fillStroke();
PdfFormXObject xObject = new PdfFormXObject(rect);
Image rectImg = new Image(xObject);
ImageData imgLogoCarimboOval = ImageDataFactory.create(LOGO_CARIMBO_DIGITAL.getFile().getPath());
ImageData imgLogoCarimboBg = ImageDataFactory.create(LOGO_CARIMBO_BG.getFile().getPath());
int paginaAparencia = (dispPagina == PDFService.DisposicaoPagina.ULTIMA_PAGINA?doc.getNumberOfPages():1);
String arqOriginalHash = "";
if (arquivoOriginal != null) {
arqOriginalHash = pdfService.gerarHash(arquivoOriginal);
}
PdfSignatureAppearance appearance = signer
.getSignatureAppearance()
.setReason(motivo + " - Hash: " + arqOriginalHash)
.setLocation(local)
.setReuseAppearance(false)
.setImage(imgLogoCarimboBg)
.setSignatureGraphic(imgLogoCarimboOval)
.setImageScale(100)
.setRenderingMode(RenderingMode.GRAPHIC_AND_DESCRIPTION)
.setPageRect(rect)
.setLayer2Font(font)
.setLayer2FontSize(6)
.setLayer2Text(textoAssinatura)
.setPageNumber(paginaAparencia);
signer.setFieldName(signer.getNewSigFieldName());
// Creating the signature
IExternalSignature pks = new PrivateKeySignature(key, DigestAlgorithms.SHA1, "BC");
IExternalDigest digest = new ProviderDigest("BC");
Collection<ICrlClient> crlList=null; IOcspClient ocspClient = null; ITSAClient tsaClient=null;
writer.close();
signer.signDetached(digest, pks, chain, crlList, ocspClient, tsaClient, 0, PdfSigner.CryptoStandard.CMS);
reader.close();
os.close();
log.debug("acabou assinar");
return pdfAssinado;
}
(此方法在我的最后一页创建图章并签署 pdf)但是当我尝试签署一个 500MB 的文件时,我得到了一个 java 堆 Space 行:
signer.signDetached(digest, pks, chain, crlList, ocspClient, tsaClient, 0, PdfSigner.CryptoStandard.CMS);
如果我尝试对较小的文件进行签名(我只一个一个地尝试过,不知道我是否同时尝试多个我会得到同样的错误)
我已经尝试通过我的应用更改内存,但没有成功。
PdfReader
实例化
在您的 PdfReader
实例化中,您混淆了 PDF 库和内存中原始文件副本的额外内存需求加上一点。对于 File pdfOriginal
你做:
PdfReader reader = new PdfReader(new RandomAccessBufferedFileInputStream(pdfOriginal));
RandomAccessBufferedFileInputStream
不是 iText class!但是有一个 PDFBox class 这个名字,我假设你在这里使用。此 PDFBox class 是一个 InputStream
,它还实现了 PDFBox 的 RandomAccessRead
接口,以通用随机访问方式处理本地文件系统文件。
由于 iText 确实有自己的机制来实现文件随机访问,特别是不为此使用 PDFBox 接口,它只识别并使用 RandomAccessBufferedFileInputStream
实例作为 InputStream
。因此,iText 将该 Stream 中的所有数据读入 byte[]
以获得适当的随机访问支持。
相反,如果您允许 iText 看到源是本地文件系统文件,它可以使用自己的随机文件访问权限,并且不会创建文件的内存副本。只需使用
PdfReader reader = new PdfReader(pdfOriginal);
对于您的 500MB 文件,这将减少 500 MB 的内存使用量。
额外 PdfWriter
和 PdfDocument
个实例
此外,你做到了
PdfWriter writer = new PdfWriter(pdfAssinado);
PdfDocument doc = new PdfDocument(reader,writer);
FileOutputStream os = new FileOutputStream(pdfAssinado);
PdfSigner signer = new PdfSigner(doc.getReader(),os,true);
即您自己创建了一个 PdfWriter
和一个 PdfDocument
实例,它们只会占用额外的内存,然后创建一个具有自己内部 PdfWriter
和 PdfDocument
实例的 PdfSigner
。
因此,不要创建您自己的 PdfWriter
和 PdfDocument
实例。您稍后访问自己的 PdfDocument
实例以确定页面大小;你应该改用 PdfSigner
FileOutputStream os = new FileOutputStream(pdfAssinado);
PdfSigner signer = new PdfSigner(reader,os,true);
PdfDocument doc = signer.getDocument();
并删除后面的writer.close()
指令。
无论这些额外对象需要多少,这都会减少内存使用量。
内存中的临时文件副本
你像这样实例化 PdfSigner
:
PdfSigner signer = new PdfSigner(reader,os,true);
如 JavaDocs 中所述,此构造函数将中间文件副本(签名创建期间需要)保存在 ByteArrayOutputStream
实例中,即在内存中:
/**
* Creates a PdfSigner instance. Uses a {@link java.io.ByteArrayOutputStream} instead of a temporary file.
*
* @param reader PdfReader that reads the PDF file
* @param outputStream OutputStream to write the signed PDF file
* @param append boolean to indicate whether the signing should happen in append mode or not
* @throws IOException
* @deprecated will be removed in next major release.
* Use {@link #PdfSigner(PdfReader, OutputStream, StampingProperties)} instead.
*/
@Deprecated
public PdfSigner(PdfReader reader, OutputStream outputStream, boolean append) throws IOException
而是在文件系统中为此提供一个临时文件:
String temporaryFile = pdfAssinado.getAbsolutePath() + ".tmp";
PdfSigner signer = new PdfSigner(reader, os, temporaryFile, true);
对于您的 500MB 文件,这将再次减少 500MB 以上的内存使用量。
签署 500 MB 文件时,上述更改将减少超过 1 GB 的内存占用。我不知道这是否足够,但这至少应该大大减少内存需求。
我创建了这个方法来签署 pdf 文件:
public File sign7(File pdfOriginal,
String diretorioSalvar,
String nomeArquivo,
String motivo, String local,
LocalDateTime data,
String textoAssinatura,
boolean visible,
PDFService.DisposicaoPagina dispPagina,
File arquivoOriginal
) throws IOException, DocumentException, GeneralSecurityException {
log.debug("comecou assinar");
File diretorioSaida = new File(diretorioSalvar);
diretorioSaida.mkdirs();
File pdfAssinado = new File(diretorioSalvar+File.separator+nomeArquivo);
String keystore_password = KEYSTORE_PASSWORD;
String key_password = KEYSTORE_PASSWORD;
keystore.load(KEYSTORE.getInputStream(), keystore_password.toCharArray());
PrivateKey key = (PrivateKey) keystore.getKey(alias, key_password.toCharArray());
Certificate[] chain = keystore.getCertificateChain(alias);
log.debug("keystore provider : {}", keystore.getProvider().getName());
log.debug("Assinando com alias :{}", alias);
log.debug("chain size: " + chain.length);
PdfReader reader = new PdfReader(new RandomAccessBufferedFileInputStream(pdfOriginal));
PdfWriter writer = new PdfWriter(pdfAssinado);
PdfDocument doc = new PdfDocument(reader,writer);
FileOutputStream os = new FileOutputStream(pdfAssinado);
PdfSigner signer = new PdfSigner(doc.getReader(),os,true);
signer.setCertificationLevel(PdfSigner.NOT_CERTIFIED);
//TEXTO DO CARIMBO
String texto;
ImageData imgCarimbo;
PdfPage moldPage = doc.getLastPage();
PageSize pSize = new PageSize(moldPage.getPageSize());
PdfCanvas cPage = new PdfCanvas(moldPage);
PdfFont font = null;
try {
font = PdfFontFactory.createFont(FONT.getFile().getPath(), PdfEncodings.WINANSI, true);
} catch (IOException e) {
e.printStackTrace();
}
cPage.setFillColor(Color.BLACK);
Rectangle rect = new Rectangle(
(float) (pSize.getWidth()*0.653), //0.725 Y
(float) (pSize.getHeight()*0.9), //0.90 X
(float) (pSize.getWidth()*0.32), //0.25 Largura
(float) (pSize.getHeight()*0.068)); //0.07 Altura
cPage.fillStroke();
PdfFormXObject xObject = new PdfFormXObject(rect);
Image rectImg = new Image(xObject);
ImageData imgLogoCarimboOval = ImageDataFactory.create(LOGO_CARIMBO_DIGITAL.getFile().getPath());
ImageData imgLogoCarimboBg = ImageDataFactory.create(LOGO_CARIMBO_BG.getFile().getPath());
int paginaAparencia = (dispPagina == PDFService.DisposicaoPagina.ULTIMA_PAGINA?doc.getNumberOfPages():1);
String arqOriginalHash = "";
if (arquivoOriginal != null) {
arqOriginalHash = pdfService.gerarHash(arquivoOriginal);
}
PdfSignatureAppearance appearance = signer
.getSignatureAppearance()
.setReason(motivo + " - Hash: " + arqOriginalHash)
.setLocation(local)
.setReuseAppearance(false)
.setImage(imgLogoCarimboBg)
.setSignatureGraphic(imgLogoCarimboOval)
.setImageScale(100)
.setRenderingMode(RenderingMode.GRAPHIC_AND_DESCRIPTION)
.setPageRect(rect)
.setLayer2Font(font)
.setLayer2FontSize(6)
.setLayer2Text(textoAssinatura)
.setPageNumber(paginaAparencia);
signer.setFieldName(signer.getNewSigFieldName());
// Creating the signature
IExternalSignature pks = new PrivateKeySignature(key, DigestAlgorithms.SHA1, "BC");
IExternalDigest digest = new ProviderDigest("BC");
Collection<ICrlClient> crlList=null; IOcspClient ocspClient = null; ITSAClient tsaClient=null;
writer.close();
signer.signDetached(digest, pks, chain, crlList, ocspClient, tsaClient, 0, PdfSigner.CryptoStandard.CMS);
reader.close();
os.close();
log.debug("acabou assinar");
return pdfAssinado;
}
(此方法在我的最后一页创建图章并签署 pdf)但是当我尝试签署一个 500MB 的文件时,我得到了一个 java 堆 Space 行:
signer.signDetached(digest, pks, chain, crlList, ocspClient, tsaClient, 0, PdfSigner.CryptoStandard.CMS);
如果我尝试对较小的文件进行签名(我只一个一个地尝试过,不知道我是否同时尝试多个我会得到同样的错误)
我已经尝试通过我的应用更改内存,但没有成功。
PdfReader
实例化
在您的 PdfReader
实例化中,您混淆了 PDF 库和内存中原始文件副本的额外内存需求加上一点。对于 File pdfOriginal
你做:
PdfReader reader = new PdfReader(new RandomAccessBufferedFileInputStream(pdfOriginal));
RandomAccessBufferedFileInputStream
不是 iText class!但是有一个 PDFBox class 这个名字,我假设你在这里使用。此 PDFBox class 是一个 InputStream
,它还实现了 PDFBox 的 RandomAccessRead
接口,以通用随机访问方式处理本地文件系统文件。
由于 iText 确实有自己的机制来实现文件随机访问,特别是不为此使用 PDFBox 接口,它只识别并使用 RandomAccessBufferedFileInputStream
实例作为 InputStream
。因此,iText 将该 Stream 中的所有数据读入 byte[]
以获得适当的随机访问支持。
相反,如果您允许 iText 看到源是本地文件系统文件,它可以使用自己的随机文件访问权限,并且不会创建文件的内存副本。只需使用
PdfReader reader = new PdfReader(pdfOriginal);
对于您的 500MB 文件,这将减少 500 MB 的内存使用量。
额外 PdfWriter
和 PdfDocument
个实例
此外,你做到了
PdfWriter writer = new PdfWriter(pdfAssinado);
PdfDocument doc = new PdfDocument(reader,writer);
FileOutputStream os = new FileOutputStream(pdfAssinado);
PdfSigner signer = new PdfSigner(doc.getReader(),os,true);
即您自己创建了一个 PdfWriter
和一个 PdfDocument
实例,它们只会占用额外的内存,然后创建一个具有自己内部 PdfWriter
和 PdfDocument
实例的 PdfSigner
。
因此,不要创建您自己的 PdfWriter
和 PdfDocument
实例。您稍后访问自己的 PdfDocument
实例以确定页面大小;你应该改用 PdfSigner
FileOutputStream os = new FileOutputStream(pdfAssinado);
PdfSigner signer = new PdfSigner(reader,os,true);
PdfDocument doc = signer.getDocument();
并删除后面的writer.close()
指令。
无论这些额外对象需要多少,这都会减少内存使用量。
内存中的临时文件副本
你像这样实例化 PdfSigner
:
PdfSigner signer = new PdfSigner(reader,os,true);
如 JavaDocs 中所述,此构造函数将中间文件副本(签名创建期间需要)保存在 ByteArrayOutputStream
实例中,即在内存中:
/**
* Creates a PdfSigner instance. Uses a {@link java.io.ByteArrayOutputStream} instead of a temporary file.
*
* @param reader PdfReader that reads the PDF file
* @param outputStream OutputStream to write the signed PDF file
* @param append boolean to indicate whether the signing should happen in append mode or not
* @throws IOException
* @deprecated will be removed in next major release.
* Use {@link #PdfSigner(PdfReader, OutputStream, StampingProperties)} instead.
*/
@Deprecated
public PdfSigner(PdfReader reader, OutputStream outputStream, boolean append) throws IOException
而是在文件系统中为此提供一个临时文件:
String temporaryFile = pdfAssinado.getAbsolutePath() + ".tmp";
PdfSigner signer = new PdfSigner(reader, os, temporaryFile, true);
对于您的 500MB 文件,这将再次减少 500MB 以上的内存使用量。
签署 500 MB 文件时,上述更改将减少超过 1 GB 的内存占用。我不知道这是否足够,但这至少应该大大减少内存需求。