繁体   English   中英

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

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

我正在尝试在<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

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

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

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

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


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

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

}

请注意,这是一种非常特殊的情况,考虑到<p:graphicImage><p:graphicImage>工作的方式,在 getter 方法中执行业务逻辑是完全合法的。 在 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[] 的其他事情),您就不会弄乱您的流。

做类似的事情:

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

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

暂无
暂无

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

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