使用 p:graphicImage 和 StreamedContent 显示来自数据库或远程源的动态图像 [英] Display dynamic image from database or remote source with p:graphicImage and StreamedContent

查看:18
本文介绍了使用 p:graphicImage 和 StreamedContent 显示来自数据库或远程源的动态图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在 中显示作为 StreamedContent 保存在数据库中的图像字节,如下所示:

私有StreamedContent内容;//获取器和设置器公共流内容 getImageF() {如果 (student.getImage() != null) {InputStream is = new ByteArrayInputStream(student.getImage());System.out.println("字节:"+student.getImage());content = new DefaultStreamedContent(is, "", student.getStuID());System.out.println("ddd ------------------------------- " + content);返回内容;}返回内容;}

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

标准输出打印以下内容:

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

解决方案

需要一个特殊的 getter 方法.也就是说,每个生成的图像都会被调用两次,每次都在一个完全不同的 HTTP 请求中.

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

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

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

<小时>

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

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

例如

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

其中 StudentImages 支持 bean 看起来像这样:

@Named//或者@ManagedBean@ApplicationScoped公共类 StudentImages {@EJB私人学生服务服务;public StreamedContent getImage() 抛出 IOException {FacesContext 上下文 = FacesContext.getCurrentInstance();if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {//所以,我们正在渲染 HTML.返回一个存根 StreamedContent 以便它生成正确的 URL.返回新的 DefaultStreamedContent();}别的 {//因此,浏览器正在请求图像.返回带有图像字节的真实 StreamedContent.String studentId = context.getExternalContext().getRequestParameterMap().get("studentId");学生学生 = studentService.find(Long.valueOf(studentId));返回新的 DefaultStreamedContent(new ByteArrayInputStream(student.getImage()));}}}

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

<小时>

如果您碰巧使用 OmniFaces 2.0 或更新版本,请考虑使用其 <o:graphicImage> 可以更直观地使用,应用程序范围的 getter 方法直接委托给服务方法和支持 EL 2.2 方法参数.

因此:

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

@Named//或者@ManagedBean@ApplicationScoped公共类 StudentImages {@EJB私人学生服务服务;公共字节[] getImage(Long studentId) {返回 studentService.find(studentId).getImage();}}

另请参阅关于该主题的博客.>

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

解决方案

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.

这篇关于使用 p:graphicImage 和 StreamedContent 显示来自数据库或远程源的动态图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