You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FOP自定义ResourceResolver图片加载失败:基础URI配置疑问

问题:FOP自定义图片重定向后图片加载失败

我正在开发基于FOP的嵌入式自定义XML转PDF Java应用,需要在FOP中实现自定义图片重定向。

旧XSLT代码

<fo:block text-indent="1mm" padding-before="20mm">
  <xsl:variable name="barcode" select="concat('/prescriptions/', id, '/barcode.html')"/>
  <fo:external-graphic>
    <xsl:attribute name="src">
      <xsl:value-of select="$barcode" />
    </xsl:attribute>
  </fo:external-graphic>
</fo:block>

自定义CustomResourceResolver类

package embedding;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.xmlgraphics.io.Resource;
import org.apache.xmlgraphics.io.ResourceResolver;
import java.io.BufferedInputStream;
import java.net.URI;
import java.net.URISyntaxException;

public class CustomResourceResolver implements ResourceResolver {
    String base;
    String session;
    String cookie;
    String user_id;
    String authorization;
    static final Logger logger = Logger.getLogger("fop-log");
    
    public String get_base(){
        return base;
    }
    
    public CustomResourceResolver(String _base, String _session, String _cookie, String _user_id, String _authorization){
        super();
            base = _base;
            session = _session;
            cookie = _cookie;
            user_id = _user_id;
            authorization = _authorization;
    }
    
    @Override
    public Resource getResource(URI uri) throws IOException {          
    HttpURLConnection urlConnection = null;
    try {
      String href = uri.getRawPath();

      if (!href.startsWith("/") && !base.endsWith("/")) {
          base = base + "/";
      }

      String new_uri;
      if (href.startsWith("http://") || href.startsWith("https://")) {
          new_uri = href;
      } else {
          new_uri = base + href;
      }
      
      uri = new URI(new_uri);
      logger.log(Level.INFO, "loading: ({0})", new_uri_uri); // 此处存在笔误
      URL url = uri.toURL();
      
      urlConnection = (HttpURLConnection) url.openConnection();
      if(session!= null){
          urlConnection.setRequestProperty("Cookie", cookie + "=" + session);
      }
      if(authorization != null){
          urlConnection.setRequestProperty("Fop-authorization", authorization );
      }
      if(user_id != null){
          urlConnection.setRequestProperty("Fop-user",user_id );
      }
      
      InputStream in = new BufferedInputStream(urlConnection.getInputStream());
      return new Resource(in);
      
    } catch (MalformedURLException e) {
        logger.log(Level.SEVERE, null, e);
        return null;
    } catch (IOException | URISyntaxException ioe) {
        logger.log(Level.SEVERE, null, ioe);
      return null;
    } finally {
      if (urlConnection != null) {
        urlConnection.disconnect();
      }
    }
    }

    @Override
    public OutputStream getOutputStream(URI uri) throws IOException {
        URL url = uri.toURL();
    return url.openConnection().getOutputStream();
    }
    
}

调用代码

CustomResourceResolver resolver = new CustomResourceResolver(base, session, cookie, user_id, authorization);
FopFactoryBuilder builder = new FopFactoryBuilder(new File(".").toURI(), (ResourceResolver)resolver);
final FopFactory fopFactory = builder.build();
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();

OutputStream out = new java.io.FileOutputStream(pdffile);
out = new java.io.BufferedOutputStream(out);
try {
       Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out);
                
        TransformerFactory factory = TransformerFactory.newInstance();
        URIResolver uiResolver = new CustomUriResolver(base, session, cookie, user_id, authorization);
        
        factory.setURIResolver(uiResolver);
        Transformer transformer = factory.newTransformer(new StreamSource(xsltfile));

        // 设置样式表中的<param>参数
        transformer.setParameter("versionParam", "2.0");

        // 配置XSLT转换输入源
        Source src = new StreamSource(xmlfile);

        // 将生成的FO SAX事件传递给FOP处理
        Result res = new SAXResult(fop.getDefaultHandler());

        // 启动XSLT转换和FOP处理
        transformer.transform(src, res);
            } finally {
                out.close();
            }

