简体   繁体   English

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

[英]Display dynamic image from database or remote source with p:graphicImage and StreamedContent

I'm trying to display image bytes which is saved in database as a StreamedContent in the <p:graphicImage> as follows:我正在尝试在<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;
}

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

The <p:graphicImage> requires a special getter method. <p:graphicImage>需要一个特殊的 getter 方法。 It will namely be invoked twice per generated image, each in a completely different HTTP request.即每个生成的图像将被调用两次,每次都在一个完全不同的 HTTP 请求中。

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.第一个请求 JSF 页面的 HTML 结果的 HTTP 请求将首次调用 getter 以生成 HTML <img>元素,该元素在包含信息的src属性中具有正确的唯一且自动生成的 URL每当 webbrowser 即将请求图像时,应该调用哪个 bean 和 getter。 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).它不会以任何方式使用,因为这不是 HTML 的工作方式(图像在 HTML 输出中不是“内联”的,而是单独请求的)。

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.一旦 webbrowser 检索 HTML 结果作为 HTTP 响应,它将解析 HTML 源以便将结果直观地呈现给最终用户。 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.一旦 webbrowser 在解析 HTML 源代码期间遇到<img>元素,它就会在其src属性中指定的 URL 上发送一个全新的 HTTP 请求,以便下载该图像的内容并将其嵌入到视觉呈现中。 This will invoke the getter method for the second time which in turn should return the actual image content.这将第二次调用 getter 方法,该方法应该返回实际的图像内容。

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.您的特定情况下, PrimeFaces 显然无法识别和调用 getter 以检索实际图像内容,或者 getter 没有返回预期的图像内容。 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> . #{item}变量名的用法和日志中的大量调用表明您在<ui:repeat><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.最有可能的是,支持 bean 是请求范围的,并且在请求图像期间没有正确保留数据模型,并且 JSF 将无法在正确的迭代轮中调用 getter。 A view scoped bean would also not work as the JSF view state is nowhere available when the browser actually requests the image.视图作用域 bean 也不会工作,因为当浏览器实际请求图像时,JSF 视图状态在任何地方都不可用。


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.要解决这个问题,最好的办法是重写 getter 方法,以便可以在每个请求的基础上调用它,其中您将唯一图像标识符作为<f:param>传递,而不是依赖某些支持 bean 属性这可能会在后续 HTTP 请求期间“不同步”。 It would make completely sense to use a separate application scoped managed bean for this which doesn't have any state.为此使用单独的应用程序范围的托管 bean 是完全有意义的,它没有任何状态。 Moreover, an InputStream can be read only once, not multiple times.此外,一个InputStream只能读取一次,不能多次读取。

In other words: never declare StreamedContent nor any InputStream or even UploadedFile as a bean property;换句话说:永远不要将StreamedContent或任何InputStream甚至UploadedFile为 bean 属性; only create it brand-new in the getter of a stateless @ApplicationScoped bean when the webbrowser actually requests the image content .只有当 webbrowser 实际请求图像内容时,才在无状态@ApplicationScoped bean 的 getter 中创建它

Eg例如

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

}

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.请注意,这是一种非常特殊的情况,考虑到<p:graphicImage><p:graphicImage>工作的方式,在 getter 方法中执行业务逻辑是完全合法的。 Invoking business logic in getters is namely usually frowned upon, see also Why JSF calls getters multiple times .在 getter 中调用业务逻辑通常是不受欢迎的,另请参阅为什么 JSF 多次调用 getter 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.另请注意,您不能像这样使用 EL 2.2 传递方法参数的特性#{studentImages.image(student.id)}因为这个参数不会在图像 URL 中结束。 Thus you really need to pass them as <f:param> .因此,您确实需要将它们作为<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.如果您碰巧使用OmniFaces 2.0 或更新版本,请考虑使用它的<o:graphicImage>代替,它可以更直观地使用,应用程序范围的 getter 方法直接委托给服务方法并支持 EL 2.2 方法参数。

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.另请参阅有关该主题 的博客

Try including a mime type.尝试包含一个 mime 类型。 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:所以添加一个 mime 类型的 image/png 或 image/jpg 看看是否有效:

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

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 1) 你没有正确初始化图像 2) 你的流是空的,所以你什么也没得到

I'm assuming student.getImage() has a signature of byte[] so first make sure that that data is actually intact and represents an image.我假设 student.getImage() 具有 byte[] 的签名,因此首先确保该数据实际上是完整的并代表图像。 Secondly--you're not specifying a content-type which should be "image/jpg" or whatever you're using.其次——你没有指定一个应该是“image/jpg”或任何你正在使用的内容类型。

Here's some boilerplate code to check it with, I'm using Primefaces 2 for this.这是一些用于检查的样板代码,我为此使用了 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;
    }
}

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.我的猜测是,您的 JPA 或其他数据库框架中可能发生了一些事情,其中​​ byte[] 为空或格式错误。 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.最后,我将从 bean 中克隆数据,以便 student.getImage() 只会被复制到一个新数组中然后使用。 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.这样,如果您有一些未知的事情发生(移动对象或更改 byte[] 的其他事情),您就不会弄乱您的流。

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).这样你的 bean 就有一个副本(或 Arrays.copy()——无论你的船漂浮在什么地方)。 I can't stress enough how something simple like this/content type is usually what's wrong.我怎么强调像这样/内容类型这样简单的东西通常是错的。 Good luck with it.祝你好运。

The answer from BalusC is (as usual) the correct one. BalusC的答案(像往常一样)是正确的。

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.最后的请求是从浏览器完成的,以从构造的<img>标签中获取 URL。 This is not done in a 'jsf context'.这不是在“jsf 上下文”中完成的。

So if you try to eg access the phaseId (logging or whatever reason)因此,如果您尝试访问例如 phaseId(日志记录或任​​何原因)

context.getCurrentPhaseId().getName()

This will result in a NullPointerException and the somehow misleading error message you will get is:这将导致NullPointerException并且您将获得的某种误导性错误消息是:

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.我花了很长时间才弄清楚问题出在哪里。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM