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

发布于 2024-12-17 09:58:36 字数 2263 浏览 3 评论 0原文

我正在尝试显示在 中作为 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

I'm trying to display image bytes which is saved in database as a StreamedContent in the <p:graphicImage> as follows:

<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;
}

This returns a blank image. How is this caused and how can I solve it?

The stdout prints the following:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

臻嫒无言 2024-12-24 09:58:36

需要特殊的 getter 方法。即每个生成的图像将被调用两次,每次都在完全不同的 HTTP 请求中。

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

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

在您的特定情况下,PrimeFaces 显然无法识别和调用 getter 来检索实际图像内容,或者 getter 没有返回预期的图像内容。 #{item} 变量名称的使用以及日志中的大量调用表明您在 中使用它。最有可能的是,支持 bean 是请求范围的,并且在请求图像期间未正确保留数据模型,并且 JSF 将无法在正确的迭代期间调用 getter。视图作用域 bean 也不起作用,因为当浏览器实际请求图像时,JSF 视图状态不可用。


要解决这个问题,最好的办法是重写 getter 方法,以便可以在每个请求的基础上调用它,其中您将唯一的图像标识符作为 传递而不是依赖某些可能在后续 HTTP 请求期间“不同步”的支持 bean 属性。为此使用一个没有任何状态的单独的应用程序范围的托管 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()));
        }
    }

}

请注意,这是一个非常特殊的情况,其中在 getter 方法中执行业务逻辑是完全合法的,考虑到 如何; 在幕后工作。在 getters 中调用业务逻辑通常是不受欢迎的,另请参阅 为什么 JSF 调用多次吸气剂。不要使用此特殊情况作为其他标准(非特殊)情况的借口。另请注意,您无法使用 EL 2.2 传递方法参数的功能,例如 #{studentImages.image(student.id)} 因为此参数不会最终出现在图像 URL 中。因此,您确实需要将它们作为 传递。


如果您碰巧使用 OmniFaces 2.0 或更高版本,请考虑使用其 相反,可以更直观地使用,将应用程序范围的 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 <p:graphicImage> requires a special getter method. It will namely be invoked twice per generated image, each in a completely different HTTP request.

The first HTTP request, which has requested the HTML result of a JSF page, will invoke the getter for the first time in order to generate the HTML <img> element with the right unique and auto-generated URL in the src attribute which contains information about which bean and getter exactly should be invoked whenever the webbrowser is about to request the image. Note that the getter does at this moment not need to return the image's contents. It would not be used in any way as that's not how HTML works (images are not "inlined" in HTML output, but they are instead requested separately).

Once the webbrowser retrieves the HTML result as HTTP response, it will parse the HTML source in order to present the result visually to the enduser. Once the webbrowser encounters an <img> element during parsing the HTML source, then it will send a brand new HTTP request on the URL as specified in its src attribute in order to download the content of that image and embed it in the visual presentation. This will invoke the getter method for the second time which in turn should return the actual image content.

In your particular case PrimeFaces was apparently either unable to identify and invoke the getter in order to retrieve the actual image content, or the getter didn't return the expected image content. The usage of #{item} variable name and the lot of calls in the log suggests that you were using it in an <ui:repeat> or a <h:dataTable>. Most likely the backing bean is request scoped and the datamodel isn't properly preserved during the request for the image and JSF won't be able to invoke the getter during the right iteration round. A view scoped bean would also not work as the JSF view state is nowhere available when the browser actually requests the image.


To solve this problem, your best bet is to rewrite the getter method as such so that it can be invoked on a per-request basis wherein you pass the unique image identifier as a <f:param> instead of relying on some backing bean properties which may go "out of sync" during subsequent HTTP requests. It would make completely sense to use a separate application scoped managed bean for this which doesn't have any state. Moreover, an InputStream can be read only once, not multiple times.

In other words: never declare StreamedContent nor any InputStream or even UploadedFile as a bean property; only create it brand-new in the getter of a stateless @ApplicationScoped bean when the webbrowser actually requests the image content.

E.g.

<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>

Where the StudentImages backing bean can look like this:

@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()));
        }
    }

}

Please note that this is a very special case wherein performing business logic in a getter method is completely legit, considering how the <p:graphicImage> works under the covers. Invoking business logic in getters is namely usually frowned upon, see also Why JSF calls getters multiple times. Don't use this special case as excuse for other standard (non-special) cases. Please also note that you can't make use of EL 2.2 feature of passing method arguments like so #{studentImages.image(student.id)} because this argument won't end up in the image URL. Thus you really need to pass them as <f:param>.


If you happen to use OmniFaces 2.0 or newer, then consider using its <o:graphicImage> instead which can be used more intuitively, with an application scoped getter method directly delegating to the service method and supporting EL 2.2 method arguments.

Thus so:

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

With

@Named // Or @ManagedBean
@ApplicationScoped
public class StudentImages {

    @EJB
    private StudentService service;

    public byte[] getImage(Long studentId) {
        return studentService.find(studentId).getImage();
    }

}

See also the blog on the subject.

零度° 2024-12-24 09:58:36

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

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

Try including a mime type. In your posted example, you have it as "". The blank image may be because it doesn't recognize the stream as a image file since you made that field an empty string. So add a mime type of image/png or image/jpg and see if that works:

String mimeType = "image/jpg";
StreamedContent file = new DefaultStreamedContent(bytes, mimeType, filename);  
彡翼 2024-12-24 09:58:36

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

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[] 的事情,您就不会弄乱您的流。

执行类似的操作:

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

以便您的 bean 拥有一个副本(或 Arrays.copy()--无论你的船漂浮什么)我都无法强调像这样的简单内容/内容类型通常是什么问题。

There's a couple possibilities here (and please post the entire class if this isn't it).

1) You're not initializing the image properly
2) Your stream is empty so you're getting nothing

I'm assuming student.getImage() has a signature of byte[] so first make sure that that data is actually intact and represents an image. Secondly--you're not specifying a content-type which should be "image/jpg" or whatever you're using.

Here's some boilerplate code to check it with, I'm using Primefaces 2 for this.

/** '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;
    }
}

and some markup...

<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>

If that code works then you're set up properly. Despite the fact it is garbage code for the streams (don't use it in production) it should give you a point to troubleshoot from. My guess is that you might have something happening in your JPA or other Database framework where you're byte[] is empty or it is formatted wrong. Alternatively you could just have a content-type problem.

Lastly, I would clone the data from the bean so that student.getImage() would only be copied into a new array and then used. This way if you have something unknown going on (something else moving the object or changing the byte[] you're not messing with your streams.

Do something like:

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

so that your bean has a copy (or Arrays.copy()--whatever floats your boat). I can't stress enough how something simple like this/content type is usually what's wrong. Good luck with it.

凉墨 2024-12-24 09:58:36

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

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

我花了相当长的时间才弄清楚是什么问题。

The answer from BalusC is (as usual) the correct one.

But keep one thing (as already stated by him) in mind. The final request is done from the browser to get the URL from the constructed <img> tag. This is not done in a 'jsf context'.

So if you try to e.g. access the phaseId (logging or whatever reason)

context.getCurrentPhaseId().getName()

This will result in a NullPointerException and the somehow misleading error message you will get is:

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

It took me quite some time to figure out what was the problem.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文