54 votes

Afficher une image dynamique à partir d'une base de données ou d'une source distante avec p:graphicImage et StreamedContent

J'essaie d'afficher les octets d'une image qui est sauvegardée dans la base de données en tant qu'un StreamedContent dans le <p:graphicImage> comme suit :

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

Cela renvoie une image vide. Comment cela se produit-il et comment puis-je le résoudre ?

Le stdout imprime ce qui suit :

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

108voto

BalusC Points 498232

El <p:graphicImage> nécessite une méthode getter spéciale. Elle sera notamment invoquée deux fois par image générée, chacune dans une requête HTTP complètement différente.

La première requête HTTP, qui a demandé le résultat HTML d'une page JSF, invoquera le getter pour la première fois afin de générer le HTML. <img> avec la bonne URL unique et générée automatiquement dans l'élément src qui contient des informations sur le bean et le getter qui doivent être invoqués chaque fois que le navigateur web est sur le point de demander l'image. Notez que le getter fait à ce moment no doivent retourner le contenu de l'image. Il ne serait pas utilisé de quelque manière que ce soit, car ce n'est pas ainsi que fonctionne le HTML (les images ne sont pas "intégrées" dans la sortie HTML, mais elles sont au contraire demandées séparément).

Une fois que le navigateur Web a récupéré le résultat HTML en tant que réponse HTTP, il analyse la source HTML afin de présenter le résultat visuellement à l'utilisateur final. Lorsque le navigateur web rencontre un <img> lors de l'analyse de la source HTML, il enverra une toute nouvelle requête HTTP sur l'URL spécifiée dans l'élément src afin de télécharger le contenu de cette image et de l'intégrer dans la présentation visuelle. La méthode getter sera invoquée pour la deuxième fois et renverra l'attribut réel contenu de l'image.

En votre cas particulier Apparemment, PrimeFaces était soit incapable d'identifier et d'invoquer le getter afin de récupérer le contenu réel de l'image, soit le getter n'a pas renvoyé le contenu de l'image attendu. L'utilisation de #{item} Le nom de la variable et le nombre d'appels dans le journal suggèrent que vous l'utilisiez dans un <ui:repeat> ou un <h:dataTable> . Il est fort probable que le bean de sauvegarde soit "request scoped" et que le modèle de données ne soit pas correctement préservé lors de la requête de l'image et que JSF ne soit pas en mesure d'invoquer le getter lors du bon tour d'itération. Un bean à portée de vue ne fonctionnerait pas non plus car l'état de la vue JSF n'est disponible nulle part lorsque le navigateur demande l'image.


Pour résoudre ce problème, le mieux est de réécrire la méthode getter de manière à ce qu'elle puisse être invoquée pour chaque demande, en passant l'identifiant unique de l'image en tant qu'objet <f:param> au lieu de s'appuyer sur les propriétés de certains beans de soutien qui peuvent être "désynchronisées" lors des requêtes HTTP suivantes. Il serait tout à fait logique d'utiliser pour cela un bean géré séparé, à portée applicative, qui n'a pas d'état. De plus, un InputStream ne peut être lu qu'une seule fois, pas plusieurs fois.

En d'autres termes : ne jamais déclarer StreamedContent ni aucun InputStream ou même UploadedFile en tant que propriété d'un bean ; ne la créez que dans le getter d'une propriété apatride. @ApplicationScoped lorsque le navigateur demande le contenu de l'image. .

Par exemple

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

Où le StudentImages Le haricot arrière peut ressembler à ça :

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

}

Veuillez noter qu'il s'agit d'un cas très particulier dans lequel l'exécution d'une logique d'entreprise dans une méthode getter est tout à fait légitime, compte tenu de la façon dont la méthode getter est utilisée. <p:graphicImage> travaille sous les couvertures. L'invocation de la logique métier dans les getters est généralement désapprouvée. Pourquoi JSF appelle les getters plusieurs fois . N'utilisez pas ce cas spécial comme excuse pour d'autres cas standard (non spéciaux). Veuillez également noter que vous ne pouvez pas utiliser la fonctionnalité EL 2.2 permettant de passer des arguments de méthode comme suit #{studentImages.image(student.id)} car cet argument n'apparaîtra pas dans l'URL de l'image. Ainsi, vous devez vraiment les passer en tant que <f:param> .


Si vous utilisez OmniFaces 2.0 ou plus récent alors envisagez d'utiliser son <o:graphicImage> qui peut être utilisé de manière plus intuitive, avec une méthode getter à l'échelle de l'application déléguant directement à la méthode du service et supportant les arguments de méthode EL 2.2.

Ainsi donc :

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

Avec

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

    @EJB
    private StudentService service;

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

}

Voir aussi le blog sur le sujet.

6voto

rcheuk Points 835

Essayez d'inclure un type de mime. Dans votre exemple, il s'agit de "". L'image vide peut être due au fait qu'il ne reconnaît pas le flux comme un fichier image puisque vous avez fait de ce champ une chaîne vide. Ajoutez donc un type mime de type image/png ou image/jpg et voyez si cela fonctionne :

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

5voto

Daniel B. Chapman Points 1963

Il y a plusieurs possibilités ici (et s'il vous plaît, postez la classe entière si ce n'est pas ça).

1) Vous n'initialisez pas l'image correctement 2) Votre flux est vide et vous ne recevez rien.

Je suppose que student.getImage() a une signature de byte[], donc assurez-vous d'abord que ces données sont effectivement intactes et représentent une image. Deuxièmement, vous ne spécifiez pas un type de contenu qui devrait être "image/jpg" ou ce que vous utilisez.

Voici un code standard pour le vérifier, j'utilise Primefaces 2 pour cela.

/** '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;
    }
}

et quelques balises...

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

Si ce code fonctionne, c'est que vous êtes bien installé. Malgré le fait que ce soit du code poubelle pour les flux (ne l'utilisez pas en production) cela devrait vous donner un point de départ pour le dépannage. Je pense qu'il se passe quelque chose dans votre JPA ou autre framework de base de données où votre byte[] est vide ou mal formaté. Il se peut aussi que vous ayez simplement un problème de type de contenu.

Enfin, je clonerais les données du haricot afin que student.getImage() soit seulement copié dans un nouveau tableau et ensuite utilisé. De cette façon, si quelque chose d'inconnu se produit (quelque chose d'autre déplaçant l'objet ou changeant le byte[]), vous ne perturbez pas vos flux.

Fais quelque chose comme :

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

afin que votre haricot ait une copie (ou Arrays.copy()--quel que soit votre choix). Je ne peux pas insister assez sur le fait que quelque chose de simple comme ceci/type de contenu est généralement ce qui ne va pas. Bonne chance avec ça.

4voto

morecore Points 195

La réponse de BalusC est (comme d'habitude) la bonne.

Mais gardez une chose à l'esprit (comme il l'a déjà dit). La demande finale est faite par le navigateur pour obtenir l'URL du site construit. <img> étiquette. Cela ne se fait pas dans un "contexte jsf".

Ainsi, si vous essayez d'accéder par exemple à la phaseId (journalisation ou autre raison)

context.getCurrentPhaseId().getName()

Il en résultera un NullPointerException et le message d'erreur quelque peu trompeur que vous obtiendrez est le suivant :

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

Il m'a fallu un certain temps pour comprendre quel était le problème.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X