使用p:graphicImage和StreamedContent显示来自数据库或远程源的动态图像



我试图显示图像字节,这是保存在数据库中的StreamedContent<p:graphicImage>如下:

<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

<p:graphicImage>需要一个特殊的getter方法。也就是说,每个生成的图像将被调用两次,每次都在一个完全不同的HTTP请求中。

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

一旦web浏览器将HTML结果检索为HTTP响应,它将解析HTML源,以便将结果可视化地呈现给最终用户。一旦web浏览器在解析HTML源时遇到<img>元素,它就会在其src属性中指定的URL上发送一个全新的HTTP请求,以便下载该图像的内容并将其嵌入到可视化表示中。这将第二次调用getter方法,该方法将返回实际的图像内容。

您的特殊情况下, PrimeFaces显然无法识别和调用getter来检索实际的图像内容,或者getter没有返回预期的图像内容。#{item}变量名的使用和日志中的大量调用表明您在<ui:repeat><h:dataTable>中使用了它。最可能的情况是,支持bean是请求作用域的,并且在请求图像期间没有正确地保存数据模型,JSF将无法在正确的迭代周期中调用getter。视图作用域的bean也不能工作,因为当浏览器实际请求图像时,JSF视图状态无处可用。


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

换句话说:永远不要将StreamedContent或任何InputStream甚至UploadedFile声明为bean属性;只有当web浏览器实际请求图像内容时,才在无状态@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()));
        }
    }
}

请注意,这是一个非常特殊的情况,在getter方法中执行业务逻辑是完全合法的,考虑到<p:graphicImage>是如何在幕后工作的。通常不赞成在getter中调用业务逻辑,请参见为什么JSF多次调用getter。不要用这种特殊情况作为其他标准(非特殊)情况的借口。还请注意,您不能使用EL 2.2传递方法参数的特性,如#{studentImages.image(student.id)},因为该参数不会在图像URL中结束。因此,您确实需要将它们作为<f:param>传递。


如果你碰巧使用的是OmniFaces 2.0或更新版本,那么可以考虑使用它的<o:graphicImage>,它可以更直观地使用,具有应用范围的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();
    }
}

请参阅有关该主题的博客。

尝试包含mime类型。在你张贴的例子中,你把它作为"。空白图像可能是因为它不将流识别为图像文件,因为您将该字段设置为空字符串。所以添加一个mime类型image/png或image/jpg,看看是否有效:

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

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

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()——随你怎么想)。我必须强调,像这样简单的内容类型通常是错误的。祝你好运。

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

但是记住一件事(正如他已经说过的)。最后的请求是从浏览器中完成的,从构造的<img>标记获取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

我花了好长时间才找出问题所在。

最新更新