命令行调用方式

java -jar FopXml2Pdf.jar file.xml file.xsl file.config

运行错误日志

Sep 19, 2023 6:52:36 AM embedding.CustomResourceResolver getResource
INFO: loading: (http://localhost:3000/prescriptions/420/barcode.html)
Sep 19, 2023 6:52:36 AM org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext createImageSource
SEVERE: Unable to create ImageInputStream for InputStream from system identifier 'file:/media/sf_Clintel/careright/./' (stream is closed)
Sep 19, 2023 6:52:36 AM org.apache.fop.events.LoggingEventListener processEvent
SEVERE: Image not found. URI: /prescriptions/420/barcode.html. (No context info available)

从日志可见,已正确加载目标URL并获取数据,但new File(".").toURI()解析为本地路径,导致图片加载失败,请问该如何修改?


解决方案

1. 修正FopFactoryBuilder的基础URI

无需使用本地路径作为基础URI,直接传入你的服务基础URI(如http://localhost:3000/),这样FOP会以该URI为基准处理资源路径。修改调用代码:

// 替换原new File(".").toURI(),使用传入的base作为基础URI
FopFactoryBuilder builder = new FopFactoryBuilder(URI.create(base), resolver);

2. 修复CustomResourceResolver中的笔误

代码中new_uri_uri是错误变量名,改为new_uri:

logger.log(Level.INFO, "loading: ({0})", new_uri);

3. 避免输入流过早关闭

原代码在finally块中直接断开urlConnection,会导致返回的InputStream被提前关闭。自定义一个流包装类,在流实际关闭时再断开连接:

// 在CustomResourceResolver类内部添加该包装类
private class CloseTrackingInputStream extends BufferedInputStream {
    private HttpURLConnection connection;

    public CloseTrackingInputStream(InputStream in, HttpURLConnection connection) {
        super(in);
        this.connection = connection;
    }

    @Override
    public void close() throws IOException {
        try {
            super.close();
        } finally {
            connection.disconnect();
        }
    }
}

4. 关联资源的正确URI

返回Resource时传入目标URI,让FOP能正确识别资源标识:

InputStream in = new CloseTrackingInputStream(urlConnection.getInputStream(), urlConnection);
return new Resource(in, targetUri); // targetUri是拼接后的完整URI

最终修改后的getResource方法片段

@Override
public Resource getResource(URI uri) throws IOException {          
    HttpURLConnection urlConnection = null;
    try {
      String href = uri.toString(); // 改用toString避免getRawPath丢失路径信息

      String new_uri;
      if (href.startsWith("http://") || href.startsWith("https://")) {
          new_uri = href;
      } else {
          if (!href.startsWith("/") && !base.endsWith("/")) {
              base = base + "/";
          }
          new_uri = base + href;
      }
      
      URI targetUri = new URI(new_uri);
      logger.log(Level.INFO, "loading: ({0})", new_uri);
      URL url = targetUri.toURL();
      
      urlConnection = (HttpURLConnection) url.openConnection();
      if(session!= null){
          urlConnection.setRequestProperty("Cookie", cookie + "=" + session);
      }
      if(authorization != null){
          urlConnection.setRequestProperty("Fop-authorization", authorization );
      }
      if(user_id != null){
          urlConnection.setRequestProperty("Fop-user",user_id );
      }
      
      InputStream in = new CloseTrackingInputStream(urlConnection.getInputStream(), urlConnection);
      return new Resource(in, targetUri);
      
    } catch (MalformedURLException | URISyntaxException e) {
        logger.log(Level.SEVERE, null, e);
        throw new IOException("Failed to resolve resource", e);
    }
}

内容的提问来源于stack exchange,提问作者Ben Price

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 16:24:54