web-dev-qa-db-de.com

Zeigen Sie dynamisches Bild aus der Datenbank mit p: graphicImage und StreamedContent an

Ich versuche, Bildbytes anzuzeigen, die in der Datenbank als StreamedContent im <p:graphicImage> Gespeichert sind:

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

Dies gibt ein leeres Bild zurück. Wie entsteht das und wie kann ich es lösen?

Die Standardausgabe gibt Folgendes aus:

INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
47
minhltnt

Das <p:graphicImage> Erfordert eine spezielle Getter-Methode. Es wird nämlich zweimal pro erzeugtem Bild aufgerufen, und zwar jeweils in einer völlig anderen HTTP-Anfrage.

Die erste HTTP-Anforderung, die das HTML-Ergebnis einer JSF-Seite angefordert hat, ruft den Getter zum ersten Mal auf, um das HTML-Element <img> Mit der richtigen eindeutigen und automatisch generierten URL in src-Attribut, das Informationen darüber enthält, welche Bean und welcher Getter genau aufgerufen werden sollen, wenn der Webbrowser das Image anfordert. Beachten Sie, dass der Getter in diesem Moment nicht den Inhalt des Bildes zurückgeben muss. Es wird in keiner Weise verwendet, da HTML auf diese Weise nicht funktioniert (Bilder werden in der HTML-Ausgabe nicht "inline" dargestellt, sondern separat angefordert).

Sobald der Webbrowser das HTML-Ergebnis als HTTP-Antwort abruft, analysiert er die HTML-Quelle, um das Ergebnis dem Endbenutzer visuell darzustellen. Sobald der Webbrowser beim Parsen der HTML-Quelle auf ein <img> - Element stößt, sendet er eine brandneue HTTP-Anfrage an die URL, die im Attribut src angegeben ist, um den Inhalt dieses Bildes herunterzuladen und in die visuelle Präsentation einbetten. Dadurch wird die Getter-Methode zum zweiten Mal aufgerufen, die wiederum den Bildinhalt actual zurückgeben sollte.

In Ihrem speziellen Fall war PrimeFaces anscheinend nicht in der Lage, den Getter zu identifizieren und aufzurufen, um den tatsächlichen Bildinhalt abzurufen, oder der Getter hat den erwarteten Bildinhalt nicht zurückgegeben. Die Verwendung des Variablennamens #{item} Und die Anzahl der Aufrufe im Protokoll deuten darauf hin, dass Sie ihn in einem <ui:repeat> Oder einem <h:dataTable> Verwendet haben. Höchstwahrscheinlich ist die Backing-Bean anforderungsabhängig und das Datenmodell wird während der Anforderung für das Image nicht ordnungsgemäß beibehalten, und JSF kann den Getter während der richtigen Iterationsrunde nicht aufrufen. Ein View Scoped Bean würde auch nicht funktionieren, da der JSF-Ansichtsstatus nirgends verfügbar ist, wenn der Browser das Bild tatsächlich anfordert.


Um dieses Problem zu lösen, sollten Sie die Getter-Methode so umschreiben, dass sie auf Anfragebasis aufgerufen werden kann, wobei Sie die eindeutige Bildkennung als <f:param> Übergeben, anstatt sich auf eine Hintergrundbohne zu verlassen Eigenschaften, die bei nachfolgenden HTTP-Anforderungen möglicherweise nicht mehr synchron sind. Es ist durchaus sinnvoll, hierfür eine separate Application Scoped Managed Bean zu verwenden, die keinen Status hat. Darüber hinaus kann ein InputStream nur einmal und nicht mehrmals gelesen werden.

Mit anderen Worten: deklarieren Sie niemals StreamedContent oder InputStream oder sogar UploadedFile als Bean-Eigenschaft; erst wenn der Webbrowser den Bildinhalt tatsächlich anfordert , kann er im Getter einer zustandslosen @ApplicationScoped - Bean fabrikneu erstellt werden.

Z.B.

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

Wo die Backing Bean StudentImages so aussehen kann:

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

}

