web-dev-qa-db-ja.com

p:graphicImageと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;
}

これは空白の画像を返します。これはどのように引き起こされ、どうすれば解決できますか?

Stdoutは次を出力します。

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

_<p:graphicImage>_には特別なgetterメソッドが必要です。つまり、生成されたイメージごとに2回呼び出され、それぞれ完全に異なるHTTP要求で呼び出されます。

JSFページのHTML結果を要求した最初のHTTPリクエストは、src属性に正しい一意の自動生成されたURLを持つHTML _<img>_要素を生成するために、初めてゲッターを呼び出します。 webbrowserが画像を要求しようとするたびに、どのbeanとgetterが正確に呼び出されるべきかに関する情報が含まれています。ゲッターはこの時点でnotイメージのコンテンツを返す必要があることに注意してください。それはHTMLの仕組みではないため、決して使用されません(画像はHTML出力では「インライン化」されませんが、代わりに個別に要求されます)。

Webブラウザーは、HTMLの結果をHTTP応答として取得すると、結果を視覚的にエンドユーザーに提示するためにHTMLソースを解析します。 HTMLソースの解析中にwebbrowserが_<img>_要素を検出すると、その画像のコンテンツをダウンロードしてビジュアルに埋め込むために、src属性で指定されたURLで新しいHTTPリクエストを送信しますプレゼンテーション。これにより、ゲッターメソッドが2回目に呼び出され、actualイメージコンテンツが返されます。

特定のケースでは、PrimeFacesは実際の画像コンテンツを取得するためにゲッターを識別して呼び出すことができなかったか、またはゲッターが予期した画像コンテンツを返さなかったようです。 _#{item}_変数名の使用とログ内の多くの呼び出しは、_<ui:repeat>_または_<h:dataTable>_で使用していたことを示唆しています。最も可能性が高いのは、バッキングBeanのスコープがリクエストであり、イメージのリクエスト中にデータモデルが適切に保存されず、JSFが正しい反復ラウンド中にゲッターを呼び出すことができないことです。また、ブラウザが実際に画像を要求するときにJSFビューステートが利用できないため、ビュースコープBeanも機能しません。


この問題を解決するための最善の方法は、いくつかのバッキングBeanプロパティに依存する代わりに、一意の画像識別子を_<f:param>_として渡すリクエストごとに呼び出すことができるように、getterメソッドを書き換えることです後続のHTTPリクエスト中に「非同期」になる可能性があります。このために、状態を持たない別のアプリケーションスコープのマネージドBeanを使用することは完全に意味があります。さらに、InputStreamは複数回ではなく、1回だけ読み取ることができます。

つまり、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>_が内部でどのように機能するかを考慮して、getterメソッドでビジネスロジックを実行することが完全に合法である非常に特殊なケースであることに注意してください。つまり、ゲッターでのビジネスロジックの呼び出しは、通常は眉をひそめています。 JSFがゲッターを複数回呼び出す理由 も参照してください。この特別なケースを、他の標準的な(特別でない)ケースの言い訳として使用しないでください。この引数は画像URLには含まれないため、#{studentImages.image(student.id)}のようなメソッド引数を渡すEL 2.2機能を利用できないことにも注意してください。したがって、本当に_<f:param>_として渡す必要があります。


偶然 OmniFaces 2.0以降 を使用している場合は、代わりに _<o:graphicImage>_ を使用することを検討してください。サービスメソッドとサポートする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();
    }

}
_

この件に関する ブログ も参照してください。

92
BalusC

MIMEタイプを含めてみてください。投稿した例では、 ""になっています。空白の画像は、そのフィールドを空の文字列にしたため、ストリームが画像ファイルとして認識されないことが原因である可能性があります。したがって、MIMEタイプのimage/pngまたはimage/jpgを追加し、それが機能するかどうかを確認します。

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

ここにはいくつかの可能性があります(そうでない場合はクラス全体を投稿してください)。

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 []が空であるか、フォーマットが間違っている場合に何かが発生する可能性があると考えられます。または、コンテンツタイプの問題がある場合もあります。

最後に、student.getImage()が新しい配列にのみコピーされて使用されるように、Beanからデータを複製します。このように、何か不明なことが起こっている場合(オブジェクトを移動したり、byte []を変更したりする場合)、ストリームをいじることはありません。

次のようなことをしてください:

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

beanにコピー(またはArrays.copy()-ボートに浮かぶもの)が含まれるようにします。私は、この/コンテンツタイプのような単純なものが通常どのように間違っているかを十分に強調することはできません。頑張ってください。

4

BalusC からの答えは(いつものように)正しいものです。

しかし、(彼がすでに述べたように)1つのことを念頭に置いてください。ブラウザから最後のリクエストが行われ、構築された<img> 鬼ごっこ。これは「jsfコンテキスト」では行われません。

だから、例えばphaseIdにアクセスします(ロギングまたはその他の理由)

context.getCurrentPhaseId().getName()

これによりNullPointerExceptionが生成され、何らかの誤解を招くエラーメッセージが表示されます。

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

問題が何であるかを理解するのにかなり時間がかかりました。

3
morecore