【问题标题】:Display dynamic image from database or remote source with p:graphicImage and StreamedContent使用 p:graphicImage 和 StreamedContent 显示来自数据库或远程源的动态图像
【发布时间】:2017-02-03 06:52:21
【问题描述】:

我正在尝试在<p:graphicImage> 中显示以StreamedContent 形式保存在数据库中的图像字节,如下所示:

<p:graphicImage  value="#{item.imageF}" width="50"  id="grpImage" height="80"/>
private StreamedContent content; // getter and setter

public StreamedContent getImageF() {

    if (student.getImage() != null) {
        InputStream is = new ByteArrayInputStream(student.getImage());
        System.out.println("Byte :"+student.getImage());
        content = new DefaultStreamedContent(is, "", student.getStuID());
        System.out.println("ddd ------------------------------- " + content);
        return content;
    }

    return content;
}

这将返回一个空白图像。这是如何引起的,我该如何解决?

标准输出打印以下内容:

INFO: Byte :[B@a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@b0887b
INFO: Byte :[B@a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1d06a92
INFO: Byte :[B@d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@39a60
INFO: Byte :[B@d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@8c3daa
INFO: Byte :[B@124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1dbe05b
INFO: Byte :[B@124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@66a266
INFO: Byte :[B@a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1293976
INFO: Byte :[B@a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@17b7399
INFO: Byte :[B@d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1e245a5
INFO: Byte :[B@d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@4a7153
INFO: Byte :[B@124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1561bfd
INFO: Byte :[B@124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@47a8c2

【问题讨论】:

    标签: database image jsf jsf-2 primefaces


    【解决方案1】:

    这里有几种可能性(如果不是,请发布整个课程)。

    1) 您没有正确初始化图像 2) 你的信息流是空的,所以你什么也得不到

    我假设 student.getImage() 具有 byte[] 的签名,因此首先确保该数据实际上是完整的并且代表图像。其次——你没有指定应该是“image/jpg”或你正在使用的任何内容类型。

    这里有一些样板代码可供检查,我正在使用 Primefaces 2。

    /** 'test' package with 'test/test.png' on the path */
    @RequestScoped
    @ManagedBean(name="imageBean")
    public class ImageBean
    {
        private DefaultStreamedContent content;
    
        public StreamedContent getContent()
        {
            if(content == null)
            {
                /* use your database call here */
                BufferedInputStream in = new BufferedInputStream(ImageBean.class.getClassLoader().getResourceAsStream("test/test.png"));
                ByteArrayOutputStream out = new ByteArrayOutputStream();
    
                int val = -1;
                /* this is a simple test method to double check values from the stream */
                try
                {
                    while((val = in.read()) != -1)
                        out.write(val);
                }
                catch(IOException e)
                {
                    e.printStackTrace();
                }
    
                byte[] bytes = out.toByteArray();
                System.out.println("Bytes -> " + bytes.length);
                content = new DefaultStreamedContent(new ByteArrayInputStream(bytes), "image/png", "test.png");
            }
    
            return content;
        }
    }
    

    还有一些标记...

    <html 
        xmlns="http://www.w3.org/1999/xhtml" 
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:p="http://primefaces.prime.com.tr/ui"
    >
    
        <h:head>
    
        </h:head>
    
        <h:body>
            <p:graphicImage value="#{imageBean.content}" />
        </h:body>
    </html>
    

    如果该代码有效,则说明您已正确设置。尽管事实上它是流的垃圾代码(不要在生产中使用它)它应该给你一个点来排除故障。我的猜测是,您的 JPA 或其他数据库框架中可能发生了某些事情,您的 byte[] 为空或格式错误。或者,您可能只是遇到内容类型问题。

    最后,我会从 bean 中克隆数据,这样 student.getImage() 只会被复制到一个新数组中然后使用。这样,如果您有一些未知的事情发生(移动对象或更改字节 [] 的其他事情,您就不会弄乱您的流。

    执行以下操作:

    byte[] data = new byte[student.getImage().length]
    for(int i = 0; i < data.length; i++)
      data[i] = student.getImage()[i];
    

    这样你的 bean 就有一个副本(或 Arrays.copy()——无论你的船如何漂浮)。我不能强调像这种/内容类型这样简单的东西通常是怎么回事。祝你好运。

    【讨论】:

      【解决方案2】:

      &lt;p:graphicImage&gt; 需要一个特殊的 getter 方法。即每个生成的图像都会调用两次,每次都在完全不同的 HTTP 请求中。

      第一个请求 JSF 页面的 HTML 结果的 HTTP 请求将第一次调用 getter,以便在 @987654327 中生成具有正确唯一且自动生成的 URL 的 HTML &lt;img&gt; 元素@ 属性,其中包含有关当 web 浏览器将要请求图像时应该调用哪个 bean 和 getter 的信息。请注意,此时 getter 不需要需要返回图像的内容。它不会以任何方式使用,因为这不是 HTML 的工作方式(图像不会在 HTML 输出中“内联”,而是单独请求)。

      一旦网络浏览器将 HTML 结果作为 HTTP 响应检索,它将解析 HTML 源,以便将结果直观地呈现给最终用户。一旦网络浏览器在解析 HTML 源代码期间遇到 &lt;img&gt; 元素,它将在其 src 属性中指定的 URL 上发送一个全新的 HTTP 请求,以下载该图像的内容并将其嵌入到视觉中介绍。这将第二次调用 getter 方法,该方法又应返回 实际 图像内容。

      您的特定情况 PrimeFaces 显然要么无法识别和调用 getter 以检索实际图像内容,要么 getter 没有返回预期的图像内容。 #{item} 变量名的使用和日志中的大量调用表明您在&lt;ui:repeat&gt;&lt;h:dataTable&gt; 中使用它。支持 bean 很可能是请求范围的,并且数据模型在请求图像期间没有正确保留,JSF 将无法在正确的迭代轮次中调用 getter。视图范围的 bean 也不会工作,因为当浏览器实际请求图像时,JSF 视图状态不可用。


      要解决这个问题,最好的办法是重写 getter 方法,以便可以在每个请求的基础上调用它,其中您将唯一的图像标识符作为 &lt;f:param&gt; 传递,而不是依赖于一些支持 bean在后续 HTTP 请求期间可能会“不同步”的属性。为此使用一个单独的应用程序范围的托管 bean 是完全有意义的,它没有任何状态。此外,InputStream 只能读取一次,不能多次读取。

      换句话说:永远不要将 StreamedContent 或任何 InputStream 甚至 UploadedFile 声明为 bean 属性;仅当网络浏览器实际请求图像内容时,才在无状态 @ApplicationScoped bean 的 getter 中创建全新的

      例如

      <p:dataTable value="#{bean.students}" var="student">
          <p:column>
              <p:graphicImage value="#{studentImages.image}">
                  <f:param name="studentId" value="#{student.id}" />
              </p:graphicImage>
          </p:column>
      </p:dataTable>
      

      StudentImages 支持 bean 可能如下所示:

      @Named // Or @ManagedBean
      @ApplicationScoped
      public class StudentImages {
      
          @EJB
          private StudentService service;
      
          public StreamedContent getImage() throws IOException {
              FacesContext context = FacesContext.getCurrentInstance();
      
              if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
                  // So, we're rendering the HTML. Return a stub StreamedContent so that it will generate right URL.
                  return new DefaultStreamedContent();
              }
              else {
                  // So, browser is requesting the image. Return a real StreamedContent with the image bytes.
                  String studentId = context.getExternalContext().getRequestParameterMap().get("studentId");
                  Student student = studentService.find(Long.valueOf(studentId));
                  return new DefaultStreamedContent(new ByteArrayInputStream(student.getImage()));
              }
          }
      
      }
      

      请注意,考虑到&lt;p:graphicImage&gt; 在幕后工作的方式,这是一个非常特殊的情况,其中在 getter 方法中执行业务逻辑是完全合法的。在 getter 中调用业务逻辑通常是不受欢迎的,另见Why JSF calls getters multiple times。不要将此特殊情况作为其他标准(非特殊)情况的借口。另请注意,您不能像#{studentImages.image(student.id)} 这样使用EL 2.2 传递方法参数的特性,因为这个参数不会出现在图像URL 中。因此,您确实需要将它们传递为&lt;f:param&gt;


      如果你碰巧使用了OmniFaces 2.0 or newer,那么考虑使用它的&lt;o:graphicImage&gt;,这样可以更直观地使用,应用程序范围的getter方法直接委托给服务方法并支持EL 2.2方法参数。

      这样:

      <p:dataTable value="#{bean.students}" var="student">
          <p:column>
              <o:graphicImage value="#{studentImages.getImage(student.id)}" />
          </p:column>
      </p:dataTable>
      

      @Named // Or @ManagedBean
      @ApplicationScoped
      public class StudentImages {
      
          @EJB
          private StudentService service;
      
          public byte[] getImage(Long studentId) {
              return studentService.find(studentId).getImage();
          }
      
      }
      

      另请参阅the blog 关于该主题。

      【讨论】:

      • BalusC,这正是我想要的,但我就是不知道应该如何将响应发送回客户端。你真是个传奇。非常感谢。 :)
      • @Tiny:EJB 和 CDI 托管 bean 作为代理注入。代理实例上的方法调用基本上将当前实例定位在其范围内,然后在其上调用所需的方法。您很容易在来自这些方法的异常的堆栈跟踪中看到这一点。因此,它们的范围与客户的范围无关,并且可以很容易地变小。
      • @Xtreme:考虑在为时已晚之前继续前进。
      • @Julios_Rodmax:数据不会保存为 bean 的实例变量以供将来重用。它在每次调用时都在 getter 方法中创建了全新的。事实上,您永远不应该将任何 InputStream 保存为实例变量以供将来重用。只能读一次!这不是 JSF 特有的,这只是基本的 Java。
      • @Julios_Rodmax:如果方法不同步,那么绝对没有延迟。 Java 支持多线程,你知道的。
      【解决方案3】:

      尝试包含 mime 类型。在您发布的示例中,您将其作为“”。空白图像可能是因为它无法将流识别为图像文件,因为您将该字段设置为空字符串。所以添加一个 mime 类型的 image/png 或 image/jpg 看看是否可行:

      String mimeType = "image/jpg";
      StreamedContent file = new DefaultStreamedContent(bytes, mimeType, filename);  
      

      【讨论】:

        【解决方案4】:

        BalusC 的答案(和往常一样)是正确的。

        但请记住一件事(正如他已经说过的)。最后的请求是从浏览器完成的,以从构造的&lt;img&gt; 标记中获取 URL。这不是在“jsf 上下文”中完成的。

        因此,如果您尝试例如访问 phaseId(记录或任何原因)

        context.getCurrentPhaseId().getName()
        

        这将导致NullPointerException 并且您将收到的某种误导性错误消息是:

        org.primefaces.application.resource.StreamedContentHandler () - Error in streaming dynamic resource. Error reading 'image' on type a.b.SomeBean
        

        我花了很长时间才弄清楚问题出在哪里。

        【讨论】:

          猜你喜欢
          • 2012-04-21
          • 1970-01-01
          相关资源
          最近更新 更多