Bitte beachten Sie, dass dies ein sehr spezieller Fall ist, in dem das Ausführen von Geschäftslogik in einer Getter-Methode völlig legitim ist, wenn man bedenkt, wie <p:graphicImage> Im Verborgenen funktioniert. Das Aufrufen von Geschäftslogik in Gettern wird normalerweise verpönt, siehe auch Warum JSF Getter mehrmals aufruft . Verwenden Sie diesen Sonderfall nicht als Entschuldigung für andere Standardfälle. Bitte beachten Sie auch, dass Sie die EL 2.2-Funktion zum Übergeben von Methodenargumenten wie z. B. #{studentImages.image(student.id)} nicht verwenden können, da dieses Argument nicht in der Bild-URL endet. Daher müssen Sie sie wirklich als <f:param> Übergeben.


Wenn Sie OmniFaces 2.0 oder neuer verwenden, sollten Sie die Verwendung von <o:graphicImage> in Betracht ziehen, die intuitiver verwendet werden kann, wobei eine Getter-Methode mit Anwendungsbereich direkt delegiert wird zu der Dienstmethode und unterstützenden EL 2.2-Methodenargumenten.

Also so:

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

Mit

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

    @EJB
    private StudentService service;

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

}

Siehe auch das Blog zum Thema.

92
BalusC

Versuchen Sie es mit einem MIME-Typ. In Ihrem Beispiel haben Sie es als "". Das leere Bild kann daran liegen, dass es den Stream nicht als Bilddatei erkennt, da Sie dieses Feld zu einer leeren Zeichenfolge gemacht haben. Fügen Sie also einen MIME-Typ von image/png oder image/jpg hinzu und prüfen Sie, ob dies funktioniert:

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

Hier gibt es ein paar Möglichkeiten (und bitte posten Sie die gesamte Klasse, wenn dies nicht der Fall ist).

1) Sie initialisieren das Bild nicht richtig. 2) Ihr Stream ist leer und Sie erhalten nichts

Ich gehe davon aus, dass student.getImage () eine Signatur von byte [] hat. Stellen Sie daher zunächst sicher, dass die Daten tatsächlich intakt sind und ein Bild darstellen. Zweitens - Sie geben keinen Inhaltstyp an, der "image/jpg" sein soll, oder was auch immer Sie verwenden.

Hier ist ein Code, mit dem Sie das überprüfen können. Hierfür verwende ich 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;
    }
}

und etwas Aufschlag ...

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

Wenn dieser Code funktioniert, sind Sie richtig eingerichtet. Trotz der Tatsache, dass es sich um Garbage Code für die Streams handelt (in der Produktion nicht verwenden) sollte es Ihnen einen Punkt geben, von dem aus Sie Fehler beheben können. Ich vermute, dass in Ihrer JPA oder einem anderen Datenbank-Framework möglicherweise etwas passiert, in dem Byte [] leer oder falsch formatiert ist. Alternativ könnten Sie nur ein Problem mit dem Inhaltstyp haben.

Zuletzt würde ich die Daten aus dem Bean klonen, so dass student.getImage () nur in ein neues Array kopiert und dann verwendet würde. Auf diese Weise werden Ihre Streams nicht gestört, wenn etwas Unbekanntes passiert (etwas anderes bewegt das Objekt oder ändert das Byte [].

Mach etwas wie:

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

damit Ihre Bohne eine Kopie hat (oder Arrays.copy () - was auch immer Ihr Boot schwimmt). Ich kann nicht genug betonen, wie etwas Einfaches wie dieser/Inhaltstyp normalerweise falsch ist. Viel Glück damit.

4

Die Antwort von BalusC ist (wie immer) die richtige.

Beachten Sie jedoch eine Sache (wie bereits von ihm angegeben). Die letzte Anforderung wird vom Browser ausgeführt, um die URL vom konstruierten <img> - Tag abzurufen. Dies erfolgt nicht in einem 'jsf-Kontext'.

Wenn Sie also versuchen, z. Zugriff auf die phaseId (Protokollierung oder aus welchem ​​Grund auch immer)

context.getCurrentPhaseId().getName()

Dies führt zu einem NullPointerException und die irgendwie irreführende Fehlermeldung lautet:

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

Ich habe einige Zeit gebraucht, um herauszufinden, was das Problem war.

3
morecore